API Reference
Everything you need to design an endpoint that Glance Widgets can render: what requests the widget sends, what response format your endpoint should return, and the rules the renderer follows. Every live example on this page is rendered by the same rules the app uses.
Overview#
A Glance Widgets widget is driven by one HTTPS GET request to an endpoint you own. Your endpoint returns one JSON object — a frame — that describes what to draw. The app validates and normalizes the frame, and WidgetKit renders it on the Home Screen.
A frame has two expressive channels that are composited together:
- The root pixel grid (
pixels) — a rectangular dot matrix of up to 4,096 cells laid out within the full widget canvas. Style every dot, or leave most of it todefaultandbatch. - Layers — content elements with position and size: text, curves, images, icons, lines, shapes and local dot matrices.
data so your server renders the whole widget — see Server-rendered images.Quick start#
The smallest useful response is a grid. Elements in the pixel style array inherit default when their value is null; explicit objects override it.
{
"protocol": 1,
"default": { "color": "#8EC8FB", "opacity": 0.12, "cornerRadius": 0.5 },
"pixels": [
[null, null, null, null, null],
[
null,
{ "color": "#7EDDB5", "opacity": 1 },
null,
{ "color": "#7EDDB5", "opacity": 1 },
null
],
[null, null, null, null, null],
[
{ "color": "#DFFF70", "opacity": 1 },
null,
null,
null,
{ "color": "#DFFF70", "opacity": 1 }
],
[
null,
{ "color": "#DFFF70", "opacity": 1 },
{ "color": "#DFFF70", "opacity": 1 },
{ "color": "#DFFF70", "opacity": 1 },
null
]
]
}- Deploy the endpoint anywhere that serves HTTPS.
- In the app, create a widget and paste the URL in the API field.
- Preview it at real Home Screen size, then add the widget to your Home Screen.
Widget content structure#
Card material and content
A widget combines the card’s visual style and material with the content inside it. Material is configured in the app’s custom widget editor, or potentially an editor on another platform in the future. The API cannot select or dynamically change the card material; it supplies content and content styling.
Layers on the card come from two places. Editor layers are added, positioned and configured in the editor’s visual workspace and saved with the widget. An editor layer can stay fully static, or it can have a data path so the API updates its position, content and style on every response. The editor’s settings are then the initial state before the first response and the fallback for any field a response leaves out.
Remote layers need no configuration in the editor: the API declares them in each response. They can be content the saved widget never contained, defined entirely by the API. You can use both kinds together; the Response body and Layers sections show how each is written.
Basic layers and pixel grids
In Glance Widgets, you can express basic content by adding layers such as text, straight lines, curves, images, icons and shapes. Layers can overlap, with their stacking order controlled by the dedicated zIndex field.
A pixel grid extends these possibilities while keeping the description and parsing simple. Your server can generate its own visual expressions as a two-dimensional array. You can also express the basic content types as pixel art when that suits your needs or preferences; the cell budget determines the available detail.
A pixel grid has two forms. A matrix layer (type: "matrix") occupies a positioned, sized region of the canvas like any other layer; its content is a grid. The root grid (top-level pixels) is outside the layer system and uses the whole content canvas as its layout area. Both share the same cell styling, layout rules and API structure. The root form makes a single, consistently styled pixel theme easier to express without creating a layer. Its layout fits and centers the grid; whether dots visually fill the card depends on the grid aspect ratio, gaps and edge inset.
The grid’s logical positions continue to exist even when some dots are hidden. You can style each dot or leave a position blank, like the unlit cells on an old red-on-black pixel display. Blank regions can form a pattern or leave room for other content layers. The “Pixels & precedence” section explains the explicit {"empty": true} value.
Three composited levels
- Bottom: card style and material, configured in the editor as the background and container.
- Middle: root grid, laid out across the content canvas, with individual or all cells optionally blank.
- Top: content layers, combining editor layers and remote layers declared by the API in
zIndexorder. Matrix layers belong here too.
The request#
The widget sends an HTTPS GET. Your own query parameters are preserved, and the app appends reserved gw_* parameters describing the space it is drawing into. Non-HTTPS URLs are rejected.
| Parameter | Example | Description |
|---|---|---|
gw_protocol | 1 | Protocol version the client speaks. |
gw_platform | ios | Client platform. |
gw_family | small · medium · large | Widget size family. |
gw_canvas_width_pt | 158.00 | Available content canvas width in iOS logical pixels (pt), to two decimal places. |
gw_canvas_height_pt | 158.00 | Available content canvas height in iOS logical pixels (pt), to two decimal places. |
gw_aspect_ratio | 1.0000 | Width ÷ height (four decimals). |
gw_context | app_preview | Rendering context. app_home: app home; app_preview: editor preview; widget: Home Screen widget. |
gw_state_<key> | — | Reserved for future interaction-state extensions; not used by the current request flow. All parameter names starting with gw_ are reserved for the client and must not be used for custom query parameters. |
gw_protocol- Example
1 - DescriptionProtocol version the client speaks.
gw_platform- Example
ios - DescriptionClient platform.
gw_family- Example
small·medium·large - DescriptionWidget size family.
gw_canvas_width_pt- Example
158.00 - DescriptionAvailable content canvas width in iOS logical pixels (pt), to two decimal places.
gw_canvas_height_pt- Example
158.00 - DescriptionAvailable content canvas height in iOS logical pixels (pt), to two decimal places.
gw_aspect_ratio- Example
1.0000 - DescriptionWidth ÷ height (four decimals).
gw_context- Example
app_preview - DescriptionRendering context.
app_home: app home;app_preview: editor preview;widget: Home Screen widget. gw_state_<key>- Example—
- DescriptionReserved for future interaction-state extensions; not used by the current request flow. All parameter names starting with
gw_are reserved for the client and must not be used for custom query parameters.
pt is the iOS logical layout unit, corresponding to Flutter logical pixels in the app. It is neither a physical screen pixel count nor a grid cell count. At a 3× display scale, for example, 158 pt spans 474 physical pixels. Both parameters exclude the card’s content inset: width = outer widget width − 2 × contentInset, and likewise for height, with a minimum of 1 pt.
Canvas sizes vary by device. On 390–393 pt wide iPhones, the sizes are roughly 158 × 158 (small), 338 × 158 (medium) and 338 × 354 (large).
GET https://api.example.com/widget?team=core&gw_protocol=1&gw_platform=ios&gw_family=medium&gw_canvas_width_pt=338.00&gw_canvas_height_pt=158.00&gw_aspect_ratio=2.1392&gw_context=app_previewTimeouts are short: about 8 s to send, and 12 s to receive in the app. Any 2xx status is a success. Respond with UTF-8 JSON, ideally with Content-Type: application/json.
Response body#
Return one UTF-8 JSON object, at most 512 KB. protocol is optional; explicitly return 1, the only supported non-null version. The object describes the content for this request. Read it in three groups: the root grid, the data root and the remote layer declarations in layers. Unknown top-level fields are ignored.
Root grid: pixels, default, layout and batch
pixels determines the grid’s m*n dimensions and describes the cell styles; layout arranges the cells. Building complex arrays cell by cell is tedious even with server-side code, so batch applies one style to many dots and default covers cells without an individual declaration.
| Field | Type / values | Purpose and rules |
|---|---|---|
pixels | (Pixel | null)[][] | Required root grid content. Each entry is the style of one logical cell at pixels[row][column], starting top-left. A rectangle of at least 1 × 1 and at most 4,096 cells. |
default | Pixel | Default cell style; also supplies omitted properties in explicit pixel objects and each batch style. |
layout | MatrixLayout | Arranges the whole grid: cell spacing, aspect ratio and edge inset. It does not define individual dot colors or shapes. |
batch | { pixel, coordinates }[] | Apply one pixel style to a set of zero-based [row, column] coordinates. Useful for server-generated patterns without repeating a style for every dot. |
pixels- Type / values
(Pixel | null)[][] - Purpose and rulesRequired root grid content. Each entry is the style of one logical cell at
pixels[row][column], starting top-left. A rectangle of at least 1 × 1 and at most 4,096 cells. default- Type / values
Pixel - Purpose and rulesDefault cell style; also supplies omitted properties in explicit pixel objects and each batch style.
layout- Type / values
MatrixLayout - Purpose and rulesArranges the whole grid: cell spacing, aspect ratio and edge inset. It does not define individual dot colors or shapes.
batch- Type / values
{ pixel, coordinates }[] - Purpose and rulesApply one
pixelstyle to a set of zero-based[row, column]coordinates. Useful for server-generated patterns without repeating a style for every dot.
Precedence is default → batch entries in order → explicit pixels objects. null keeps the default/batch result. Omitted properties in an explicit object fall back to default, rather than merging properties from a batch. Omitting a cell’s style means putting null in its slot; do not remove array entries or shorten rows. See “Pixels & precedence” for examples.
The data root: data
The optional data object defaults to an empty object and holds what your layers read. You choose the field names and nesting, for example grouping the layers of one panel under data.orders. The Image material’s data path also reads its image from here (see Server-rendered images).
data itself is just a freely organized object, and its values are not drawn by themselves. Dynamic content created in the editor, or remote layers your API returns, read a field of data that you specify through dataPath and are parsed and drawn according to the rules. If a field drives an editor layer or a remote layer, it must be an object that follows the protocol for that layer type. In general, the field a dataPath points to contains the layer’s position, content and style; see layers below and the Layers section.
Remote layers: layers
The optional layers array declares layers your API adds; omit it for none. Each entry has exactly three fields: id, unique in the response and starting with remote:; type, one of the seven layer types; and dataPath, pointing to the object in data that describes the layer.
A layer’s position, content and style do not go in the layers entry. They go in the object dataPath points to: frame, zIndex, rotation and transform set size and layout, content is the content and style the style. A remote layer is built entirely from that object, so it must have a frame.
{
"protocol": 1,
"pixels": [[{ "empty": true }]],
"data": {
"orders": {
"badge": {
"frame": { "right": 0.07, "top": 0.14, "width": 0.24, "height": 0.2 },
"content": { "text": "LIVE" },
"style": { "color": "#0B1222", "backgroundColor": "#7EDDB5", "opacity": 1, "cornerRadiusRatio": 1 }
}
}
},
"layers": [
{ "id": "remote:badge", "type": "text", "dataPath": "data.orders.badge" }
]
}Each response’s layers is the complete remote layer set for that frame and is replaced as a whole by the next successful response; it is not a list of incremental edits. Layers added in the editor are not declared here; they are bound by the data path set in the editor, as described in the Layers section.
pixels remains required for a layer-only frame. Return [[{"empty": true}]] to leave the root grid blank and draw layers over the card material.Complete response body type reference#
The nested object and related types below describe the response. ? marks an optional field, [] an array and | alternatives. This is a type reference, not a JSON response to copy. A layer’s type selects its corresponding content and style; fields from unrelated types are not interchangeable. The following sections explain purposes, units and conditions.
type WidgetResponse = {
protocol?: 1
pixels: (Pixel | null)[][]
default?: Pixel
layout?: MatrixLayout
batch?: Batch[]
// Objects that layers point to live anywhere in data; layers only declare remote layers.
data?: { [key: string]: JsonValue | LayerNode }
layers?: {
id: `remote:${string}`
type: 'text' | 'matrix' | 'curve' | 'image' | 'icon' | 'line' | 'shape'
dataPath: string // points to the object in data that describes this layer
}[]
}
// The object a dataPath points to. type selects content and style:
// text → TextContent / TextStyle, matrix → MatrixContent / (none),
// curve → CurveContent / CurveStyle, image → ImageContent / ImageStyle,
// icon → IconContent / IconStyle, line → LineContent / LineStyle,
// shape → (none) / ShapeStyle
type LayerNode = {
frame?: Frame // required for remote layers
zIndex?: number
rotation?: number
transform?: { translateX?: number; translateY?: number }
content?: object
style?: object
}
type Frame = {
width: number; height: number
left?: number; right?: number; top?: number; bottom?: number
}
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
type Color = string // #RRGGBB / #AARRGGBB
type GapMode = 'zero' | 'near' | 'default' | 'far' | 'extraFar'
type Pixel = {
color?: Color; opacity?: number; cornerRadius?: number
rotation?: number; halo?: number; empty?: boolean
}
type Batch = { pixel: Pixel; coordinates: [row: number, column: number][] }
type MatrixLayout = {
gapMode?: GapMode; edgeInsetRatio?: number; cellAspectRatio?: number
columnGapRatio?: number; rowGapRatio?: number
}
type MatrixContent = {
pixels?: (Pixel | null)[][] // required for remote layers
default?: Pixel; batch?: Batch[]; layout?: MatrixLayout
}
type TextContent = { text?: string | number }
type TextStyle = ChromeStyle & {
color?: Color; textOpacity?: number; fontSizeRatio?: number
fontWeight?: '300' | '400' | '500' | '600' | '700'
fontFamily?: 'system' | 'systemMonospace' | 'monospace'
fontFamilyFallback?: string[]; letterSpacing?: number; baselineRatio?: number
horizontalAlignment?: 'left' | 'center' | 'right'
verticalAlignment?: 'top' | 'center' | 'bottom'
autoWrap?: boolean; paddingRatio?: number
}
type ChromeStyle = EffectsStyle & {
backgroundColor?: Color; opacity?: number
cornerRadiusRatio?: number
borderColor?: Color; borderOpacity?: number; borderWidth?: number
gradientEnabled?: boolean; gradientColors?: Color[]
gradientStops?: number[]; gradientAngle?: number
}
type EffectsStyle = {
maskColor?: Color; maskOpacity?: number
vignetteIntensity?: number
readabilityGradient?: 'none' | 'top' | 'bottom' | 'left' | 'right' | 'both'
| 'topLeftDiagonal' | 'topRightDiagonal'
}
type ShapeStyle = ChromeStyle & { color?: Color }
type ImageContent = { url?: string }
type ImageStyle = ChromeStyle & { color?: Color }
type IconContent =
| {
icon?: string; iconCodePoint?: number
iconFont?: 'phosphorBold' | 'devicon' | 'simpleIcons' | 'material'
}
| { url?: string }
type IconStyle = {
color?: Color; textOpacity?: number
iconGlowEnabled?: boolean; iconGlowColor?: Color
iconGlowOpacity?: number; iconGlowRadius?: number
}
type LineContent = { direction?: 'horizontal' | 'vertical' }
type LineStyle = {
color?: Color; opacity?: number; strokeWidth?: number
pattern?: 'solid' | 'dashed' | 'dotted'; cap?: 'round' | 'butt'
dashLength?: number; dashGap?: number
gradientEnabled?: boolean; gradientColors?: Color[]; gradientStops?: number[]
}
type CurveContent = {
mode?: 'samples' | 'function'
points?: [x: number, y: number][]
expression?: string; parameters?: { [name: string]: number }
domain?: [number, number]; yDomain?: [number, number]
interpolation?: 'linear' | 'stepAfter' | 'monotone'
markers?: {
at: { x?: number; sampleIndex?: number; offsetPt?: [number, number] }
symbol?: { shape?: 'circle'; radiusPt?: number; fill?: Color }
label?: string
labelStyle?: {
color?: Color; fontSizePt?: number
position?: 'top' | 'bottom' | 'left' | 'right'
}
}[]
}
type CurveStyle = {
color?: Color; opacity?: number; strokeWidth?: number
lineCap?: 'round' | 'butt' | 'square'; lineJoin?: 'round' | 'bevel' | 'miter'
dashLength?: number; dashGap?: number
fillColor?: Color; fillOpacity?: number; plotPadding?: number
}Pixel style#
These fields describe one pixel’s style in the root grid’s default, batch[].pixel or pixels[row][column]. They also apply to default, batch[].pixel and pixels in a matrix layer’s content.
| Field | Range | Description |
|---|---|---|
color | #RRGGBB · #AARRGGBB | Hex color. The 8-digit form is alpha first (ARGB), not CSS #RRGGBBAA. |
opacity | 0 – 1 | Dot visibility, clamped and multiplied by color alpha: 0 is transparent; 1 applies no further opacity reduction. |
cornerRadius | 0 – 0.5 | Radius relative to the cell short side: 0 sharp, 0.5 maximally rounded (a circle for square cells, a capsule for rectangular cells). |
rotation | degrees | 45 turns a square into a diamond. |
halo | 0 – 1 | Dot glow strength; 0 disables it. Highlights selected cells or adds a lit pixel-display appearance. |
empty | boolean | true skips drawing the cell. |
color- Range
#RRGGBB·#AARRGGBB - DescriptionHex color. The 8-digit form is alpha first (ARGB), not CSS
#RRGGBBAA. opacity- Range0 – 1
- DescriptionDot visibility, clamped and multiplied by color alpha:
0is transparent;1applies no further opacity reduction. cornerRadius- Range0 – 0.5
- DescriptionRadius relative to the cell short side:
0sharp,0.5maximally rounded (a circle for square cells, a capsule for rectangular cells). rotation- Rangedegrees
- Description
45turns a square into a diamond. halo- Range0 – 1
- DescriptionDot glow strength;
0disables it. Highlights selected cells or adds a lit pixel-display appearance. empty- Rangeboolean
- Description
trueskips drawing the cell.
For the root grid, if you omit default, pixels start from color #0A2D48, opacity 0.28, cornerRadius 0.18. Set opacity: 1 when you want vivid, fully opaque colors.
Pixels & precedence#
Each cell resolves in a fixed order, lowest to highest priority:
default(or the built-in seed if absent)- each
batchentry, in order: later style objects replace earlier ones, and each entry’s omitted properties come from default - explicit, non-null values in
pixels
null means no override here, not transparent. {"empty": true} is an explicit blank: nothing is drawn, but the cell keeps its slot in the grid. An explicit pixel object falls back to default for omitted color and style properties, not the batch beneath it. The empty flag is explicit: with default {"empty": true}, null slots stay blank, while an ordinary explicit pixel object draws a dot again.
{
"protocol": 1,
"layout": { "gapMode": "near" },
"default": { "color": "#18313A", "opacity": 0.34, "cornerRadius": 0.18 },
"pixels": [
[
{ "color": "#7CFFB2", "cornerRadius": 0.18 },
null,
{ "color": "#F4BC54", "rotation": 45 }
],
[
{ "empty": true },
{ "color": "#FFFFFF", "opacity": 0.7 },
null
]
],
"batch": [
{
"pixel": { "color": "#38A6FF", "cornerRadius": 0.5, "opacity": 1 },
"coordinates": [
[0, 1],
[1, 2]
]
}
]
}Batch coordinates are zero-based [row, column] integer pairs and must lie inside the grid; out-of-range coordinates reject the frame in the app.
Grid layout#
These fields describe the whole grid’s layout. Put them in the response’s layout object, or in a matrix layer’s content.layout.
The grid is uniformly fitted and centered with contain, preserving the pixel’s width-to-height ratio. Gaps and edge insets use the pixel’s short side as their reference, rather than a percentage of the whole card. Values of edgeInsetRatio, columnGapRatio and rowGapRatio are multiples of a single pixel’s short-side length. gapMode selects a preset gap multiplier, while cellAspectRatio specifies the pixel’s width-to-height ratio.
| Field | Values | Default | Purpose |
|---|---|---|---|
gapMode | zero · near · default · far · extraFar | default | Gap presets between adjacent pixels: 0, 0.08, 0.16, 0.28, 0.42 times a single pixel’s short-side length, respectively. Applies horizontally and vertically. |
edgeInsetRatio | −0.45–0.45 | 0 | Margin around the grid = a single pixel’s short-side length × this value. Positive values add margin; negative values extend edge pixels beyond the drawing region, where they are clipped. |
cellAspectRatio | 0.1–10 | 1 | Width / height of a single pixel. 1 makes a square, below 1 a vertical bar, above 1 a horizontal bar. Does not change the grid’s row or column count. |
columnGapRatio | 0–10 | — | Horizontal gap between adjacent pixel columns = a single pixel’s short-side length × this value. Overrides the horizontal gapMode spacing; omitted values use the preset. |
rowGapRatio | 0–10 | — | Vertical gap between adjacent pixel rows = a single pixel’s short-side length × this value. Overrides the vertical gapMode spacing; omitted values use the preset. |
gapMode- Values
zero·near·default·far·extraFar - Default
default - PurposeGap presets between adjacent pixels:
0,0.08,0.16,0.28,0.42times a single pixel’s short-side length, respectively. Applies horizontally and vertically. edgeInsetRatio- Values−0.45–0.45
- Default
0 - PurposeMargin around the grid = a single pixel’s short-side length × this value. Positive values add margin; negative values extend edge pixels beyond the drawing region, where they are clipped.
cellAspectRatio- Values0.1–10
- Default
1 - PurposeWidth / height of a single pixel.
1makes a square, below1a vertical bar, above1a horizontal bar. Does not change the grid’s row or column count. columnGapRatio- Values0–10
- Default—
- PurposeHorizontal gap between adjacent pixel columns = a single pixel’s short-side length × this value. Overrides the horizontal
gapModespacing; omitted values use the preset. rowGapRatio- Values0–10
- Default—
- PurposeVertical gap between adjacent pixel rows = a single pixel’s short-side length × this value. Overrides the vertical
gapModespacing; omitted values use the preset.
aspect = cellAspectRatio
short = min(aspect, 1)
gx = (columnGapRatio ?? gapModeRatio) * short
gy = (rowGapRatio ?? gapModeRatio) * short
inset = edgeInsetRatio * short
spanX = columns * aspect + (columns - 1) * gx + 2 * inset
spanY = rows + (rows - 1) * gy + 2 * inset
cellHeight = min(canvasWidth / spanX, canvasHeight / spanY)
cellWidth = cellHeight * aspect{
"protocol": 1,
"default": { "color": "#FFFFFF", "opacity": 0.1, "cornerRadius": 0.5 },
"layout": { "gapMode": "near", "cellAspectRatio": 0.32, "edgeInsetRatio": 0.45 },
"pixels": [
[
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#FFB86B", "opacity": 1 },
{ "color": "#FFB86B", "opacity": 1 },
{ "color": "#FFB86B", "opacity": 1 },
null,
null,
null,
null,
null,
null,
null
]
]
}Layers#
Layers are flat — no nesting, no children. They are drawn over the root grid; remote and editor layers are composited together by zIndex, with id order breaking ties.
Layer declarations
Each entry in layers declares one remote layer:
| Field | Type | Description |
|---|---|---|
id | string | Starts with remote: and is unique within the response. Distinguishes layers and orders those with equal zIndex. |
type | string | text · matrix · curve · image · icon · line · shape. Selects the fields allowed in content and style of the object dataPath points to. |
dataPath | string | Where the object that describes this layer sits in data; see “Data paths” below. |
id- Typestring
- DescriptionStarts with
remote:and is unique within the response. Distinguishes layers and orders those with equalzIndex. type- Typestring
- Description
text·matrix·curve·image·icon·line·shape. Selects the fields allowed incontentandstyleof the objectdataPathpoints to. dataPath- Typestring
- DescriptionWhere the object that describes this layer sits in
data; see “Data paths” below.
Extra or missing fields, a wrong or duplicate id prefix, an unknown type, or a malformed dataPath make the whole response invalid.
Describing a dynamic layer
Whether it is an editor layer, added in the editor and saved as part of the custom widget configuration, or a remote layer returned purely by the API, a layer points to a field inside data through a path. For the former you enter the path in the matching form field in the editor; for the latter it is the dataPath field described earlier. The object a dataPath points to has the same structure in both cases, made of layout, content and style:
| Field | Type | Description |
|---|---|---|
frame | object | Position and size, see below. Required for remote layers. Optional for editor layers, where it replaces the editor’s position and size as a whole. |
zIndex | integer | Stacking order; larger values draw above smaller ones. Remote layers default to 0. |
rotation | degrees | Rotates the whole layer around its center. Remote layers default to 0. Per-pixel rotation in a matrix uses the pixel style’s rotation. |
transform | object | translateX, translateY: an offset applied after the frame is placed, in fractions of the layer’s own width and height. |
content | object | Layer content; fields depend on type, see “Layer styles and content”. |
style | object | Layer style; fields depend on type. matrix has no style fields because its styling is split elsewhere: what shapes the grid as a whole belongs to layout, and how each individual pixel is drawn lives in the pixels 2D array. The default style and batch styles also sit in content. |
frame- Typeobject
- DescriptionPosition and size, see below. Required for remote layers. Optional for editor layers, where it replaces the editor’s position and size as a whole.
zIndex- Typeinteger
- DescriptionStacking order; larger values draw above smaller ones. Remote layers default to
0. rotation- Typedegrees
- DescriptionRotates the whole layer around its center. Remote layers default to
0. Per-pixel rotation in a matrix uses the pixel style’srotation. transform- Typeobject
- Description
translateX,translateY: an offset applied after the frame is placed, in fractions of the layer’s own width and height. content- Typeobject
- DescriptionLayer content; fields depend on
type, see “Layer styles and content”. style- Typeobject
- DescriptionLayer style; fields depend on
type.matrixhas no style fields because its styling is split elsewhere: what shapes the grid as a whole belongs tolayout, and how each individual pixel is drawn lives in thepixels2D array. The default style and batch styles also sit incontent.
This object accepts only the fields above, and content and style accept only the fields listed for the layer’s type. An omitted field and a null field both mean “not provided”.
How widget layers merge
- Precedence is the object
dataPathpoints to > editor settings > renderer defaults. - Object values inside
contentandstylemerge field by field onto the editor’s saved values — for example a matrix’scontent.defaultandcontent.layout. Strings, numbers, booleans and arrays replace the saved value as a whole. framealways replaces the saved frame as a whole. Anchors only make sense together: a responserightmerged onto a savedleftwould simply lose to it. That is why a returnedframemust includewidthandheight.- An omitted or
nullfield keeps the editor’s value;0,falseand empty arrays are valid overrides. - Each response starts again from the saved configuration. When a later response stops returning a field, the editor’s value returns; when a request fails, the last successful response stays on screen.
Matrix, text, curve, line and shape layers have a “Current preview” switch in the editor. It only affects the edit page: the default-style preview shows the editor’s values so you can tune the fallback appearance. The app home, Home Screen and StandBy always apply the response.
[
{
"id": "local:title",
"type": "text",
"frame": { "left": 0.07, "top": 0.12, "width": 0.6, "height": 0.16 },
"zIndex": 10,
"content": { "text": "Orders today" },
"style": {
"color": "#A9C4E8",
"fontSizeRatio": 0.1,
"fontWeight": "500",
"horizontalAlignment": "left"
}
},
{
"id": "local:total",
"type": "text",
"frame": { "left": 0.07, "top": 0.32, "width": 0.6, "height": 0.4 },
"zIndex": 11,
"dataPath": "data.orders.total",
"content": { "text": "—" },
"style": {
"color": "#FFFFFF",
"fontSizeRatio": 0.26,
"fontWeight": "700",
"horizontalAlignment": "left"
}
}
]{
"protocol": 1,
"pixels": [
[
{ "empty": true }
]
],
"data": {
"orders": {
"total": {
"content": { "text": 128 },
"style": { "color": "#7EDDB5" }
},
"badge": {
"frame": {
"right": 0.07,
"top": 0.14,
"width": 0.24,
"height": 0.2
},
"zIndex": 12,
"content": { "text": "LIVE" },
"style": {
"color": "#0B1222",
"fontSizeRatio": 0.08,
"fontWeight": "700",
"horizontalAlignment": "center",
"verticalAlignment": "center",
"backgroundColor": "#7EDDB5",
"opacity": 1,
"cornerRadiusRatio": 1
}
}
}
},
"layers": [
{
"id": "remote:badge",
"type": "text",
"dataPath": "data.orders.badge"
}
]
}In this example local:title has no data path and always shows its editor text. local:total is bound to data.orders.total; that object only provides the text and color, so the size, weight and position still come from the editor. remote:badge is declared in layers and takes its position, content and style from data.orders.badge.
Position and size
frame defines a layer’s position and size within the content canvas. All values are proportions: horizontal values use canvas width, and vertical values use canvas height. For example, width: 0.5 makes the layer half the canvas width; top: 0.1 places its top edge 10% of the canvas height below the canvas top.
- Width and height are required: whenever a
frameis given, it must give bothwidthandheight. - Position from one edge: use
left/rightandtop/bottomfor distances from the corresponding canvas edges. For example,right: 0.1leaves 10% of the canvas width between the layer’s right edge and the canvas right edge. - Both edges: with both
leftandright, the layer fills the width between those margins andwidthis not used. If both are0.1, the layer occupies 80% of the canvas width.topandbottomwork the same way. - No position: without either horizontal edge, the layer starts at the canvas left; without either vertical edge, it starts at the top.
widthandheightare clamped to0.02–1.5.
{
"left": 0.1,
"top": 0.1,
"width": 0.4,
"height": 0.2
}On a 200 × 100 pt content canvas, this places the layer 20 pt from the left and 10 pt from the top, with a width of 80 pt and a height of 20 pt. Adding "transform": {"translateX": 0.5} moves it right by half its own width, another 40 pt.
| Field | Type / range | Purpose |
|---|---|---|
width / height | 0.02–1.5 | Required. Width/height as fractions of the corresponding canvas axis. Opposing anchors override size on their axis. |
left / right | number | Distance from left/right edge as a fraction of canvas width. Neither anchor starts at x = 0; right alone pins to the right. |
top / bottom | number | Distance from top/bottom edge as a fraction of canvas height. Neither anchor starts at y = 0; bottom alone pins to the bottom. |
transform.translateX / translateY | number | Offset after placement, in units of the layer’s own width/height; positive values move right/down. |
width/height- Type / range0.02–1.5
- PurposeRequired. Width/height as fractions of the corresponding canvas axis. Opposing anchors override size on their axis.
left/right- Type / rangenumber
- PurposeDistance from left/right edge as a fraction of canvas width. Neither anchor starts at x = 0; right alone pins to the right.
top/bottom- Type / rangenumber
- PurposeDistance from top/bottom edge as a fraction of canvas height. Neither anchor starts at y = 0; bottom alone pins to the bottom.
transform.translateX/translateY- Type / rangenumber
- PurposeOffset after placement, in units of the layer’s own width/height; positive values move right/down.
Data paths
dataPath starts at data and walks fields and numeric indexes — data.orders.total, data.cards[0]. There are no expressions, wildcards or string keys. It must point to an object.
^data(?:\.[A-Za-z_][A-Za-z0-9_]*|\[[0-9]+\])*$When the object is invalid
Errors in that object only affect its layer; the response stays valid. When the path does not exist, does not point to an object, the object contains a field that it or the layer’s type does not support, a value has the wrong type or an unsupported option, a frame lacks width or height, a remote layer has no frame, or a remote matrix has no content.pixels:
- an editor layer falls back to its saved state, and the app explains why below the layer’s data path field;
- a remote layer is not drawn;
- the root grid and all other layers are shown as usual.
Numbers outside the ranges listed in the field tables are not errors; the renderer clamps them.
{
"protocol": 1,
"default": { "color": "#8EC8FB", "opacity": 0.06, "cornerRadius": 0.5 },
"layout": { "gapMode": "far" },
"pixels": [
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
],
"batch": [
{
"pixel": { "color": "#7EDDB5", "opacity": 1, "halo": 0.5 },
"coordinates": [
[7, 17]
]
}
],
"data": {
"orders": {
"title": {
"frame": {
"left": 0.07,
"top": 0.12,
"width": 0.6,
"height": 0.16
},
"zIndex": 10,
"content": { "text": "Orders today" },
"style": {
"color": "#A9C4E8",
"fontSizeRatio": 0.1,
"fontWeight": "500",
"horizontalAlignment": "left"
}
},
"total": {
"frame": {
"left": 0.07,
"top": 0.32,
"width": 0.6,
"height": 0.4
},
"zIndex": 11,
"content": { "text": 128 },
"style": {
"color": "#FFFFFF",
"fontSizeRatio": 0.26,
"fontWeight": "700",
"horizontalAlignment": "left"
}
},
"badge": {
"frame": {
"right": 0.07,
"top": 0.14,
"width": 0.24,
"height": 0.2
},
"zIndex": 5,
"style": {
"gradientEnabled": true,
"gradientColors": ["#2A63D4", "#56B9DD"],
"cornerRadiusRatio": 1,
"borderColor": "#D7F6FF",
"borderOpacity": 0.46,
"borderWidth": 1
}
},
"trend": {
"frame": {
"right": 0.07,
"top": 0.16,
"width": 0.24,
"height": 0.16
},
"zIndex": 6,
"content": {
"icon": "ph_trend_up",
"iconCodePoint": 58542,
"iconFont": "phosphorBold"
},
"style": { "color": "#FFFFFF" }
}
}
},
"layers": [
{
"id": "remote:orders-title",
"type": "text",
"dataPath": "data.orders.title"
},
{
"id": "remote:orders-total",
"type": "text",
"dataPath": "data.orders.total"
},
{
"id": "remote:badge",
"type": "shape",
"dataPath": "data.orders.badge"
},
{
"id": "remote:trend",
"type": "icon",
"dataPath": "data.orders.trend"
}
]
}Layer styles and content#
The object a dataPath points to describes content in content and appearance in style. Fields are grouped by type; the content. / style. prefix shows where a field goes in that object. Colors use #RRGGBB or alpha-first #AARRGGBB; pt means iOS logical pixels. Defaults in the tables apply when a remote layer omits a field. An editor layer keeps its editor setting instead, and fresh editor presets can differ from these defaults.
Style fields shared across layer types
These fields apply to text, image and shape; each type’s table explains its opacity behavior. Other layers use the style fields listed for their own type.
| Field | Type / values | Purpose and rules |
|---|---|---|
style.backgroundColor | Color | Color filling the layer background. Text backgrounds are hidden until style.opacity is raised; shapes fall back to style.color when this field is absent. |
style.cornerRadiusRatio | 0–1 | Round the frame corners and clip content to that outline. Radius = layer short side × value ÷ 2. 0 (the default) is square-cornered; 1 makes a square circular or a rectangle capsule-shaped. |
style.borderColor | Color | Color of the border drawn inside the layer outline, white by default. Visible only when both borderWidth and borderOpacity exceed zero. |
style.borderOpacity | 0–1 | Border visibility only: 0 hides it and 1 applies no further opacity reduction; default 0. Does not change the background or content opacity. |
style.borderWidth | number · pt | Inward border thickness in pt, default 0. Text content is inset by the same amount. Use 0–8 pt; text clamps values to this range. |
style.gradientEnabled | boolean | Replace the flat background with a linear gradient. Off when omitted by the API; fresh editor shapes normally save explicit true. |
style.gradientColors | Color[] | Two or three colors in gradient order: first at the start, last at the end, with intermediate positions set by gradientStops. Requires gradientEnabled. |
style.gradientStops | number[] · 0–1 | One position per gradient color: 0 is the start and 1 the end. [0, 0.2, 1] places the middle color at 20% of the gradient. Use increasing values; absent or mismatched stops are evenly spaced. |
style.gradientAngle | number · degrees | Rotate only the background gradient, not the layer or content, in degrees. 0 runs left to right and 90 top to bottom. API default 0; fresh editor shapes preset 135. |
style.maskColor | Color | A uniform color overlay above the layer content, black by default. maskOpacity controls its strength; use dark colors to dim content or another color for a wash. |
style.maskOpacity | 0–0.6 | Uniform overlay strength: 0 disables it; higher values make the mask more visible. Default 0. Does not directly reduce the underlying content opacity. |
style.vignetteIntensity | 0–2 | Darkening at the edges and corners. 0 disables it; higher values darken the perimeter while keeping the center relatively bright. Default 0. |
style.readabilityGradient | string | A black overlay fading inward from a chosen edge to improve overlaid text contrast. none is off (default); top, bottom, left, right choose an edge; both covers top and bottom; topLeftDiagonal and topRightDiagonal fade from that upper corner toward the opposite corner. |
style.backgroundColor- Type / valuesColor
- Purpose and rulesColor filling the layer background. Text backgrounds are hidden until
style.opacityis raised; shapes fall back tostyle.colorwhen this field is absent. style.cornerRadiusRatio- Type / values0–1
- Purpose and rulesRound the frame corners and clip content to that outline. Radius = layer short side × value ÷ 2.
0(the default) is square-cornered;1makes a square circular or a rectangle capsule-shaped. style.borderColor- Type / valuesColor
- Purpose and rulesColor of the border drawn inside the layer outline, white by default. Visible only when both
borderWidthandborderOpacityexceed zero. style.borderOpacity- Type / values0–1
- Purpose and rulesBorder visibility only:
0hides it and1applies no further opacity reduction; default0. Does not change the background or content opacity. style.borderWidth- Type / valuesnumber · pt
- Purpose and rulesInward border thickness in pt, default
0. Text content is inset by the same amount. Use 0–8 pt; text clamps values to this range. style.gradientEnabled- Type / valuesboolean
- Purpose and rulesReplace the flat background with a linear gradient. Off when omitted by the API; fresh editor shapes normally save explicit
true. style.gradientColors- Type / valuesColor[]
- Purpose and rulesTwo or three colors in gradient order: first at the start, last at the end, with intermediate positions set by
gradientStops. RequiresgradientEnabled. style.gradientStops- Type / valuesnumber[] · 0–1
- Purpose and rulesOne position per gradient color:
0is the start and1the end.[0, 0.2, 1]places the middle color at 20% of the gradient. Use increasing values; absent or mismatched stops are evenly spaced. style.gradientAngle- Type / valuesnumber · degrees
- Purpose and rulesRotate only the background gradient, not the layer or content, in degrees.
0runs left to right and90top to bottom. API default0; fresh editor shapes preset135. style.maskColor- Type / valuesColor
- Purpose and rulesA uniform color overlay above the layer content, black by default.
maskOpacitycontrols its strength; use dark colors to dim content or another color for a wash. style.maskOpacity- Type / values0–0.6
- Purpose and rulesUniform overlay strength:
0disables it; higher values make the mask more visible. Default0. Does not directly reduce the underlying content opacity. style.vignetteIntensity- Type / values0–2
- Purpose and rulesDarkening at the edges and corners.
0disables it; higher values darken the perimeter while keeping the center relatively bright. Default0. style.readabilityGradient- Type / valuesstring
- Purpose and rulesA black overlay fading inward from a chosen edge to improve overlaid text contrast.
noneis off (default);top,bottom,left,rightchoose an edge;bothcovers top and bottom;topLeftDiagonalandtopRightDiagonalfade from that upper corner toward the opposite corner.
Dashed boundaries and dimension lines explain fields; they are not drawn by the layer.
text · Text
- Content:
content.textis the text to show — a string, or a number shown as written. An editor layer shows its static editor text until the response provides one. - Style:
stylecontrols typography and layout. Backgrounds, rounding and borders use the shared fields above.
| Field | Type / values | Purpose and rules |
|---|---|---|
content.text | string · number | The text to show. Numbers are converted as written, so 128 displays “128”. Empty for a remote layer that omits it. |
style.color | Color | Text color, white by default; does not color the background. |
style.textOpacity | 0–1 | Glyph visibility only, default 1. 0 hides text and 1 preserves the color’s own alpha. Text background opacity uses style.opacity. |
style.opacity | 0–1 | Text background opacity, default 0; does not control the glyphs or the entire layer. |
style.fontSizeRatio | 0.02–0.28 | Font size = the whole content canvas short side × value, default 0.12. A 160 pt canvas short side and 0.1 give a 16 pt font, not 10% of the text frame height. |
style.fontWeight | string | Glyph stroke weight as strings "300", "400", "500", "600", "700", from lighter to heavier. Default "400". |
style.fontFamily | string | Choose glyph design: system is the default proportional system font; systemMonospace is system fixed-width, useful for counters and clocks; monospace uses Menlo. |
style.fontFamilyFallback | string[] | Installed font names to try for missing glyphs; does not download fonts. |
style.letterSpacing | number · pt | Extra distance between glyphs in pt, default 0 on iOS. Positive values spread letters out; negative values tighten them. Does not change font size. |
style.baselineRatio | number | Optional first-line baseline position: distance from text content top = font size × value. Useful for aligning separate text layers; omit for the font’s natural baseline. |
style.horizontalAlignment | left / center / right | Place text left, center or right inside the viewport after padding and border. Default right; does not move the layer frame. |
style.verticalAlignment | top / center / bottom | Place text at the top, center or bottom of the viewport, default top. Does not change frame.top or frame.bottom. |
style.autoWrap | boolean | Wrap at the available text width, default false. Explicit breaks remain; overflowing content is clipped. |
style.paddingRatio | 0–0.3 | Inset on each side = text frame short side × value, default 0, plus border width. Higher values leave less room for text. |
content.text- Type / valuesstring · number
- Purpose and rulesThe text to show. Numbers are converted as written, so
128displays “128”. Empty for a remote layer that omits it. style.color- Type / valuesColor
- Purpose and rulesText color, white by default; does not color the background.
style.textOpacity- Type / values0–1
- Purpose and rulesGlyph visibility only, default
1.0hides text and1preserves the color’s own alpha. Text background opacity usesstyle.opacity. style.opacity- Type / values0–1
- Purpose and rulesText background opacity, default
0; does not control the glyphs or the entire layer. style.fontSizeRatio- Type / values0.02–0.28
- Purpose and rulesFont size = the whole content canvas short side × value, default
0.12. A 160 pt canvas short side and0.1give a 16 pt font, not 10% of the text frame height. style.fontWeight- Type / valuesstring
- Purpose and rulesGlyph stroke weight as strings
"300","400","500","600","700", from lighter to heavier. Default"400". style.fontFamily- Type / valuesstring
- Purpose and rulesChoose glyph design:
systemis the default proportional system font;systemMonospaceis system fixed-width, useful for counters and clocks;monospaceuses Menlo. style.fontFamilyFallback- Type / valuesstring[]
- Purpose and rulesInstalled font names to try for missing glyphs; does not download fonts.
style.letterSpacing- Type / valuesnumber · pt
- Purpose and rulesExtra distance between glyphs in pt, default
0on iOS. Positive values spread letters out; negative values tighten them. Does not change font size. style.baselineRatio- Type / valuesnumber
- Purpose and rulesOptional first-line baseline position: distance from text content top = font size × value. Useful for aligning separate text layers; omit for the font’s natural baseline.
style.horizontalAlignment- Type / values
left/center/right - Purpose and rulesPlace text left, center or right inside the viewport after padding and border. Default
right; does not move the layer frame. style.verticalAlignment- Type / values
top/center/bottom - Purpose and rulesPlace text at the top, center or bottom of the viewport, default
top. Does not changeframe.toporframe.bottom. style.autoWrap- Type / valuesboolean
- Purpose and rulesWrap at the available text width, default
false. Explicit breaks remain; overflowing content is clipped. style.paddingRatio- Type / values0–0.3
- Purpose and rulesInset on each side = text frame short side × value, default
0, plus border width. Higher values leave less room for text.
When the text or its style changes, the runtime computes the smallest area the new text, font size, line breaks, padding and border need, limited to the content canvas, so the Home Screen does not clip text to an outdated area. This never rewrites frame.
Dashed boundaries and dimension lines explain fields; they are not drawn by the layer.
{
"protocol": 1,
"pixels": [
[
{ "empty": true }
]
],
"data": {
"parcels": {
"frame": { "left": 0.08, "top": 0.22, "width": 0.84, "height": 0.56 },
"content": { "text": 128 },
"style": {
"color": "#D9F2F7",
"textOpacity": 1,
"fontSizeRatio": 0.26,
"fontWeight": "600",
"fontFamily": "systemMonospace",
"horizontalAlignment": "center",
"verticalAlignment": "center",
"autoWrap": false,
"paddingRatio": 0,
"backgroundColor": "#397DA0",
"opacity": 0.3,
"cornerRadiusRatio": 0,
"borderColor": "#90BBCD",
"borderOpacity": 0.6,
"borderWidth": 1
}
}
},
"layers": [
{ "id": "remote:count", "type": "text", "dataPath": "data.parcels" }
]
}matrix · Matrix
- Content:
contentmirrors the root grid —pixels,default,batchandlayout, with exactly the same meaning and rules.pixelssets the actual grid dimensions. - Style: a matrix has no
stylefields. Pixel appearance goes incontent.default,batchandpixels; spacing and insets go incontent.layout.
| Field | Type / values | Purpose and rules |
|---|---|---|
content.pixels | (Pixel | null)[][] | Local grid content, with the same rules as the root pixels. Required for remote matrices; an editor matrix shows its default sample until pixels arrive. |
content.default | Pixel | Fallback pixel style. For editor matrices it merges field by field onto the editor’s default pixel style; for remote matrices the base is color #5EA2FF, opacity 0.82, cornerRadius 0.16. |
content.batch | { pixel, coordinates }[] | Batch styles, with the same rules as the root batch. Must be returned together with content.pixels. |
content.layout | MatrixLayout | Gaps, cell aspect ratio and edge inset, with the fields in “Grid layout”. Merges field by field onto editor settings; gapMode defaults to near. |
content.pixels- Type / values
(Pixel | null)[][] - Purpose and rulesLocal grid content, with the same rules as the root
pixels. Required for remote matrices; an editor matrix shows its default sample until pixels arrive. content.default- Type / values
Pixel - Purpose and rulesFallback pixel style. For editor matrices it merges field by field onto the editor’s default pixel style; for remote matrices the base is
color #5EA2FF,opacity 0.82,cornerRadius 0.16. content.batch- Type / values
{ pixel, coordinates }[] - Purpose and rulesBatch styles, with the same rules as the root
batch. Must be returned together withcontent.pixels. content.layout- Type / values
MatrixLayout - Purpose and rulesGaps, cell aspect ratio and edge inset, with the fields in “Grid layout”. Merges field by field onto editor settings;
gapModedefaults tonear.
Dashed boundaries and dimension lines explain fields; they are not drawn by the layer.
Priority, highest first: explicit values in pixels, then batch, then the response’s default / layout, then the editor’s default / layout. The rows and columns set in the editor only shape the default sample: without valid pixels, the renderer draws a stable sample in the merged default style, so the layer does not disappear before the API is ready. Valid pixels are never resized or rearranged.
{
"protocol": 1,
"pixels": [
[
{ "empty": true }
]
],
"data": {
"title": {
"frame": { "left": 0.06, "top": 0.08, "width": 0.88, "height": 0.14 },
"content": { "text": "Contributions · 20 weeks" },
"style": {
"color": "#A9C4E8",
"fontSizeRatio": 0.09,
"fontWeight": "500",
"horizontalAlignment": "left"
}
},
"heatmap": {
"frame": { "left": 0.06, "top": 0.28, "width": 0.88, "height": 0.64 },
"content": {
"default": { "color": "#FFFFFF", "opacity": 0.08, "cornerRadius": 0.25 },
"layout": { "gapMode": "near" },
"pixels": [
[
{ "color": "#7EDDB5", "opacity": 0.85 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.7448688173552516 },
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.7545944628656934 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.9830690317402184 },
null,
null
],
[
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.5229617408287728 },
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.9544462806264128 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.6555567507870917 },
null,
null,
null
],
[
{ "color": "#7EDDB5", "opacity": 0.7222294489865844 },
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.6638935382087501 },
{ "color": "#7EDDB5", "opacity": 0.6413972526463148 },
null,
null,
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.5311184461090914 },
{ "color": "#7EDDB5", "opacity": 0.8927628180779044 },
{ "color": "#7EDDB5", "opacity": 0.8698567704801822 },
{ "color": "#7EDDB5", "opacity": 0.5938312809225924 },
null,
null,
{ "color": "#7EDDB5", "opacity": 0.710199232267813 },
{ "color": "#7EDDB5", "opacity": 0.9951574673001937 },
{ "color": "#7EDDB5", "opacity": 0.974044818430527 }
],
[
null,
{ "color": "#7EDDB5", "opacity": 0.6708227479019796 },
{ "color": "#7EDDB5", "opacity": 0.9875732368676824 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.6668848538904473 },
null,
null,
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.5228328839421528 },
{ "color": "#7EDDB5", "opacity": 0.5214774850893487 },
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.7065983124568725 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.972886955341248 },
null
],
[
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.9993158218339733 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.7303804474628726 },
null,
null
],
[
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.8229182097281214 },
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.6114321719061702 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.8256594886799811 },
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.7008502184288221 }
],
[
{ "color": "#7EDDB5", "opacity": 0.5000626562032289 },
null,
null,
{ "color": "#7EDDB5", "opacity": 0.6949160968071824 },
{ "color": "#7EDDB5", "opacity": 0.8072480735714076 },
{ "color": "#7EDDB5", "opacity": 0.5647999972978959 },
null,
null,
null,
null,
null,
{ "color": "#7EDDB5", "opacity": 0.6708958418347376 },
{ "color": "#7EDDB5", "opacity": 0.8120773109877517 },
{ "color": "#7EDDB5", "opacity": 0.6297695437501125 },
null,
null,
{ "color": "#7EDDB5", "opacity": 0.5926441641837852 },
{ "color": "#7EDDB5", "opacity": 0.9642279221859965 },
{ "color": "#7EDDB5", "opacity": 1 },
{ "color": "#7EDDB5", "opacity": 0.8892938715888167 }
]
]
}
}
},
"layers": [
{ "id": "remote:title", "type": "text", "dataPath": "data.title" },
{ "id": "remote:heatmap", "type": "matrix", "dataPath": "data.heatmap" }
]
}curve · Curve
- Content:
contentdeclares samples or a function, plus markers that follow the curve. Curve content always comes from the API; an editor curve layer only saves its data path, initial position and fallback style. - Style:
stylecontrols stroke, fill and plot padding.
Curve content: properties of content in the object dataPath points to.
| Field | Type / values | Purpose and rules |
|---|---|---|
content.mode | samples / function | Select the curve source: samples connects entries in points; function evaluates expression over domain. Defaults to function with an expression, otherwise samples. |
content.points | [x, y][] | Up to 1,024 samples as numeric pairs. |
content.expression | string · ≤512 | Bounded math expression in x: + - * / ^, parentheses, sin cos exp log abs sqrt min max, pi, e and named parameters. Does not execute arbitrary scripts. |
content.parameters | object · ≤64 | Up to 64 named numeric parameters, values clamped to ±1e9. |
content.domain | [min, max] | X data range [min, max], mapping to the plot’s left/right edges. Inferred from samples when omitted, otherwise [0, 1]. |
content.yDomain | [min, max] | Y display range [min, max]: min maps to the plot bottom and max to the top. With [0, 100], 50 is halfway up. Inferred from samples when omitted, otherwise [0, 1]. |
content.interpolation | linear / stepAfter / monotone | Shape between samples: linear joins them directly; stepAfter holds the previous value before jumping; monotone smooths the connection while avoiding overshoot (default). |
content.markers | array · ≤32 | Up to 32 circular markers with optional labels; fields below. |
content.mode- Type / values
samples/function - Purpose and rulesSelect the curve source:
samplesconnects entries inpoints;functionevaluatesexpressionoverdomain. Defaults to function with an expression, otherwise samples. content.points- Type / values
[x, y][] - Purpose and rulesUp to 1,024 samples as numeric pairs.
content.expression- Type / valuesstring · ≤512
- Purpose and rulesBounded math expression in
x:+ - * / ^, parentheses,sin cos exp log abs sqrt min max,pi,eand named parameters. Does not execute arbitrary scripts. content.parameters- Type / valuesobject · ≤64
- Purpose and rulesUp to 64 named numeric parameters, values clamped to ±1e9.
content.domain- Type / values
[min, max] - Purpose and rulesX data range
[min, max], mapping to the plot’s left/right edges. Inferred from samples when omitted, otherwise[0, 1]. content.yDomain- Type / values
[min, max] - Purpose and rulesY display range
[min, max]: min maps to the plot bottom and max to the top. With[0, 100], 50 is halfway up. Inferred from samples when omitted, otherwise[0, 1]. content.interpolation- Type / values
linear/stepAfter/monotone - Purpose and rulesShape between samples:
linearjoins them directly;stepAfterholds the previous value before jumping;monotonesmooths the connection while avoiding overshoot (default). content.markers- Type / valuesarray · ≤32
- Purpose and rulesUp to 32 circular markers with optional labels; fields below.
Marker fields: properties of each content.markers[] object.
| Field | Type / values | Purpose and rules |
|---|---|---|
at.x | number | X data coordinate of the marker; its height is evaluated from the curve. Choose this or sampleIndex. |
at.sampleIndex | integer | Zero-based sample index for a marker in sample mode; use samples ordered by increasing x. |
at.offsetPt | [dx, dy] · −64–64 | Label offset [horizontal, vertical] from the curve point in pt, default [0, 0]; positive values move right/down. Moves only the label, not the dot or curve. |
symbol.shape | circle | Markers currently render as circles. |
symbol.radiusPt | 1–16 | Circle radius in pt, default 4. |
symbol.fill | Color | Circle fill color, default #E6EDC9. |
label | string | Optional marker label; not a nested text layer. |
labelStyle.color | Color | Label color, default #E6EEEE. |
labelStyle.fontSizePt | 6–32 | Label font size in pt, default 10. |
labelStyle.position | top / bottom / left / right | Label placement relative to the point, default top. |
at.x- Type / valuesnumber
- Purpose and rulesX data coordinate of the marker; its height is evaluated from the curve. Choose this or
sampleIndex. at.sampleIndex- Type / valuesinteger
- Purpose and rulesZero-based sample index for a marker in sample mode; use samples ordered by increasing x.
at.offsetPt- Type / values
[dx, dy]· −64–64 - Purpose and rulesLabel offset
[horizontal, vertical]from the curve point in pt, default[0, 0]; positive values move right/down. Moves only the label, not the dot or curve. symbol.shape- Type / values
circle - Purpose and rulesMarkers currently render as circles.
symbol.radiusPt- Type / values1–16
- Purpose and rulesCircle radius in pt, default
4. symbol.fill- Type / valuesColor
- Purpose and rulesCircle fill color, default
#E6EDC9. label- Type / valuesstring
- Purpose and rulesOptional marker label; not a nested text layer.
labelStyle.color- Type / valuesColor
- Purpose and rulesLabel color, default
#E6EEEE. labelStyle.fontSizePt- Type / values6–32
- Purpose and rulesLabel font size in pt, default
10. labelStyle.position- Type / values
top/bottom/left/right - Purpose and rulesLabel placement relative to the point, default
top.
Curve style: properties of style in the object dataPath points to.
| Field | Type / values | Purpose and rules |
|---|---|---|
style.color | Color | Curve stroke color; area fill uses fillColor separately. |
style.opacity | 0–1 | Overall curve opacity, default 1; also multiplies fill and marker opacity. |
style.strokeWidth | 0.25–12 · pt | Curve stroke width in pt, default 2. |
style.lineCap | round / butt / square | Stroke endpoints: round adds a semicircle (default), butt ends flat at the coordinate, square extends half a stroke width with a square cap. |
style.lineJoin | round / bevel / miter | Joins at bends: round rounds them (default), bevel cuts the corner, miter preserves a sharp corner. Most visible on linear or stepped paths. |
style.dashLength | 0.5–64 · pt | Dash segment length in pt; omit for a solid curve. |
style.dashGap | 0–64 · pt | Dash gap in pt, default 4; applies when dashes are enabled. |
style.fillColor | Color | Fill color between the curve and plot bottom; defaults to the stroke color. |
style.fillOpacity | 0–1 | Area-fill opacity, default 0 (off), further multiplied by style.opacity. Does not change stroke width or stroke opacity. |
style.plotPadding | 0–48 · pt | Inset on all four sides between the layer frame and plot in pt, default 0. Higher values shrink the plot; domain / yDomain still map into that inner area. |
style.color- Type / valuesColor
- Purpose and rulesCurve stroke color; area fill uses
fillColorseparately. style.opacity- Type / values0–1
- Purpose and rulesOverall curve opacity, default
1; also multiplies fill and marker opacity. style.strokeWidth- Type / values0.25–12 · pt
- Purpose and rulesCurve stroke width in pt, default
2. style.lineCap- Type / values
round/butt/square - Purpose and rulesStroke endpoints:
roundadds a semicircle (default),buttends flat at the coordinate,squareextends half a stroke width with a square cap. style.lineJoin- Type / values
round/bevel/miter - Purpose and rulesJoins at bends:
roundrounds them (default),bevelcuts the corner,miterpreserves a sharp corner. Most visible on linear or stepped paths. style.dashLength- Type / values0.5–64 · pt
- Purpose and rulesDash segment length in pt; omit for a solid curve.
style.dashGap- Type / values0–64 · pt
- Purpose and rulesDash gap in pt, default
4; applies when dashes are enabled. style.fillColor- Type / valuesColor
- Purpose and rulesFill color between the curve and plot bottom; defaults to the stroke color.
style.fillOpacity- Type / values0–1
- Purpose and rulesArea-fill opacity, default
0(off), further multiplied bystyle.opacity. Does not change stroke width or stroke opacity. style.plotPadding- Type / values0–48 · pt
- Purpose and rulesInset on all four sides between the layer frame and plot in pt, default
0. Higher values shrink the plot;domain/yDomainstill map into that inner area.
Dashed boundaries and dimension lines explain fields; they are not drawn by the layer.
Markers draw circles and text, not axes. Use [x, y] samples, label and labelStyle.position for app/widget compatibility. Compose separate layers for image or icon annotations.
{
"protocol": 1,
"pixels": [
[
{ "empty": true }
]
],
"data": {
"activity": {
"frame": { "left": 0.05, "top": 0.08, "width": 0.9, "height": 0.84 },
"content": {
"mode": "samples",
"points": [
[0, 30],
[2, 18],
[4, 65],
[6, 45],
[8, 90],
[10, 70]
],
"domain": [0, 10],
"yDomain": [0, 100],
"interpolation": "linear",
"markers": [
{
"at": { "x": 8 },
"symbol": { "shape": "circle", "radiusPt": 4, "fill": "#E1EEE0" },
"label": "Now",
"labelStyle": {
"color": "#E1EEE0",
"fontSizePt": 9,
"position": "top"
}
}
]
},
"style": {
"color": "#A7DAD7",
"opacity": 1,
"strokeWidth": 2,
"lineCap": "round",
"lineJoin": "round",
"fillColor": "#77B6BB",
"fillOpacity": 0.22,
"plotPadding": 8
}
}
},
"layers": [
{
"id": "remote:activity",
"type": "curve",
"dataPath": "data.activity"
}
]
}image · Image
- Content:
content.urlsupplies the image — an HTTPS URL, adata:image/…;base64,URI or a raw Base64 string. - Style: URL images use cover to fill the frame and crop overflow. Rounding, borders and overlays use shared fields; editor-uploaded local images use contain.
| Field | Type / values | Purpose and rules |
|---|---|---|
content.url | string | Image URL or Base64 data. An editor image layer whose source is set to API data reads this field from the object dataPath points to; when it provides url, the layer shows that image. Servers cannot reference device file paths. |
style.opacity | 0.1–1 | Image visibility, default 1. 0.5 draws it at half opacity so content beneath shows through; minimum 0.1. Does not change overlay or border opacity. |
style.color | Color | Loading placeholder color; does not tint the image. |
content.url- Type / valuesstring
- Purpose and rulesImage URL or Base64 data. An editor image layer whose source is set to API data reads this field from the object
dataPathpoints to; when it providesurl, the layer shows that image. Servers cannot reference device file paths. style.opacity- Type / values0.1–1
- Purpose and rulesImage visibility, default
1.0.5draws it at half opacity so content beneath shows through; minimum0.1. Does not change overlay or border opacity. style.color- Type / valuesColor
- Purpose and rulesLoading placeholder color; does not tint the image.
Dashed boundaries and dimension lines explain fields; they are not drawn by the layer.
Native remote images require image/*, are limited to 6 MB and downsampled to a 1,200 px longest edge. Base64 decoding is limited to 4 MB, but inline data still counts toward the 512 KB JSON limit.
icon · Icon
- Content: a built-in icon is identified by matching
content.icon,content.iconCodePointandcontent.iconFontvalues; an image icon usescontent.url. One object provides one or the other. - Style: icons fit completely within
frame, preserving aspect ratio.textOpacitycontrols visibility; fields below control glyph color and glow.
| Field | Type / values | Purpose and rules |
|---|---|---|
content.icon | string | Stable catalog name, such as ph_activity, used by App previews to look up the icon. Supply it together with the two fields below for the same icon; the name and code point are not alternatives. When an editor layer receives icon, its saved code point and font are no longer used. |
content.iconCodePoint | integer | Unicode code point within the font specified by iconFont, used to draw the Home Screen glyph. For example, ph_activity uses 57344; this is not a list index. Must come with icon. Without it, only a subset of legacy Material names has native fallbacks, not the full current catalog. |
content.iconFont | phosphorBold / devicon / simpleIcons / material | Bundled font identifier used by the Home Screen. Must come with icon and iconCodePoint; for example, ph_activity uses phosphorBold. The same code point can draw different symbols in different fonts. Does not load remote fonts. |
content.url | string | Image icon URL or Base64 data, shown with contain. Cannot be returned together with content.icon. An editor icon layer whose source is set to API data accepts either a built-in icon or a url from that object. |
style.color | Color | Color of a built-in glyph, white by default; does not tint image icons. |
style.textOpacity | 0–1 | Overall icon opacity, default 1, for both glyphs and images. 0 hides the icon. |
style.iconGlowEnabled | boolean | Enable glow, default false; supported for built-in glyphs and editor-uploaded local SVGs, not remote image icons. |
style.iconGlowColor | Color | Color of the blurred glow surrounding the icon, not the glyph itself. Defaults to the icon color; requires iconGlowEnabled. |
style.iconGlowOpacity | 0–1 | Glow opacity, default 0.65; higher values make it more visible. The icon’s textOpacity also affects final glow visibility. |
style.iconGlowRadius | 0.01–0.16 | Glow blur radius relative to the icon’s short side, default 0.07. Higher values spread the light over a larger area without resizing the glyph. |
content.icon- Type / valuesstring
- Purpose and rulesStable catalog name, such as
ph_activity, used by App previews to look up the icon. Supply it together with the two fields below for the same icon; the name and code point are not alternatives. When an editor layer receivesicon, its saved code point and font are no longer used. content.iconCodePoint- Type / valuesinteger
- Purpose and rulesUnicode code point within the font specified by
iconFont, used to draw the Home Screen glyph. For example,ph_activityuses57344; this is not a list index. Must come withicon. Without it, only a subset of legacy Material names has native fallbacks, not the full current catalog. content.iconFont- Type / values
phosphorBold/devicon/simpleIcons/material - Purpose and rulesBundled font identifier used by the Home Screen. Must come with
iconandiconCodePoint; for example,ph_activityusesphosphorBold. The same code point can draw different symbols in different fonts. Does not load remote fonts. content.url- Type / valuesstring
- Purpose and rulesImage icon URL or Base64 data, shown with contain. Cannot be returned together with
content.icon. An editor icon layer whose source is set to API data accepts either a built-in icon or aurlfrom that object. style.color- Type / valuesColor
- Purpose and rulesColor of a built-in glyph, white by default; does not tint image icons.
style.textOpacity- Type / values0–1
- Purpose and rulesOverall icon opacity, default
1, for both glyphs and images.0hides the icon. style.iconGlowEnabled- Type / valuesboolean
- Purpose and rulesEnable glow, default
false; supported for built-in glyphs and editor-uploaded local SVGs, not remote image icons. style.iconGlowColor- Type / valuesColor
- Purpose and rulesColor of the blurred glow surrounding the icon, not the glyph itself. Defaults to the icon color; requires
iconGlowEnabled. style.iconGlowOpacity- Type / values0–1
- Purpose and rulesGlow opacity, default
0.65; higher values make it more visible. The icon’stextOpacityalso affects final glow visibility. style.iconGlowRadius- Type / values0.01–0.16
- Purpose and rulesGlow blur radius relative to the icon’s short side, default
0.07. Higher values spread the light over a larger area without resizing the glyph.
Use the separate tool page to browse and search in the App’s category order, get the matching icon, iconCodePoint and iconFont values, adjust styles live, and copy the layer JSON. Open the icon browser & preview
Dashed boundaries and dimension lines explain fields; they are not drawn by the layer.
Native remote image icons are limited to 1 MB and downsampled to a 512 px longest edge. The editor manages locally uploaded icons.
{
"protocol": 1,
"pixels": [
[
{ "empty": true }
]
],
"data": {
"icons": [
{
"frame": {
"left": 0.1,
"top": 0.25,
"width": 0.2,
"height": 0.5
},
"content": {
"icon": "ph_activity",
"iconCodePoint": 57344,
"iconFont": "phosphorBold"
},
"style": { "color": "#A7DAD7", "textOpacity": 1 }
},
{
"frame": {
"left": 0.4,
"top": 0.25,
"width": 0.2,
"height": 0.5
},
"content": {
"icon": "ph_lightning",
"iconCodePoint": 58078,
"iconFont": "phosphorBold"
},
"style": { "color": "#F0C778", "textOpacity": 1 }
},
{
"frame": {
"left": 0.7,
"top": 0.25,
"width": 0.2,
"height": 0.5
},
"content": {
"icon": "ph_trend_up",
"iconCodePoint": 58542,
"iconFont": "phosphorBold"
},
"style": { "color": "#A7DAD7", "textOpacity": 0.45 }
}
]
},
"layers": [
{
"id": "remote:activity",
"type": "icon",
"dataPath": "data.icons[0]"
},
{
"id": "remote:lightning",
"type": "icon",
"dataPath": "data.icons[1]"
},
{
"id": "remote:trend",
"type": "icon",
"dataPath": "data.icons[2]"
}
]
}line · Line
- Content:
content.directionchooses a horizontal or vertical line through the frame center. Userotationfor other angles. - Style:
framesets position and length,strokeWidthsets thickness, andpatternchooses solid, dashed or dotted strokes.
| Field | Type / values | Purpose and rules |
|---|---|---|
content.direction | horizontal / vertical | Direction, horizontal by default. Horizontal lines cross the frame’s vertical center; vertical lines cross its horizontal center. |
style.color | Color | Stroke color, white by default; replaced by gradientColors when a gradient is enabled. |
style.opacity | 0–1 | Opacity of the entire line, default 1, including gradient colors and dotted circles. |
style.strokeWidth | 0.5–8 · pt | Stroke width in pt, default 2. The frame controls length, not thickness. |
style.pattern | solid / dashed / dotted | Stroke pattern: solid is continuous (default); dashed alternates painted segments and gaps; dotted repeats circles with diameter set by strokeWidth. |
style.cap | round / butt | Endpoints of solid strokes and dash segments: round is semicircular (default), butt ends flat. Dotted strokes always use circles and ignore this field. |
style.dashLength | 1–32 · pt | Length of each dash in pt, default 6; used only for dashed. |
style.dashGap | 0–32 · pt | Empty distance between dash segments or dots in pt, default 4. Higher values spread them farther apart; unused for solid. |
style.gradientEnabled | boolean | Draw the stroke with a gradient, default false. |
style.gradientColors | Color[] | Two or three gradient colors; requires gradientEnabled. Native widgets run left to right across the frame; app previews run top to bottom for vertical lines. |
style.gradientStops | number[] · 0–1 | One increasing position per color: 0 at the start and 1 at the end. Omit for even spacing. |
content.direction- Type / values
horizontal/vertical - Purpose and rulesDirection, horizontal by default. Horizontal lines cross the frame’s vertical center; vertical lines cross its horizontal center.
style.color- Type / valuesColor
- Purpose and rulesStroke color, white by default; replaced by
gradientColorswhen a gradient is enabled. style.opacity- Type / values0–1
- Purpose and rulesOpacity of the entire line, default
1, including gradient colors and dotted circles. style.strokeWidth- Type / values0.5–8 · pt
- Purpose and rulesStroke width in pt, default
2. The frame controls length, not thickness. style.pattern- Type / values
solid/dashed/dotted - Purpose and rulesStroke pattern:
solidis continuous (default);dashedalternates painted segments and gaps;dottedrepeats circles with diameter set bystrokeWidth. style.cap- Type / values
round/butt - Purpose and rulesEndpoints of solid strokes and dash segments:
roundis semicircular (default),buttends flat. Dotted strokes always use circles and ignore this field. style.dashLength- Type / values1–32 · pt
- Purpose and rulesLength of each dash in pt, default
6; used only fordashed. style.dashGap- Type / values0–32 · pt
- Purpose and rulesEmpty distance between dash segments or dots in pt, default
4. Higher values spread them farther apart; unused forsolid. style.gradientEnabled- Type / valuesboolean
- Purpose and rulesDraw the stroke with a gradient, default
false. style.gradientColors- Type / valuesColor[]
- Purpose and rulesTwo or three gradient colors; requires
gradientEnabled. Native widgets run left to right across the frame; app previews run top to bottom for vertical lines. style.gradientStops- Type / valuesnumber[] · 0–1
- Purpose and rulesOne increasing position per color:
0at the start and1at the end. Omit for even spacing.
Dashed boundaries and dimension lines explain fields; they are not drawn by the layer.
{
"protocol": 1,
"pixels": [
[
{ "empty": true }
]
],
"data": {
"solid": {
"frame": { "left": 0.1, "top": 0.32, "width": 0.8, "height": 0.04 },
"content": { "direction": "horizontal" },
"style": {
"color": "#A4DAE3",
"opacity": 0.55,
"strokeWidth": 1,
"pattern": "solid",
"cap": "butt"
}
},
"dashed": {
"frame": { "left": 0.1, "top": 0.62, "width": 0.8, "height": 0.04 },
"content": { "direction": "horizontal" },
"style": {
"color": "#A4DAE3",
"opacity": 1,
"strokeWidth": 3,
"pattern": "dashed",
"cap": "butt",
"dashLength": 8,
"dashGap": 6
}
}
},
"layers": [
{ "id": "remote:solid", "type": "line", "dataPath": "data.solid" },
{ "id": "remote:dashed", "type": "line", "dataPath": "data.dashed" }
]
}shape · Shape
- Content: the layer itself is a rounded rectangle and needs no
content. Use it for panels, badges and progress tracks. - Style:
styledefines fill and border; shared fields control rounding, gradients and overlays. Explicitly enable gradients and set visible border values.
| Field | Type / values | Purpose and rules |
|---|---|---|
content | object | No content value is needed; the layer itself is a filled, optionally bordered rounded rectangle. |
style.color | Color | Flat fill color, default #2A63D4. backgroundColor takes precedence; with a gradient enabled, gradientColors determines the fill. |
style.opacity | 0–1 | Fill opacity only, default 1. 0 hides the fill while allowing the border to remain; borderOpacity controls that border separately. |
content- Type / valuesobject
- Purpose and rulesNo content value is needed; the layer itself is a filled, optionally bordered rounded rectangle.
style.color- Type / valuesColor
- Purpose and rulesFlat fill color, default
#2A63D4.backgroundColortakes precedence; with a gradient enabled,gradientColorsdetermines the fill. style.opacity- Type / values0–1
- Purpose and rulesFill opacity only, default
1.0hides the fill while allowing the border to remain;borderOpacitycontrols that border separately.
Dashed boundaries and dimension lines explain fields; they are not drawn by the layer.
{
"protocol": 1,
"pixels": [
[
{ "empty": true }
]
],
"data": {
"badge": {
"frame": { "left": 0.12, "top": 0.22, "width": 0.76, "height": 0.56 },
"style": {
"opacity": 1,
"cornerRadiusRatio": 1,
"gradientEnabled": true,
"gradientColors": ["#225B80", "#78B6BA"],
"gradientStops": [0, 1],
"gradientAngle": 0,
"borderColor": "#BCE3E4",
"borderOpacity": 0.9,
"borderWidth": 2
}
}
},
"layers": [
{ "id": "remote:badge", "type": "shape", "dataPath": "data.badge" }
]
}Server-rendered images#
Besides describing content with pixels and layers, your server can take over drawing entirely: render the whole widget into one image with any chart library, template engine or headless browser, and return it through data. The widget shows it as an Image material that fills the canvas — no layers to configure, and none of the drawing syntax on this page to learn.
It fits well when you already have a report or image pipeline, need complex charts, custom fonts or free-form layout, or want the server to own every visual detail so you can change it without touching the widget in the app.
Set up in the app
- In the editor, set Material to Image;
- set Image Source to API;
- enter the field that holds the image as the Data Path, for example
data.cover; - optionally turn on a readability gradient if you overlay text layers.
Response
{
"protocol": 1,
"pixels": [[{ "empty": true }]],
"data": {
"cover": "https://cdn.example.com/widgets/orders-medium@3x.png"
}
}| Value at the data path | Rules |
|---|---|
| HTTPS URL | Must return image/*, up to 6 MB; the Home Screen downsamples it to 1,200 px on the longest edge. |
data:image/…;base64, or raw Base64 | Embedded in the response, best for small images; the whole JSON response is still limited to 512 KB. |
- HTTPS URL
- Must return
image/*, up to 6 MB; the Home Screen downsamples it to 1,200 px on the longest edge. data:image/…;base64,or raw Base64- Embedded in the response, best for small images; the whole JSON response is still limited to 512 KB.
This data path belongs to the material setting and points directly to the image string. It is not a layer and does not need an object.
The image fills the whole widget with cover scaling and is cropped at the edges. Render it at the aspect ratio of gw_canvas_width_pt × gw_canvas_height_pt from the request, at 2–3× pixel density so it stays sharp on the Home Screen.
pixels is still required; return [[{"empty": true}]] to show only the image. You can still draw pixels or layers on top, for example native text for a value that changes often.Request signing#
You can add a secret to a widget in the app so that every request carries two headers your server can verify, providing proof that the request comes from a Glance Widgets widget rather than an unrelated or malicious third party. The secret itself is never sent.
| Header | Value |
|---|---|
X-GlanceWidgets-Timestamp | Unix time in milliseconds, as a decimal string. |
X-GlanceWidgets-Signature | Base64(HMAC-SHA256(key = secret, message = timestamp + "\n" + secret)), UTF-8 throughout. |
X-GlanceWidgets-Timestamp- Unix time in milliseconds, as a decimal string.
X-GlanceWidgets-SignatureBase64(HMAC-SHA256(key = secret, message = timestamp + "\n" + secret)), UTF-8 throughout.
GlanceWidgets. The signature covers the timestamp and secret only — not the URL, query or body.On the server: reject stale timestamps (for example ±5 minutes), recompute the signature, and compare in constant time.
import crypto from 'node:crypto'
const SECRET = process.env.GLANCE_SECRET // same value as in the app
const WINDOW_MS = 5 * 60 * 1000
export function verify(req) {
const ts = req.get('X-GlanceWidgets-Timestamp')
const sig = req.get('X-GlanceWidgets-Signature')
if (!ts || !sig || Math.abs(Date.now() - Number(ts)) > WINDOW_MS) return false
const expected = crypto
.createHmac('sha256', SECRET)
.update(`${ts}\n${SECRET}`)
.digest('base64')
const a = Buffer.from(sig)
const b = Buffer.from(expected)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}import base64, hashlib, hmac, os, time
SECRET = os.environ["GLANCE_SECRET"]
WINDOW_MS = 5 * 60 * 1000
def verify(headers) -> bool:
ts = headers.get("X-GlanceWidgets-Timestamp")
sig = headers.get("X-GlanceWidgets-Signature")
if not ts or not sig or abs(time.time() * 1000 - int(ts)) > WINDOW_MS:
return False
message = f"{ts}\n{SECRET}".encode()
expected = base64.b64encode(
hmac.new(SECRET.encode(), message, hashlib.sha256).digest()
).decode()
return hmac.compare_digest(sig, expected)Test vector: secret this is secret, timestamp 1700000000000 → sSFWELbV2YwjdDQhWZwTcWlX5BWUx5J6TPpsZmuPii0=.
Limits & errors#
| Limit | Value |
|---|---|
| Body size | 512 KB (524,288 bytes) |
| Root grid | rows × columns ≤ 4,096; ≥ 1 × 1; rectangular |
| Layer declarations | Each has only id, type, dataPath, with ids prefixed remote: |
| Protocol | 1 or omitted |
| Curve | ≤ 1,024 points · ≤ 32 markers · expression ≤ 512 chars |
| Images | ≤ 6 MB remote · ≤ 4 MB Base64 · cover fit |
- Body size
- 512 KB (524,288 bytes)
- Root grid
- rows × columns ≤ 4,096; ≥ 1 × 1; rectangular
- Layer declarations
- Each has only
id,type,dataPath, with ids prefixedremote: - Protocol
1or omitted- Curve
- ≤ 1,024 points · ≤ 32 markers · expression ≤ 512 chars
- Images
- ≤ 6 MB remote · ≤ 4 MB Base64 · cover fit
Oversized bodies, invalid root grids, out-of-range batch coordinates, or malformed layer declarations cause the app to reject the whole response. Errors in the object a layer points to only affect that layer: an editor layer falls back to its saved state and a remote layer is not drawn. Curve samples, markers and parameters are truncated to their budgets; invalid or overlong expressions are not executed.
Refresh#
Home Screen widgets request a new frame on the WidgetKit timeline, about every 15 minutes. iOS decides the exact moment based on battery, usage and the system’s refresh budget. Design frames that stay meaningful between updates — include a "last updated" time in data if it matters.
Playground#
Edit the JSON and watch it render. The validator applies the same limits and precedence as the app.
Valid frame 0 cells · 0 layers · 0 bytes
Checklist#
- HTTPS endpoint, responds
2xxwithin a few seconds. - Root is an object with
"protocol": 1and a rectangularpixelsarray ≤ 4,096 cells. - Colors are
#RRGGBB(or ARGB#AARRGGBB). - Each
layersentry has onlyid,typeanddataPath, with ids startingremote:. - Every
dataPathpoints to an object indata; for remote layers it includes aframewithwidthandheight. - Grid sized from
gw_aspect_ratio, not a fixed table. - If signing is enabled, verify the timestamp window and signature in constant time.