Skip to main content

Serverless

Build advanced features and parse large payloads in any language with a free VM.

Written by Ryan Kulp

We welcome advanced community plugins. But sometimes they require authors to host or maintain their own middleman services.

Using TRMNL Serverless you can achieve the flexibility of 3rd party tools (e.g. Cloudflare Workers) without spinning up services or paying for subscriptions.

The legacy version of this feature was called Transformer, and it was replaced entirely by Serverless in August 2026.

Quickstart

  1. Visit Plugins > Private Plugins > New, provide a name and click Save

  2. Click Edit Markup, then click the Serverless tab

  3. Select a programming language and build whatever you want*

*network access is included ;)

Code provided in the Serverless text editor must include a run function that returns a hash / dictionary / JSON friendly object. Below are examples by language.

Ruby

Includes httparty gem for network requests. Must require in your code.

require 'httparty'

def quotes
HTTParty.get('https://dummyjson.com/quotes?limit=10')['quotes']
end

def run(input)
{ input:, quotes: }
end

Node (v20)

Includes fetch() module for network requests.

async function run(input) {
const res = await fetch("https://dummyjson.com/quotes?limit=1");
const data = await res.json();
return { quote: data.quotes[0] };
}


​PHP
Includes standard curl extension for network requests.​
​

function run($input) {
$ch = curl_init("https://dummyjson.com/quotes?limit=1");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
unset($ch);
return ["quote" => $data["quotes"][0]];
}


​Python

Includes Requests library for network request. Must import in your code.
​

import requests

def run(input):
data = requests.get("https://dummyjson.com/quotes?limit=1").json()
return {"quote": data["quotes"][0]}

Accessing existing variables

Global variables like {{ trmnl }} , as well as custom form field values, are available inside the parameter of your run function. We suggest input but it may be whatever you prefer.

Benefits of using this sandbox

In addition to making otherwise disallowed plugin endpoints possible, the run() strategy can enhance your developer experience overall.

Cleaner code

If your plugin needs just a few bits of data but you find yourself traversing deeply nested nodes to find it, surfacing those values as top level JSON objects can simplify your plugin's markup.

Deeper functionality

TRMNL is committed to making Liquid templating a formidable tool in plugin development. But let's not pretend JavaScript isn't a lot more powerful. As plugins become more advanced we hope to reduce the learning curve with data parsing inside this sandbox versus alongside your markup.

Frequently Asked Questions

Is this supported by webhooks?

Yes, private plugins with either the "Polling" or "Webhook" strategy have access to the Serverless function.

Can I refactor my existing (published) plugins to use this feature?

Of course, have fun.

Can I install additional packages into this VM?

Yes, but it's currently a manual process. Send us a live chat or email liz@trmnl.com with a particular package / gem / library you might need, and we can add it to the default image for your preferred language. For example, the Ruby image already has HTTParty included for simple network requests.

What are the specs on this runtime?

You get 128mb and 5 seconds to do your business.

Troubleshooting

We strongly encourage you to enable Debug Logs from your private plugin settings page while developing Serverless plugins. This will return native errors, for example:

let data = await fetch('https://dummyjson.com/quotes?limit=5')                     
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
Did this answer your question?