Writing a station
The structure of a station document, with a minimal example.
On this page
This station deploys nginx and adds a tab with a counter and a button. It needs no permissions.
schema: 1
id: counter
name: Counter
description: A counter, to learn the format
version: "1.0.0"
deploy:
image_ref: nginx:alpine
port: 80
ui:
tabs:
- id: main
name: Counter
panels:
- id: count
type: stat
title: Clicks
source: {action: read_count}
- id: click
type: button
label: Click me
action: click
script: |
export function read_count() {
return { data: { value: quasar.store.get('count') || 0 } }
}
export function click() {
quasar.store.set('count', (quasar.store.get('count') || 0) + 1)
return { toast: 'Clicked', refresh: ['count'] }
}How it works:
- The
countpanel calls theread_countaction and shows the number it returns. - The button calls
click, which saves a new count. clickreturns a message, and asks Quasar to refresh thecountpanel.
| Field | Required | Description |
|---|---|---|
schema |
yes | Format version. Always 1 for now. |
id |
yes | Unique identifier: lowercase letters, digits and hyphens. Also the default subdomain. Installing a station whose id is already used is refused. |
name |
yes | Display name. |
description |
yes | One line describing the station. |
version |
yes | Your version number, shown when updating. |
author |
Your name. | |
deploy |
yes | What to run. See below. |
permissions |
What the script may reach. See permissions. | |
ui |
Tabs and panels. See interface. | |
hooks |
Actions run on events or on a schedule. See hooks and series. | |
script |
The actions, in JavaScript. See script. |
A station must have at least one tab or one hook.
deploy is a catalogue entry, with the same fields and placeholders. Parameters, generated secrets and compose adaptation all work the same way.
Don’t write id, name, description or category in deploy: they belong at the top level of the document.
A select parameter can get its options from an action, with options_from. Useful for lists that keep growing, such as game versions:
params:
- name: VERSION
label: Minecraft version
kind: select
default: "1.21.4"
options: ["1.21.4", "1.21.1"] # fallback
options_from: official_versionsexport function official_versions() {
const m = quasar.http.get('https://piston-meta.mojang.com/mc/game/version_manifest_v2.json').json()
return {
options: m.versions.filter(v => v.type === 'release').map(v => v.id),
default: m.latest.release, // optional
}
}The action can also return a plain list instead of {options, default}.
Good to know:
- The returned options are added before the written
options, which stay as a fallback if the call fails. - The answer is cached for one hour.
- The action runs before the application exists, so it can only use
quasar.http, on hosts allowed bynet.external.
The same rules as catalogues apply:
{{NAME}}is replaced by Quasar when deploying.${NAME}is read by Docker Compose from the.envfile.
For a value the script needs to read, write it in env and declare it in the env permission.
When you install a station, Quasar checks it and lists every problem:
- the document’s structure and values,
- that every action the interface uses is exported by the script,
- that services named in the interface exist and are covered by permissions.
Start from components.yaml in the examples to see every panel working.