Widgets
Build a widget
A widget is one JSON file. It names where its data comes from and how to lay it out, and the TV fetches the data and draws the card. There’s no code to write and nothing to compile: if you can read an API’s JSON, you can make a widget from it.
The built-in clock, weather, calendar, markets and headlines are written the same way, and yours are drawn by the same code, in the same theme and accent colour.
Rather have an AI assistant write it? Build with AI has prompts for Claude Code, Codex, Cursor, ChatGPT and the rest.
What you need
- ArtFrameRemote on your Mac, set up with your TV.
- A text editor. One that understands JSON Schema (VS Code, for instance) will check your file as you type.
- An API that answers over
httpswith JSON, RSS or plain text, without a login. An API key you paste into the widget’s settings is fine.
Your first widget
This one shows where the International Space Station is. Save it as
iss.widget.json:
{
"$schema": "https://artframe.mlynn.dev/widgets/widget.schema.json",
"spec": 1,
"id": "yourname.iss",
"name": "Space station",
"description": "Where the International Space Station is right now",
"icon": "globe.americas",
"size": "small",
"title": "Space station",
"data": {
"iss": {
"source": "http",
"url": "https://api.wheretheiss.at/v1/satellites/25544",
"refresh": 120
}
},
"layout": {
"type": "column",
"children": [
{ "type": "text", "size": 22, "text": "{{ data.iss.latitude | fixed:1 }}°, {{ data.iss.longitude | fixed:1 }}°" },
{ "type": "text", "color": "muted", "text": "{{ data.iss.altitude | round }} km up" }
]
}
}
Then:
- In ArtFrameRemote, open Screen › Cards and click Add Widget…. Choose your file. It appears in the list, turned on.
- Click Apply to TV. The widget goes to the TV with your HUD settings, and the card shows up the next time the information screen does.
If something’s wrong, the widget is listed as not loaded, with every problem spelled out: an unknown key, a typo in an expression, a URL that isn’t allowed. Fix the file and add it again; the new copy replaces the old one.
What each part does
idis how the HUD knows your widget. Start it with your name or handle and a dot, so it can’t clash with anyone else’s. Ids without a dot belong to the built-ins.sizedecides where the card goes:smallcards stack down the left,largeones share a row, andwideones get a row of their own.datanames each source. Its result isdata.<name>in the layout.refreshis in seconds; don’t ask more often than the data changes.layoutis a tree of nodes:columnandrowto arrange things,textandlabelfor words,sparklineandbarfor numbers, andlistto repeat something for each item in an array.{{ }}holds an expression. A|passes the value through a filter:fixed:1for one decimal place,round,number,time,agoand more.
Settings
Let people point your widget at their own thing. Each setting becomes a
field in the Mac app, and its value is settings.<key>:
"settings": [
{ "key": "repo", "label": "Repository", "type": "text", "default": "mrlynn/artframe-widgets" }
],
"data": {
"repo": { "source": "http", "url": "https://api.github.com/repos/{{ settings.repo | trim }}" }
}
Settings can be text, number, toggle or choice. Mark a text setting
"secret": true for an API key: the Mac app hides it as you type, and the TV
only ever sends it to your widget’s own site.
You can also read the HUD’s settings, such as hud.location, hud.metric
and hud.clock24h, and build on the TV’s own weather, air quality, moon,
markets, calendar and headlines data with "source": "weather" and the rest.
The spec lists what each one gives you.
The rules that trip people up
httpsonly. The TV refuses plainhttp.- Write the site out. The part of a URL before the first
/or?has to be plain text:https://api.example.com/v1?q={{ settings.city | url }}is fine;https://{{ settings.region }}.example.com/isn’t. That’s how the TV, and the person installing your widget, know exactly where it sends their data. A URL that’s nothing but one text setting ("{{ settings.feed }}") lets the person choose the site instead. - Cards don’t scroll. What doesn’t fit is cut off at the card’s edge.
Use
maxon lists andlineson text, and try your widget next to others: the HUD makes cards more compact when the screen is busy, and your layout can checklayout.compactandlayout.tightto show less. - Plan for no data. Until the first fetch, the card shows the source’s
loadingtext, anderrorif it fails. Useneedsto explain what to set up first (“Add a repository in settings”). - Limits. 64 KB per file, 400 layout nodes, 512 KB per response, one fetch a minute per source at most.
Sharing it in the gallery
The gallery is a GitHub repository, mrlynn/artframe-widgets. Every widget in it is checked by the TV’s own code and reviewed by a person before it’s listed. To add yours:
-
Fork the repository and add a folder named after your widget’s id:
widgets/yourname.iss/ widget.json your widget screenshot.png the card on your TV README.md optional: what it shows and where the data comes from -
In
widget.json, add"author"and a"version"such as"1.0.0". Raise the version every time you change the file afterwards. -
Take the screenshot on the TV itself. ArtFrameRemote includes the tool for it; with the TV awake and showing your card:
/Applications/ArtFrameRemote.app/Contents/Helpers/adb exec-out screencap -p > shot.pngIf it says there’s no device, connect first with
adb connect <your TV's IP address>. Crop the picture to your card with a little of the art around it: 300 to 1920 pixels wide, under 1 MB. -
Open a pull request. The checks run on their own and say exactly what to fix; you can run them first with
tools/build_index.py --check(it needs Java 17 and Python 3).
A reviewer then checks what a computer can’t: that the API is public and allows this use, that the widget only sends what it needs to where it needs it, that it refreshes no more often than it must, and that it reads well from the sofa in every theme. The full list is in CONTRIBUTING.md.
Once it’s merged, your widget appears here and in Browse Gallery… in ArtFrameRemote.
Going further
- The widget spec is the complete reference: every node type, attribute, filter and data source.
- The examples and the built-in widgets show the spec in use. To change a built-in, copy it, keep its id, and add your copy; it replaces ours on your TV.
- Every widget in the gallery is there to read and borrow from.