Skip to main content
3 min read

Limits & performance

A GDN app runs inside a sandbox with hard guardrails, so one app can never hang the panel or overload a data source. Most apps never come close to any of these — but when a render stops unexpectedly, it's usually one of them. Here they all are in one place.

Canvas​

LimitValue
HeightAlways 32 px.
Width1–384 px. One panel module is 64 px wide; they daisy-chain (64, 128, 192, …).
Recommended widthKeep images to 192 px or smaller for best performance, and split content across pages instead of going very wide.

Anything drawn outside the canvas is clipped — it's not an error, it just doesn't show.

Drawing​

Each page may issue at most 4096 draw operations. Every c.* call is one op (a helper like c.table expands into several). Go over and the render stops with:

error: op limit (4096) exceeded on a single page

If you hit it, you're almost certainly drawing per-pixel where a shape or helper would do — or you should split the screen across pages.

Time​

Each render is killed if it runs longer than 30 seconds of wall-clock time:

error: render timed out (>30s) — possible infinite loop

That budget has to cover any http.get calls too (each with its own timeout, below), so in practice your own code should finish in well under a second. This limit exists to stop runaway loops — the usual trigger is a loop that never terminates.

On Glance's servers a render also runs under a CPU cap (~25 CPU-seconds) and a memory cap (~512 MB). Local previews on your own machine don't apply the CPU/memory caps, so an app that's fine locally can still hit them once published — keep renders light.

HTTP​

http.get is the only network access, and it's bounded:

LimitValueWhat happens at the edge
Requests per render8 uncachedA 9th raises http limit: at most 8 …. Cache hits don't count.
Per-request timeout~4 secondsA slower endpoint returns status_code 0 (with resp["error"] set) instead of hanging.
Response size1 MBLarger bodies are truncated. Panel apps need kilobytes — pick a smaller endpoint.
MethodGET onlyPanel apps read data; they don't post it.
Default cache300 s (ttl_seconds)2xx responses are cached by (url, params, headers); repeat renders reuse them.

Because a slow API surfaces as status_code 0, always draw a fallback when resp["status_code"] != 200. Pick APIs that answer quickly — see where to find them.

Refresh​

refresh: in the manifest sets how often the panel re-renders, in seconds:

ValueUse
60The minimum — validation fails below it. Clocks only.
300A good default (5 minutes).
90015 minutes.
3600Hourly data (weather, once-a-day counts).

A fast refresh doesn't make a still frame animate — it just re-fetches and redraws more often, which is harder on your data source and rarely needed. See How often your app redraws.

Refresh and inputs​

How often an app refreshes decides which inputs and how many pages it may have:

refreshInputsSetting combinationsPagesLive data
Under 60 sNot allowed
60 to 119 sPick-lists onlyAt most 48At most 3None
120 to 299 sPick-lists onlyAt most 100At most 3Yes
300 to 899 sAnything except colorNo limitNo limitYes
900 s or moreAnythingNo limitNo limitYes

A pick-list is a dropdown, checkbox, or selection input. free-text, api-key, date, date-past, number, and color inputs are not. "Live data" means http.get; under 2 minutes, only reference data cached with ttl_seconds of 3600 or more is allowed.

Validation checks each rule:

RuleValidation message
refresh is at least 60refresh 30s is below the 60s minimum
Under 5 minutes, every input is a pick-listrefresh 180s is under 5 minutes, so every setting must be a pick-list: station is free-text
Under 15 minutes, colors come from a dropdown, not a color pickersetting accent is a color picker on an app that refreshes every 60s; under 15 minutes use a dropdown of named colors
Under 5 minutes, settings stay within the combination limitsettings allow 462 combinations; an app that refreshes every 60s may allow at most 48
Under 2 minutes, no live dataapp fetches live data every 60s; apps that fetch data refresh every 2 minutes or slower
Under 5 minutes, at most 3 pages4 pages on an app that refreshes every 120s; under 5 minutes the limit is 3
http.get caches for at least 60 seconds (a warning)http.get ttl_seconds=20 is under 60s; cache responses for at least 60 seconds

Counting combinations​

Multiply the options in every input:

InputCounts as
dropdownIts number of choices
checkbox2
selection2 multiplied by itself once per choice

A time-zone dropdown (4) and a color dropdown (8) is 4 x 8 = 32.

Colors on a fast app​

Under 15 minutes, offer the Glance accent palette as a dropdown. Every name is a color the drawing API accepts, so the value goes straight into color=:

- key: accent
app_input_type: dropdown
type: choice
label: Accent color
default: amber
choices: [white, red, orange, amber, green, skyblue, purple, pink]

If a published app used a color picker, keep the same key and map any value that isn't on the list to your default, since panels keep the values people already saved:

PALETTE = ["white", "red", "orange", "amber", "green", "skyblue", "purple", "pink"]

def accent_color(ctx):
pick = str(ctx.inputs.get("accent", "amber")).lower()
return pick if pick in PALETTE else "amber" # an older saved value may be a hex color

Inputs​

An app may make 8 uncached requests per render regardless of how many inputs it has, and there's no fixed cap on the number of inputs — but every declared input must be read by your code, or validation fails it.

See also​