Build apps for astraOS

astraOS apps are written in QML (Qt 6) and shipped as .apkg packages. Every app runs in its own sandbox (its own Linux user), talks to the system through the Astra.Core QML module and draws with the Astra.Controls design system, so it looks at home and follows the system appearance and language.

Getting started

hello/
├── app.json          the manifest
├── icon.svg          square artwork; the system rounds it
├── qml/Main.qml      the first screen
└── i18n/it.json      translations (optional)
// qml/Main.qml
import QtQuick
import Astra.Core
import Astra.Controls

Rectangle {
    color: Theme.background
    Page {
        anchors.fill: parent
        title: qsTr("Hello")
        ListGroup {
            ListItem { text: qsTr("Battery"); value: System.batteryLevel + "%" }
            ListItem {
                text: qsTr("Send a notification")
                chevron: true
                onClicked: Notifications.post(qsTr("Hi!"), qsTr("Sent from my app"))
            }
        }
    }
}

app.json

{
  "package": "com.example.hello",       // reverse domain, unique on Market
  "name": "Hello", "names": { "it": "Ciao" },
  "version": "1.0.0", "versionCode": 1,  // versionCode must grow with every release
  "description": "A friendly app.", "descriptions": { "it": "Un'app amichevole." },
  "main": "qml/Main.qml",
  "icon": "icon.svg",
  "background": "#000000",              // splash colour while the app starts
  "appearance": "auto",                 // "dark" for apps that are always dark (camera, video)
  "webengine": true,                    // web content (QtWebEngine): browsers, web apps
  "permissions": ["astra.permission.INTERNET", "astra.permission.CAMERA"],
  "opens": ["text/plain"],              // offered in "Open with" for these types
  "share": ["image/*"],                 // offered in the share sheet for these types
  "widgets": [{ "id": "now", "name": "Now", "qml": "widgets/Now.qml", "size": "small" }]
}

UI: Astra.Controls

Use the design system instead of drawing your own controls: rounded surfaces, springs and colours that follow the system theme. Main components:

  • Page, PageStack — large titles, back navigation and edge swipe
  • ListGroup, ListItem (switch, chevron, value, selection, icon tiles)
  • Button, IconButton, Switch, Slider, SegmentedControl, TextField
  • Card, EmptyState, ProgressBar, Spinner, PinEntry, Sheet (bottom sheet), ShareSheet
  • Icon (system symbols), AppIcon, Theme (colours, type scale, radii, accent)

System services: Astra.Core

System        battery, brightness, torch, device info, power
Settings      values["key"], setValue() (apps own "app.<package>." keys)
Notifications post(title, body), dismiss(id)
Packages      list(), launch(package, args), targets(mime, "share" | "open")
Application   args, intent(args) signal, dataDir, mediaDir, media(filters)
Files         list(dir), copy/move/rename/remove, readText/writeText (shared storage)
Permissions   check(permission, cb), request(permission, cb)
Dialogs       show({ title, message, buttons }, cb)   -> system bottom sheet
Location      state.fix { lat, lon, accuracy }, call("get", { maxAge, watch }, cb)   (LOCATION)
Calendar      call("list" | "add" | "update" | "delete", ...), alerts as notifications (CALENDAR)
MailClient    IMAP / SMTP over TLS for mail apps (INTERNET)
Weather, Network, Telephony, Contacts, Alarms, MediaController, Language, Fonts

Permissions

Declare every permission in app.json. Normal ones (internet, vibration, alarms) are granted at install. Runtime ones (camera, microphone, location, contacts, photos, phone, SMS, notifications, nearby devices) must be asked for with Permissions.request(): the system shows a popup the user must answer. The answer is final; people change it later in Settings › Apps. When a permission grants access to a device (camera, media), the system restarts the app after it is allowed.

Permissions.request("astra.permission.CAMERA", function(granted) {
    if (granted) camera.start()
})

Files and sharing

Apps with the MEDIA permission read and write the shared storage (Files.home). To share, use ShareSheet; to receive, declare "share" or "opens" and read Application.args ({ action: "share" | "open", files: [paths], text }), also delivered later through Application.intent.

Light and dark

Use Theme.background, Theme.surface, Theme.text and friends: they change with the system appearance. Theme.light tells which one is active. Apps that must stay dark set "appearance": "dark".

Translations

Write every string in English with qsTr() and add i18n/<language>.json, keyed by the English text. Plurals use { "one": …, "other": … }. Missing strings fall back to English.

{ "Hello": "Ciao", "%n photos": { "one": "%n foto", "other": "%n foto" } }

Lock screen widgets

Each widget is a small QML file declared in "widgets" (size small or wide); people add it in Settings › Lock Screen.

Packaging

python3 tools/apkgtool/apkgtool.py pack hello/ -o com.example.hello.apkg
python3 tools/apkgtool/apkgtool.py verify com.example.hello.apkg

apkgtool is in the astraOS source tree. Packages are reproducible and end with a SHA-256 checksum that Market verifies on upload.

Publishing on Market

  1. Ask to become a developer on the home page; you receive your sign-in once approved.
  2. In the developer panel create the app with its package name, then upload an .apkg. The package name and a growing versionCode are checked, and so is the design language: Market runs astra check on the package and refuses a version with errors, listing them; warnings are shown but don't block publishing.
  3. Add the description, category, icon and screenshots, then publish. Updates are new uploads.

Phones install apps only from Market. People can install an .apkg file directly only after turning on Settings › Developer Options › Sideload Apps; ADU over USB debugging always can.

From CI, upload with your session token:

curl -X PUT --data-binary @com.example.hello.apkg \
     -H "Authorization: Bearer $ASTRA_TOKEN" \
     "https://<server>/api/dev/apps/com.example.hello/versions?notes=Bug%20fixes"

Get a token with POST /api/auth/login and a device object (it appears in AstraID as a device you can remove at any time).

Testing with ADU

Turn on Developer Options (Settings › About › More Settings, tap Architecture seven times) and USB debugging, then:

adu install com.example.hello.apkg
adu start com.example.hello
adu logcat