Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Trmnl Laravel Package

bnussbau/laravel-trmnl

Laravel integration for TRMNL e-ink displays. Build custom plugins and screens in Laravel, expose data through routes, and render Blade views for your TRMNL device using a simple, familiar API.

View on GitHub
Deep Wiki
Context7

Getting Started

This package turns a Laravel app into a TRMNL plugin host: your app renders a Blade view at an 800×480, 1-bit-friendly size, and a TRMNL e-ink device polls that URL on a schedule and displays the result.

Minimal path:

  1. composer require bnussbau/laravel-trmnl (2.3.0 requires a Laravel version range that now includes 13; Laravel 10–13 should all resolve — check composer why-not if it doesn't).
  2. Publish the config and any assets/views: php artisan vendor:publish --tag=trmnl-config (and the views tag if you intend to override markup). Read the published config/trmnl.php first — it is the only place the route prefix, middleware, and rendering defaults are defined.
  3. Add a route/controller that returns a view using the package's Blade components, then hit it in a browser. The rendered HTML is what the device will show.
  4. In the TRMNL dashboard, create a Private Plugin → Polling URL pointing at your route, and paste the URL (with a token) into the plugin settings. The device pulls on its own refresh interval — you never push to it.

First real use case: a single "today" screen — a title bar plus a list of items pulled from your own database, cached for 5–15 minutes. Get that working end-to-end on a real device before adding layout variants; the 1-bit preview in a desktop browser will lie to you about contrast and font weight.

Where to look first, in order: published config → package Blade component namespace (the layout/framework components are the actual API surface) → laravel-trmnl-blade's component list, since 2.3.0 bumped that dependency from ^2.3.4 to ^3.0.3 and the component names/attributes are what changed.

Implementation Patterns

Render from cache, never from a live API call. The device polls on a fixed cadence and will happily hammer your endpoint. Put a Cache::remember($key, now()->addMinutes(10), fn () => ...) in front of anything slow, and make the controller's job purely "read cache → render view". A cold cache should still return a valid screen (skeleton/last updated line), not a 500.

Separate data from markup. Build a small presenter/DTO (or a TrmnlScreen service) that assembles an array of well-typed values, and keep the Blade view dumb. This is the difference between a screen you can re-layout in five minutes and one you're afraid to touch.

Use the layout components rather than hand-rolling CSS. TRMNL layouts (full, half_horizontal, half_vertical, quadrant) and framework markup (title_bar, split, etc.) exist because e-ink rendering punishes ad-hoc flexbox. Compose with them; override only by publishing the views.

Token-gate the route. Private plugin URLs are effectively bearer tokens. Put a signed route or a static token in the path/query and validate it in middleware, and exclude the route from CSRF. Don't rely on obscurity.

Cache-bust intentionally. If your screen shows "as of" timestamps derived from now(), every poll produces a new render even when the underlying data hasn't changed — fine, but don't let that trick you into thinking the data refreshed. Derive timestamps from the data's own fetched_at.

Local preview workflow. Keep a /trmnl/preview route (or a feature test asserting the view renders) that you can open in a browser and screenshot at 800×480. Add a test that asserts the controller returns 200 and contains expected strings — it catches the component-rename breakage class of bug that this release's dependency bumps can introduce.

Integration tip: if you're pulling data from an external HTML page, that's what voku/simple_html_dom is for. Isolate that scraping in one class so a major-version bump (see below) touches exactly one file.

Gotchas and Tips

The 2.3.0 dependency bumps are the risk, not the Laravel 13 line. Laravel 13 support is additive — if you're on 10–12, nothing breaks. But laravel-trmnl-blade jumped ^2.3.4 → ^3.0.3 (a major), and voku/simple_html_dom jumped ^4.8 → ^5.0 (a major). Both can change public API silently in a minor release of this package. After upgrading: re-render every screen you own, diff any views you published under resources/views/vendor/trmnl, and check every simple_html_dom call site.

simple_html_dom 5.x specifically: major versions of this library have historically changed function signatures and removed legacy helpers. Grep for str_get_html, file_get_html, and direct property access on element nodes before assuming the upgrade is free. If you only use it indirectly, confirm the package's own usage still passes on your PHP version.

Pinned views don't auto-update. If you've published Blade views to override layout, a major bump of the blade package means your overrides are now built against a stale contract. Re-publish to a temp dir and diff rather than overwriting blindly.

E-ink rendering traps: no anti-aliasing, so thin fonts and 1px borders vanish or turn to mud; gradients dither into noise; pure #000/#fff only — mid-grays are unreliable. Test on the device, not the browser. Anything relying on :hover, transitions, or JS will not work.

Config quirks: the route prefix and middleware live in the published config, but the config is merged — if you add a key that a future version also introduces, yours loses or wins unpredictably. Keep your config file minimal and diff it on upgrade.

Debugging a blank or stale screen: the failure is almost always server-side but invisible, because TRMNL shows the last good render or an error card. Log every poll with the response status, add a X-Trmnl-Render header with the cache key/age, and check storage/logs before touching the device. Also confirm the device isn't serving a cached asset — a hard refresh in the dashboard re-polls.

Extension points worth knowing: published views (full control over markup), the config's middleware stack (auth/rate limiting), and wrapping the render call in your own service if you need multi-tenant screens (one URL per user/team, one cached payload each). Avoid forking the package for layout tweaks — publish the view instead.

CI-only noise: the dependabot/fetch-metadata bumps in 2.3.0 affect the package's own GitHub Actions, not your application. Ignore them.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource