Skip to main content
3 min read

Starlark vs Python

app.star is written in Starlark, a small, sandboxed language that looks like Python and reads like Python — but isn't Python. It's what keeps every app fast and safe to run. Most Python you'd write just works; this page is the short list of things that don't, because they're what trips up hand-written and AI-written code alike.

The things that aren't there​

Each of these is a real error, with what to use instead.

You might writeWhat happensUse instead
import ...cannot use reserved keyword importNothing to import — c, ctx, http, math, fmt, color are handed to you.
import datetime / datetime.now()Variable datetime not foundctx.now (nine time fields, UTC).
import randomVariable random not foundDerive variety from ctx.now or your data; there's no RNG.
while x:cannot use reserved keyword whilefor i in range(n): — Starlark has for, not while.
try: … except:cannot use reserved keyword tryNo exceptions exist. Guard with if — check status_code, check for None.
f"value {x}"f-strings are off in this dialect"value " + str(x), "value %s" % x, or "value {}".format(x).
set([1, 2])Variable set not foundUse a list, or a dict for uniqueness.
class Foo:classes aren't supportedstruct(name="Foo", n=1) for a simple record.
"5".zfill(3)string has no attribute zfillfmt.pad(5, 3).

The rule of thumb: there is no standard library and no file, network, or system access. The only way out to the network is http.get.

What is available​

Plenty. All of this works exactly like Python:

  • Control flow: for loops, if / elif / else, break, continue, and recursion (functions can call themselves).
  • Comprehensions: list [x*2 for x in range(3)] and dict {k: v for ...}.
  • Functions & lambdas: def, default args, lambda a: a + 1.
  • Strings: .upper(), .split(), .replace(), .startswith(), .strip(), .find(), slicing, %-formatting, and .format().
  • Numbers & sequences: int, float, str, bool, abs, len, range, enumerate, reversed, sorted, min, max, zip, tuple unpacking, // for integer division.
  • print(...) for debugging — output shows in the Studio message console.
  • json — json.encode(...) / json.decode(...) if you ever need it (usually resp["json"] already did it for you).

The globals your app is handed​

You never import these; every page function can use them directly:

NameWhat it is
cThe canvas — all the c.* draw calls.
ctxInputs, the clock, and panel size.
colorNamed colors and color.dim.
mathpi, sin, cos, tan, sqrt, pow, floor, ceil, atan2, … (the module you'd import math for).
fmtfmt.pad(n, width) and fmt.commas(n) — the closest thing to strftime/number formatting.
httphttp.get for live data.

One more difference: apps are stateless​

An app can't remember anything between renders. Each refresh runs your code fresh, with just its inputs and ctx.now; there are no module-level variables that persist, no globals you can mutate across refreshes, and no on-device storage. If you need state, keep it in the data source you fetch from.

See also​