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.
  • jsonjson.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