Commands, settings, SDK functions, anything in the docs.

Build apps

Local preview

Run your app on your machine as the platform does, and steer it from the terminal.

On this page

Three commands run your app on your machine the way the platform runs it: developer dev serves a preview page that follows every change, developer set steers that page from another terminal, and developer render writes one image to a file. None of them needs an account, a display or a network connection, except for the hosts your app itself requests.

The preview page

Terminal
ledable developer dev

developer dev builds the project, serves the preview page and prints its address on stderr:

JSON
{"preview":"http://127.0.0.1:4400/"}

The page is served on 127.0.0.1 only, on port 4400 unless you pass --port; --port 0 takes any free port. Open the address exactly as printed: the page refuses requests for any other name, localhost included, so another site cannot read it. The command runs until you stop it with Ctrl-C.

Watching

Every change to a file in the project starts a new build, except in .ledable/, node_modules/ and dist/. A build type-checks the whole project first, as an upload does, then bundles the app; changes made while a build runs start one more when it is done. After each build the command prints a line on stderr, {"built": …} or {"build_error": …}, and every open page draws again.

What goes wrong shows on the page:

  • A build fails, such as on a type error: the error shows above the panel, and the panel keeps the last image that built.
  • render throws: the error and its stack show under the panel, with lines in your own source files.
  • settings throws: the error shows under the panel instead of a form.

What the page shows

  • The panel: the render, as the LEDABLE app draws a display, and how long ago it was drawn. Under it, what the render tells the display: the animation behaviour, whether it is realtime, when the display would ask again (the soft TTL) and when it would give the image up (the hard TTL), the cache id, and how long the render took.
  • Store preview: what preview draws.
  • Settings: a form for the app's settings, built from what settings returns and following the same rules as the form in the LEDABLE app: which settings show, what a select offers, and what keeps the app from being added, which the page lists. Reset clears every setting. For an account or credential setting, type a handle that ledable.dev.json holds.
  • Device: the language, time zone and display time the simulated display asks with, which the render reads as ctx.language, ctx.timezone and ctx.durationMs.

The page draws again after every build and every change, not as time passes: reload it to render again. The settings and device values are kept in .ledable/, so they are still there when you start developer dev again, and every open page shows the same ones.

Steer it from the terminal

Terminal
ledable developer set --set status=Away --set back_at=13:30:00
ledable developer set --timezone Europe/Berlin
ledable developer set --unset back_at
ledable developer set --reset

developer set changes what the running developer dev of the same project simulates, every open page follows, and it prints the render that follows. --set key=value sets a setting and --unset key clears one; both repeat. --reset clears every setting first. --language, --timezone and --duration change the simulated display. A setting the app does not have is refused. The output of the first command above says what the simulation holds, which settings still keep the app from being added, and how the render went:

JSON
{
  "values": {
    "status": "Away",
    "back_at": "13:30:00"
  },
  "panel": {
    "language": "en",
    "timezone": "UTC",
    "durationMs": 5000
  },
  "blocking": [],
  "render": {
    "ok": true,
    "behavior": "loop",
    "realtime": false,
    "softTtlMs": 300000,
    "hardTtlMs": 600000,
    "cacheId": null
  }
}

When the render fails, render has "ok": false, the error's detail and, for an error your code threw, its stack.

Render to a file

Terminal
ledable developer render --out frame.webp
ledable developer render --preview --out preview.webp
ledable developer render --values away.json --timezone Europe/Berlin --out away.webp

developer render builds the project once and writes the WebP a display would get, or with --preview the store preview. It needs no running developer dev.

OptionWhat it does
--out <file>The WebP to write; required
--values <file>A JSON file of settings; settings it leaves out take their defaults
--previewWrite the store preview instead of a render
--language <tag>The display's language, such as en-US; en unless given
--timezone <zone>The display's time zone, an Area/City name; UTC unless given
--duration <seconds>How long the display shows the app, in whole seconds; 5 unless given

The values file is one JSON object, and every value in it is a string, written as below:

away.json
{
  "status": "Away",
  "message": "At lunch",
  "back_at": "13:30:00",
  "color": "[122,146,194]"
}

The values are settled as the LEDABLE app settles them before it adds an app, and the command refuses what the app would refuse: a setting the app does not have, a value its setting does not take, or a required setting left out. It prints where the file went and the headers that tell the display how to play it, as in the Quickstart.

How values are written

--set and --values take each value as text, the way a display's address carries it:

SettingWritten asExample
string, selectThe textAway
integer, floatA number12, 2.5
booleantrue or falsetrue
multiselectA JSON array["Sat","Sun"]
dateYear, month and day2026-12-24
timeHours, minutes and seconds13:30:00
datetimeSeconds since 1 January 1970, UTC1798761600
timezoneAn IANA nameEurope/Berlin
countryA two-letter codeDE
locationA JSON object{"lat":52.52,"lng":13.4,"desc":"Berlin"}
colorRgb, colorRgbaA JSON array[246,205,109], [0,0,0,128]
account, credentialA handle ledable.dev.json holdsmy-github

ledable.dev.json

On the platform, some of what ctx offers comes from LEDABLE's services: your app's secrets, the accounts and tokens people connected, time zone lookups and the news summary. The preview answers them from ledable.dev.json beside ledable.json, which holds your own keys and tokens and stays out of version control. All of it is optional; the preview reads it again on every build.

ledable.dev.json
{
  "secrets": { "TIDES_API_KEY": "your-own-key" },
  "accounts": { "my-github": "a GitHub access token of yours" },
  "credentials": { "my-token": "a token you created for testing" },
  "timezones": [
    { "lat": 52.52, "lng": 13.405, "timezone": "Europe/Berlin" }
  ],
  "news": {
    "items": ["A headline", "Another headline"],
    "counts": {}
  }
}
  • secrets: a value for each secret ledable.json declares, which the app reads from ctx.secrets. As on the platform, an app that declares a secret without a value fails every render. See Secrets.
  • accounts: an access token for each account handle. Put the handle in an account setting, and ctx.auth.getToken(handle) returns the token.
  • credentials: a value for each credential handle, which ctx.credentials.get(handle) returns. A handle the file does not have reads as null, as a credential the person deleted does, so you can see what your app does then. See Accounts and credentials.
  • timezones: places and their time zones. ctx.locations.timezoneAt(lat, lng) answers with a place listed within half a degree of the point.
  • news: the summary ctx.news.getSummary() returns: items, a list of headlines, and counts.

Whatever the app asks for that the file does not have fails with an error that names what is missing, except a credential, which reads as null. The file takes no other keys.

Network rules

The preview holds the app's requests to the same rules as the platform: fetch reaches only HTTPS addresses on the default port, at hosts that network.allowed_hosts declares. Any other request fails in your code with the reason, so a host you forgot to declare shows while you develop, not after you upload:

Output
api.example.com is not among the app's network.allowed_hosts in ledable.json

The preview decodes images through ctx.images within the same size limits as the platform, too.