Put your app's metrics, charts and lists on a TRMNL e-paper display.
A native TRMNL plugin built with Liquid and a JavaScript transform. TRMNL handles polling and rendering; you do not need to host a separate service for the plugin.
Status: The Clawnify display connection is deployed. Creation, live polling, key replacement and disconnection have been verified against a published app, including this plugin's transform. TRMNL account import and physical-device verification are still pending; this is not yet a listed TRMNL recipe. You can preview all layouts locally with the included sample data.
Choose up to four dashboard widgets from one published Clawnify app:
- A metric, such as revenue, open tickets or occupancy.
- A trend, such as orders per day.
- A breakdown, such as your sales pipeline.
- A list, such as today's appointments.
| Layout | Tiles shown |
|---|---|
| Full | First four selected tiles |
| Half horizontal | First two |
| Half vertical | First two |
| Quadrant | First one |
Lists and breakdowns show their first two entries and a count of remaining entries. A footer shows when Clawnify fetched the data, in UTC; it is not the time the underlying records last changed. Smaller layouts use MM-DD HH:MMZ and show a +N count for hidden tiles.
This is a read-only view of app data. Interactive controls and arbitrary web pages are not captured. An app must expose dashboard widgets before it can be connected.
Setup uses the existing Clawnify API through its signed-in CLI. Use a published app that exposes dashboard widgets; replace your-workspace, APP_ID and the example widget keys below with your own.
-
List available app IDs and widget keys, then create a connection selecting one to four keys:
clawnify api '/apps/widgets?scope=all' --org your-workspace clawnify api /apps/APP_ID/display --org your-workspace -X POST \ -d '{"widget_keys":["total-stars","stars-90d"]}'
Copy the one-time
tokenfrom the creation response. It is the display key. -
Build the import file from this repo with
npm run package(requires Python 3). In TRMNL, import_build/clawnify-trmnl-plugin.zipas a Private Plugin. -
Paste the key into the plugin's Display key field, then add the plugin to a playlist. The settings request a 15-minute refresh interval; TRMNL's account and device settings control when updates actually reach the display.
Repeat the POST to change tiles or replace a lost key, then paste the new key into TRMNL. One person has one connection per app; multiple devices can share it, and replacement or disconnection affects all copies. To stop updates:
clawnify api /apps/APP_ID/display --org your-workspace -X DELETETRMNL receives the selected app data and stores the display key. Share only information you want displayed there.
The display key can only read the selected widgets from that app. It is separate from a Clawnify login or personal access token, travels in the Authorization header, and is stored as a hash in Clawnify. Clawnify rechecks the connecting person's access for each poll. Leaving the workspace suspends reads; rejoining can resume an existing connection. Explicitly disconnect a key to revoke it permanently. Signing out of Clawnify does not disconnect a display. Keys do not belong in URLs, source files, screenshots, issues or commits.
A failed fetch may leave an older image on the device. An error response that reaches the template shows Display unavailable. Revoking access stops future reads; it cannot erase an image already downloaded or displayed. Check the fetched timestamp before acting on time-sensitive information.
Requires Node.js 22+, Python 3 and Docker. No account or API key is needed for the sample preview.
git clone https://github.com/clawnify/clawnify-trmnl-plugin.git
cd clawnify-trmnl-plugin
npm test
bin/trmnlp lint
bin/trmnlp test --report _build/report
bin/trmnlp serveOpen localhost:4567. The wrapper generates a gitignored .trmnlp.yml from preview/sample.json and disables polling for this sample. It uses a pinned official trmnlp Docker image. Run python3 bin/sample-config.py after changing the sample.
The Ruby tests exercise the real polling/transform/Liquid/browser pipeline with mocked app responses. Node tests check transformation boundaries. CI runs both and uploads the report and import ZIP; it does not publish a recipe or need credentials.
src/settings.yml Polling endpoint and display-key form
src/transform.js Bounded data formatting; no credentials in output
src/shared.liquid Shared tiles and footer
src/*.liquid Four TRMNL layouts
preview/sample.json Fictional data used by previews and tests
bin/package.py Flat ZIP import, with an explicit file allowlist
To regenerate the README image, run bin/trmnlp build --png and copy _build/full.png to preview/full.png.
The plugin requests GET https://provision.clawnify.com/displays/trmnl with Accept: application/json and Authorization: Bearer <display-key>. The response is versioned:
{
"version": 1,
"name": "Operations",
"fetched_at": "2026-11-09T16:50:00.000Z",
"widgets": [
{ "key": "revenue", "kind": "metric", "title": "Revenue", "value": 42850, "format": "currency", "currency": "EUR" }
]
}Supported kinds are metric, series (points: [{x, y}]), breakdown (items: [{label, value}]) and list (items: [{label, meta?}]). Numeric formats are number, currency and percent; percentages are fractions (0.42 renders as 42%). A response containing error produces an unavailable screen, with no partial data. Unknown contract versions are rejected. See preview/sample.json for all four kinds.
Resend's TRMNL plugin was the reference for the native plugin packaging. This plugin's implementation is original and uses TRMNL's framework and preview tools.
MIT.
