Docs menu

How-to

Settings, schedules and pages

Let people configure your app, run work on a clock of its own, and give your app a web page.

Settings

Declare each setting in the manifest’s [settings] table. The hub turns them into a form for whoever runs the hub, and gives your app the values in /in/settings.json on every run.

[settings]
latitude = { type = "number", default = 48.43, label = "Latitude" }
units = { type = "choice", choices = ["metric", "imperial"], default = "metric", label = "Units" }
tz_override = { type = "string", optional = true, label = "Time zone", help = "Leave empty to use the screen's" }

The types are string, number, integer, boolean and choice. In Python, ctx.settings is a dictionary of the values with your defaults filled in:

units = ctx.settings["units"]

The manifest reference lists every field.

A test render reads its settings from the example, so you can render the same app with different settings:

[[example]]
name = "imperial"
fixture = "fixtures/default"
now = "2026-10-07T09:00:00-07:00"
settings = { units = "imperial" }
feeds = { now = "452x444.k4" }

Settings belong to your app. The screen’s own preferences, such as how often it updates and its quiet hours, are set on the hub; see Layout and preferences.

TODO(owner): the hub stores app settings, but the form that edits them is part of the planned web interface. Say how someone sets an app’s settings today, or confirm the docs should describe the form.

Schedules

The render job runs when the screen needs cards. For work on a clock of its own, add a job with a schedule:

[[job]]
name = "sync"
every = "15m"
jitter_s = 60

[[job]]
name = "render"
expected_s = 8
  • every runs the job at a fixed interval, at least 5 minutes.
  • daily = "HH:MM" runs it once a day at a local time. Use every or daily, not both.
  • jitter_s adds a random delay, so apps don’t all start at the same second.

A common pattern: sync fetches into /data every 15 minutes, and render only reads /data and draws. Rendering then never waits on the network. Reminders uses daily = "00:01" on its render job so the day’s questions are ready just after midnight.

Every job can also be run by hand, for example from an Update now button on the hub’s app card:

ctx.write_summary("ok", ["17° cloudy"], [{"label": "Update now", "job": "render"}])

A web page for your app

Some things don’t fit a settings form, such as editing a list of reminders. An app can serve its own web page, and the hub shows it under /apps/<id>/. Declare it in the manifest:

[ui]
idle_s = 120

The hub starts the container with the command serve when someone opens the page, and stops it after idle_s seconds with nobody using it. While the page is running, the hub sends jobs to the same container instead of starting a new one.

With the Python SDK, register routes with kit.route, and kit.main() handles serve:

@kit.route("GET", r"/")
def index(request):
    return 200, {"Content-Type": "text/html; charset=utf-8"}, "<h1>Reminders</h1>"

The pattern is a regular expression matched against the whole path; groups become extra arguments. request is the Python http.server request handler. Use relative URLs in your pages: the hub serves them under a prefix.

TODO(owner): app pages are proxied by the hub’s planned web interface; today the hub runs them but doesn’t serve them on :8080 yet. Confirm how to present this, and add a screenshot of an app page once there is one.

Updates, now and then

A short note when there is a new board, a new slowapp, or kit news. Rarely more than one a month.