Skip to main content
All CollectionsPlugins
Private Plugins
Private Plugins

Use our API to build your own screens.

TRMNL Team avatar
Written by TRMNL Team
Updated over 2 weeks ago

TRMNL customers with the Developer add-on may use our API to quickly and easily make their own personal dashboards.

Here's a 5 minute demo that showcases what's possible, and how easy it is to get started. Only limited coding knowledge is required.

Step 1 - Upgrade your Account

While logged into TRMNL, hover the device picker dropdown and click on the gear cog icon. Scroll down to "Developer perks" and click to upgrade. This one-time unlocks several new features on your account, forever. Learn more here.

If you chose the "Developer edition" package during purchase, this will automatically be applied on your account and no additional action is required.

Step 2 - Create a Private Plugin

From the Plugins tab, search for "Private Plugin" or "Public Plugin" to begin building. In the example below we'll follow along with a new "Private Plugin."

Let's start with the Name. This is for your eyes only, and will help you keep track of all private plugins from both the Playlists interface and the previous screen. You can build as many plugins as you'd like.

Next is your plugin's Strategy. This determines how TRMNL will receive data that gets rendered on a device screen. If you choose Webhook, you'll need to set up your own web service that sends a POST request to TRMNL with a payload of content. You don't necessarily need a server for this option -- consider Apple Shortcuts, Google Sheets + App Scripts (with automation timers), free Cloudflare Workers, a free REPLit account, GitHub Actions, or a free PythonAnywhere account.

If you choose Polling, TRMNL will fetch this content on your behalf via GET request.

If you choose the Polling strategy, TRMNL needs to know where to fetch this data. Provide a URL in the following Polling URL input box, and feel free to add additional params for authorization.

You may also add headers to your Polling URL (optional).

Assign header key/values with = and separate them with &. So authorization=bearer xxx&content-type=application/json becomes:
​

{
"authorization":"bearer xxx",
"content-type":"application/json"
}

To get started quickly, we've prepared a public endpoint with simple JSON in the response: https://usetrmnl.com/custom_plugin_example_data.json

To test how a pure collection response is handled, use this:
​https://usetrmnl.com/custom_plugin_example_data.json?collection_only=true

To confirm your headers are formatted correctly, make a GET to our example endpoint above with authorization=token qwerty as one of your header values for a special surprise in the response. ;)

Finally, decide whether you want to remove the small amount of padding (5-10 pixels) that is added to all plugins by default, including our native ones. We suggest building your plugin first, then deciding if this is necessary.

This option is primarily for plugins that want to display image content edge-to-edge. It is not a great solution for fitting more text on the screen.

Click Save in the top right to finish stubbing out your plugin.

Step 3 - Design your Plugin

Markup is the last and most important configuration. This is the frontend HTML template for your data to be merged into.

After saving your plugin above, click the "Edit Markup" button from your plugin settings page.

Expand the "Quick-start Examples" section to start working with valid markup right away. Then use our framework docs for help designing the perfect screen to showcase your content.

TRMNL supports the popular Liquid (docs) templating engine to handle merge variables, which means you can leverage {{ variable }} syntax in your Markup to merge in data when you want to create a new screen.

If you chose "Polling" as your Strategy in the previous step, click the "Force Refresh" from the screenshot above to grab fresh data. Then you can work these variables into your markup like so:

Liquid is a powerful templating library, complete with for / each loops as well as filters like {{ text | truncate: 15 }} and many more.

You may also notice some default variables inside a {{ trmnl }} namespace from the "Your Variables" dropdown. These are optional values that may be useful to customize your plugin's markup, for example by merging in a TRMNL user's first name, or leveraging their time zone to make a client-side calculation.

Unofficial Liquid docs:

Advanced, TRMNL-specific filters:

Step 4 - Share your Work

Totally optional, but we'd love to see what you built.

Join the Developer-only Discord server (invite link inside Devices > settings page) and we'll help you show it off on social media, publish to our OSS collection, and more.

Troubleshooting

I don't see any content

If you select Webhook as your strategy, you must POST data to the TRMNL server. Instructions to build this URL are available in our API docs.

If you select Polling as your strategy, TRMNL will GET from that endpoint and assume a JSON response.

In all cases, the latest private plugin screen renders will appear first inside the Plugins > Private Plugin UI, then* on a device per your configured Playlist ordering and refresh rate.

Click 'force refresh' from the Plugin Settings page to request a new screen generation at any time, versus waiting on your device to refresh on a schedule.

My JavaScript isn't doing anything / only works in live preview

Due to our rendering logic, you'll have more success with client side mutations by wrapping your JS like this:
​

document.addEventListener("DOMContentLoaded", function(e) { 
// code goes here
});

This strategy is more reliable than window.onload = function () {} or other approaches.

My live preview editor breaks / page doesn't load

If your markup includes some JavaScript that leaks out and hijacks the parent DOM, you may find yourself in a situation where the Markup editor page is unusable or yields a 500 error.

In this scenario, simply hold the down the shift key while on the previous Settings view, then click the "Edit Markup" button. It's text and link will change to load the markup editor without the live preview window, which will let you fix the bad JavaScript.

I'm sending data but the screen isn't refreshing

By default, our backend skips generating screens if the merge variables are the same between requests. This applies to private and native plugins.

If you're atttempting to "force generate" a screen, perhaps in order to test new Markup or styling, you can work around this by by clicking the "Force Refresh" feature below your Plugin UUID.

Payload data isn't merging into my markup template

If you select Polling as your strategy, we suggest puting relevant merge variables in the root node of your payload.

GET /some-endpoint.json

# works
{
name: "Jimmy",
age: 43
}

# won't work unless markup uses {{ data.name }}, {{ data.age }} etc
{
data: { name: "Jimmy", age: 43 }
}

If your root node content is an array, you can access it via data[0], data[1], and so on.

Did this answer your question?