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 https with 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:

  1. In ArtFrameRemote, open Screen › Cards and click Add Widget…. Choose your file. It appears in the list, turned on.
  2. 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

  • id is 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.
  • size decides where the card goes: small cards stack down the left, large ones share a row, and wide ones get a row of their own.
  • data names each source. Its result is data.<name> in the layout. refresh is in seconds; don’t ask more often than the data changes.
  • layout is a tree of nodes: column and row to arrange things, text and label for words, sparkline and bar for numbers, and list to repeat something for each item in an array.
  • {{ }} holds an expression. A | passes the value through a filter: fixed:1 for one decimal place, round, number, time, ago and 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

  • https only. The TV refuses plain http.
  • 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 max on lists and lines on text, and try your widget next to others: the HUD makes cards more compact when the screen is busy, and your layout can check layout.compact and layout.tight to show less.
  • Plan for no data. Until the first fetch, the card shows the source’s loading text, and error if it fails. Use needs to 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.

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:

  1. 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
  2. In widget.json, add "author" and a "version" such as "1.0.0". Raise the version every time you change the file afterwards.

  3. 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.png

    If 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.

  4. 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.