Skip to main content

Schema reference

Schemas define the settings shown when someone configures an app. Export get_schema() and return a schema.Schema. SolidPixels saves each field under its id, then passes those values to main(config) when the app renders.

Quick start

This complete app lets someone change the greeting and select a smaller font:

app.star
load("render.star", "render")
load("schema.star", "schema")

DEFAULT_WHO = "World"

def main(config):
message = "Hello, %s!" % config.str("who", DEFAULT_WHO)

if config.bool("small", False):
text = render.Text(message, font = "CG-pixel-3x5-mono")
else:
text = render.Text(message)

return render.Root(child = text)

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Text(
id = "who",
name = "Who?",
desc = "Who to greet.",
icon = "arrowsMaximize",
default = DEFAULT_WHO,
),
schema.Toggle(
id = "small",
name = "Small text",
desc = "Display the greeting with a smaller font.",
icon = "text",
default = False,
),
],
)

Pass configuration values to a local render as key=value arguments:

tetra render app.star "who=SolidPixels" "small=True" -o app.webp

Use a stable, unique id for every field. Increment version when a schema change requires SolidPixels to refresh previously stored configuration.

Icons

The icon property selects the icon displayed beside a field. SolidPixels currently publishes these icons in its attribute icon catalog:

arrowsMaximize
baseball
basketball
block-question
book
bracketsCurly
brush
bug
cakeCandles
calendar
clock
codeBranch
currencySign
dog
eye
flag
football-helmet
gauge
globe
hashtag
hexagon
hockey-mask
image
instagram
key
language
location-crosshairs
location-dot
lock
money-bill-trend-up
newspaper
temperature
text
tiktok
tv-retro
youtube

An unrecognized name uses the default icon.

Dynamic fields

Most fields contain a fixed value. Dynamic fields also receive a handler function that SolidPixels calls while the installation form is being filled out:

  • Generated returns more schema fields based on another field.
  • LocationBased returns options for a selected location.
  • OAuth2 exchanges an authorization response for an access token.
  • Typeahead returns options matching entered text.

Handlers run during configuration, not during the normal main(config) render.

Color

Color displays a color picker. Its configuration value is a hex color string with a leading #, ready to pass to a render widget. Add palette to suggest useful choices without preventing a custom color.

load("schema.star", "schema")

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Color(
id = "color",
name = "Color",
desc = "Color of the screen.",
icon = "brush",
default = "#7AB0FF",
palette = [
"#7AB0FF",
"#BFEDC4",
"#78DECC",
"#DBB5FF",
],
),
],
)

Read it with config.str("color", "#7AB0FF").

DateTime

DateTime displays a date-and-time picker. Its configuration value is a string accepted by time.parse_time().

app.star
load("render.star", "render")
load("schema.star", "schema")
load("time.star", "time")

DEFAULT_TIME = "2026-01-01T00:00:00Z"

def main(config):
event_time = time.parse_time(config.str("event_time", DEFAULT_TIME))

return render.Root(
child = render.Text(event_time.format("2006-01-02")),
)

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.DateTime(
id = "event_time",
name = "Event time",
desc = "Date and time of the event.",
icon = "clock",
),
],
)

Dropdown selects one value from a fixed list. Each schema.Option has the label shown to the user in display and the saved configuration in value.

app.star
load("render.star", "render")
load("schema.star", "schema")

OPTIONS = [
schema.Option(display = "Pink", value = "#FF94FF"),
schema.Option(display = "Mustard", value = "#FFD10D"),
]

def main(config):
return render.Root(
child = render.Text(
"Hello",
color = config.str("color", OPTIONS[0].value),
),
)

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Dropdown(
id = "color",
name = "Text color",
desc = "Color of the displayed text.",
icon = "brush",
default = OPTIONS[0].value,
options = OPTIONS,
),
],
)

Generated

Generated adds fields based on the current value of another field. Set source to that field's id; the source value is passed to handler.

Use sparingly

Generated forms are harder to understand and test than fixed schemas. Prefer a fixed set of fields when possible.

load("schema.star", "schema")

def pet_options(pet):
if pet == "dog":
return [
schema.Toggle(
id = "leash",
name = "Bring a leash",
desc = "Show the leash reminder.",
icon = "dog",
default = False,
),
]

if pet == "cat":
return [
schema.Toggle(
id = "litter_box",
name = "Litter box",
desc = "Show the litter-box reminder.",
icon = "block-question",
default = False,
),
]

