Protocol
The request
The widget sends a GET request with details about the slot it needs to fill.
Use that information to return a layout that fits the device and widget size.
GET /widget?key=s3cret
&family=systemMedium
&w=338&h=158
&scale=3
&scheme=dark
&locale=en_GB
&tz=Europe/London
&v=1
Host: you.example
Accept: */*
Your own query parameters are preserved, and yours win on a name collision — so
?key=… for a token is safe even though the widget also sends parameters of its
own.
What you receive
| Field | Type | Description |
|---|---|---|
family | string | systemSmall, systemMedium or systemLarge on the Home Screen; accessoryCircular, accessoryRectangular or accessoryInline on the Lock Screen. |
w | number | Slot width in points, for this exact device. |
h | number | Slot height in points. |
scale | number | Screen scale, 2 or 3. Multiply by w and h for pixels. |
scheme | string | light or dark. A hint — see the caveat on the request page. |
locale | string | The reader's locale, such as en_GB. |
tz | string | IANA time zone, such as Europe/London. |
v | number | Protocol version the app speaks. |
Build for the slot
The three families are genuinely different canvases, not one layout at three scales. A
small is about 158 points square; a medium is the same height and twice the width; a large
is roughly square again at over twice the height. Branch on family and send
the layout that suits it.
app.get("/widget", (request, response) => {
const family = request.query.family;
// Answer with a layout that suits the slot rather than one that fits every slot.
const view = family === "systemSmall"
? compactView()
: detailedView();
response.json({ v: 1, view });
}); w and h are the exact point size of the slot on that device, which
varies by screen. Multiply by scale when you are rendering a bitmap and need
pixels — see bitmap mode.
The widget is also offered in the three Lock Screen sizes, so family can
arrive as accessoryCircular, accessoryRectangular or
accessoryInline. They are far smaller than any Home Screen slot and iOS
always draws them in a single-hue vibrant mode, which is the same code path as a tinted
Home Screen: opaque backgrounds are dropped and accentable is what keeps a
node in the bright group. Answer them with a couple of nodes rather than with the layout
you built for a medium. The previews on this site render the three Home Screen families
only; a Lock Screen slot has to be looked at on a device.
Do not rely on scheme alone
scheme is the appearance at the moment of the fetch. WidgetKit renders one
timeline into both appearances, so a response fetched in light mode may still be on screen
after the phone switches to dark, without your server being asked again.
Semantic colour names are immune to this:
they resolve on the device at the moment of drawing, so "color": "primary" is
correct in both appearances. Use scheme to choose a nicer palette, and
semantic names to stay readable. A bitmap cannot adapt at all, which is the main reason to
prefer nodes over pixels.
Tinted Home Screen
When the reader sets the Home Screen to a tinted style, iOS discards most of the colour in a widget and redraws it in a single hue. Nothing in the request describes this, and nothing could: the mode is chosen each time the widget is drawn, one response is drawn in every mode it needs, and restyling the Home Screen redraws what is already on the phone rather than asking your server again.
It is handled on the device instead, where the answer exists. Two things follow for a
payload. An opaque background is dropped in these modes — flattened, it would come out the
same hue as the text sitting on it, and the widget would go blank rather than merely lose
its colour. And accentable: true on a node keeps that node in the bright group
rather than the dim one, which is how a number stays legible once the colour is gone.
Inspect a real request
You cannot attach a debugger to a widget extension, which makes “what did my phone ask for?” a surprisingly awkward question. This site answers it: point a widget at the endpoint below and it renders the request that produced it.
https://webhookwidget.com/api/echo family like any other server should: the medium splits these across two columns, the small answers with only the four rows that fit.Caching
Responses are cached on device and reused when a fetch fails, so a server that is briefly down shows the last good widget with a small staleness dot rather than an error. Cache keys are built from the URL with any credentials removed.
For bitmap responses, Cache-Control: max-age is the
only way to ask for a refresh interval, because there is no envelope to carry
refresh.