return []

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Text(
id = "pet",
name = "Pet",
desc = "Enter dog or cat.",
icon = "dog",
),
schema.Generated(
id = "pet_details",
source = "pet",
handler = pet_options,
),
],
)

Location

Location lets someone choose a place. Read it with config.get("location"); the returned object contains:

KeyValue
latLatitude string
lngLongitude string
descriptionHuman-readable place
localityCity or locality
place_idPlace provider identifier
timezoneIANA time zone, such as America/New_York
load("schema.star", "schema")

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Location(
id = "location",
name = "Location",
desc = "Location used by the app.",
icon = "location-dot",
),
],
)
Protect precise locations

Before sending coordinates to a third-party API, reduce their precision to the minimum that API needs. Do not expose a user's exact location unnecessarily.

LocationBased

LocationBased calls its handler with a JSON-encoded location and displays the returned schema.Option list. The selected configuration value is a JSON string containing both display and value.

load("encoding/json.star", "json")
load("schema.star", "schema")

def nearby_stations(raw_location):
location = json.decode(raw_location)
locality = location.get("locality", "")

if locality == "New York":
return [
schema.Option(
display = "Grand Central Terminal",
value = "grand_central",
),
]

return []

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.LocationBased(
id = "station",
name = "Train station",
desc = "Choose a nearby train station.",
icon = "location-crosshairs",
handler = nearby_stations,
),
],
)

The selected value has this shape:

{"display": "Grand Central Terminal", "value": "grand_central"}

Decode it with json.decode(config.get("station")) before reading value.

OAuth2

OAuth2 starts an OAuth 2 authorization flow. It requires a client ID, authorization endpoint, scopes, and a handler. The handler receives a JSON-encoded object containing the authorization response and must return the access token.

load("schema.star", "schema")

def oauth_handler(params):
# Exchange the provider's authorization code for an access token here.
# The exact request and response are provider-specific.
fail("Configure this handler for your OAuth provider")

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.OAuth2(
id = "auth",
name = "Connect account",
desc = "Authorize access to your account.",
icon = "key",
handler = oauth_handler,
client_id = "your-client-id",
authorization_endpoint = "https://provider.example/oauth/authorize",
scopes = ["profile:read"],
),
],
)
Keep client secrets out of source

The redirect URL and token exchange depend on the OAuth provider configured in SolidPixels. Register that configured redirect URL with the provider. Store a client secret with the platform's OAuth provider configuration or encrypt it with tetra encrypt; never commit the plaintext value.

PhotoSelect

PhotoSelect opens a photo picker. SolidPixels crops the selected image to 64 × 32 pixels and stores it as a Base64-encoded string.

app.star
load("encoding/base64.star", "base64")
load("render.star", "render")
load("schema.star", "schema")

def main(config):
photo = config.get("photo")

if photo == None:
child = render.Text("Choose a photo")
else:
child = render.Image(src = base64.decode(photo))

return render.Root(child = child)

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.PhotoSelect(
id = "photo",
name = "Add photo",
desc = "Photo shown on the display.",
icon = "image",
),
],
)

Text

Text displays a text input and saves its string value under the field id.

load("schema.star", "schema")

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Text(
id = "message",
name = "Message",
desc = "Message shown on the display.",
icon = "text",
default = "Hello",
),
],
)

Read it with config.str("message", "Hello").

Toggle

Toggle displays an on/off switch and saves True or False. Use config.bool() so command-line configuration is converted to a boolean too.

load("schema.star", "schema")

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Toggle(
id = "party_mode",
name = "Party mode",
desc = "Enable the party animation.",
icon = "flag",
default = False,
),
],
)

Read it with config.bool("party_mode", False).

Typeahead

Typeahead calls its handler with the current search text. Return matching schema.Option values. Like LocationBased, the selected configuration value is a JSON string containing display and value.

load("schema.star", "schema")

FRUIT = [
schema.Option(display = "Apple", value = "apple"),
schema.Option(display = "Apricot", value = "apricot"),
schema.Option(display = "Banana", value = "banana"),
]

def search_fruit(pattern):
query = pattern.lower()
return [
option
for option in FRUIT
if option.display.lower().startswith(query)
]

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Typeahead(
id = "fruit",
name = "Fruit",
desc = "Search for a fruit.",
icon = "hashtag",
handler = search_fruit,
),
],
)

The selected value has this shape:

{"display": "Apple", "value": "apple"}