Aura Docs

Develop an App

Scaffold a new Aura app — manifest, lifecycle, first run.

The shortest path from idea to running iframe. Every step happens inside the OS — open a Terminal, scaffold, launch, jump in to edit.

The workflow at a glance

1. Open the Terminal app (launcher → Terminal, or Ctrl+Alt+Space)
2. aura dev new com.example.hello       → wizard scaffolds the app
3. aura app start com.example.hello     → backend boots
4. Click the new icon in the launcher   → iframe loads
5. aura jump com.example.hello          → drop into the sandbox to edit
                                          → run `claude` for Claude Code

Steps 1-4 are this page. Step 5 is its own page — Develop in the App Sandbox.

Scaffold with aura dev new

aura dev new com.example.hello

The wizard walks you through:

PromptWhat it sets
Package nameReverse-domain id, equals the directory name. Regex ^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$.
Display nameShown in launcher, dock, Process Manager.
Icon1-3 char glyph (e.g. H, HL, HEL). Falls back to the first letter of the name.
Shape presetactivity (default, has a UI) · activity-bg (UI + stays alive when last window closes) · service (headless backend, no UI).
Instance modesingle (reuse one backend) · multi (new backend per launch).
Sandboxproot (cheap, ~3 ms spawn) · container (kernel-namespace isolation, ~80-150 ms).
ToolsAllowlist of binaries bind-mounted into /aura/my-tools. Pick from the registry; bash and node are almost always wanted.

The scaffold writes a minimal manifest (only the fields you actually chose differ from defaults), the Astro config wired to auraAppIntegration(), lifecycle stubs that call SDK factories, and a CLAUDE.md primer for the new app.

What the scaffold ships

apps/com.example.hello/
├── app.manifest.json
├── astro.config.mjs
├── package.json
├── CLAUDE.md
└── src/
    ├── pages/
    │   ├── index.astro
    │   └── api/
    │       └── lifecycle/
    │           ├── onCreate.ts
    │           ├── onStart.ts
    │           ├── onResume.ts
    │           ├── onPause.ts
    │           ├── onStop.ts
    │           ├── onDestroy.ts
    │           └── onActivityCreate.ts        (activity-mode only)

You don't need to ship entrypoint.sh or health.ts — the runner synthesises the entrypoint, and auraAppIntegration() injects the health route. Both can still be overridden by shipping your own files.

Launch the new app

aura app start com.example.hello

The AppManager allocates a port, spawns the sandbox, runs onCreate → onStart → onResume, then marks the instance resumed. Click the icon in the launcher (it appears as soon as the manifest loads) — the iframe boots and renders src/pages/index.astro.

Manifest fields that actually matter

The full schema lives in packages/core/src/types/manifest.ts. In practice, after scaffolding you'll touch six fields. Run aura dev clean-manifest apps/<id> to drop everything else back to schema defaults.

{
  "id": "com.example.hello",
  "name": "Hello",
  "instanceMode": "single",        // or "multi"
  "activityMode": "none",          // or "multi"
  "tools": ["bash", "node"],

  // Optional but common:
  "intentFilters": [               // declare you can handle SEND text/plain
    { "action": "aura.intent.action.SEND", "dataMime": ["text/plain"] }
  ],
  "dataProvider": {                // expose data to other apps
    "authority": "com.example.hello",
    "providers": [{ "path": "/api/data/notes" }]
  }
}

See Core Concepts for what each policy does at runtime.

Lifecycle hooks

Six plain hooks + two activity hooks. Every one is a one-line createLifecycleHandler call from @aura/app-sdk. Add behaviour by passing impl:

// src/pages/api/lifecycle/onDestroy.ts
import { createLifecycleHandler } from '@aura/app-sdk';
import { state } from '../../../state.js';

export const POST = createLifecycleHandler('onDestroy', async () => {
  state.activities.clear();
  await flushPendingWrites();
});

Activity hooks return path/title for new windows:

// src/pages/api/lifecycle/onActivityCreate.ts
import { createActivityCreateHandler } from '@aura/app-sdk';
import { state } from '../../../state.js';

export const POST = createActivityCreateHandler(({ activityId }) => {
  state.activities.add(activityId);
  return { path: '/', title: `Hello #${activityId.split('#').pop()}` };
});

Inside a hook: identity + runtime context

Hooks (and every other server-side handler in your app) can read their runtime identity via two SDK helpers. Don't reach for process.env directly:

import { getAppContext, readIdentityHeaders } from '@aura/app-sdk';

const ctx = getAppContext();
//  ctx = { appId, instanceId, appPort, osApiBase, dataDir, layerTag }
//
// In a request handler — pull headers the proxy stamps on every
// incoming request:
export const POST: APIRoute = async ({ request }) => {
  const { appId, instanceId, activityId } = readIdentityHeaders(request);
  // … your logic
};

activityId is only present on requests coming through an activity- scoped iframe URL; for non-activity apps it's null.

Persisting app data

Every app instance has a private, writable directory at /data inside the sandbox. It is bind-mounted by the OS from the scope's per-instance data path — scopes/<scope>/apps/<appId>/<instanceId>/ on the aura-app-data Docker volume — so anything written there survives container restarts and OS reboots.

import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';

const DATA_DIR  = process.env['AURA_DATA_DIR'] ?? '/data';   // always /data inside sandbox
const DATA_FILE = `${DATA_DIR}/mydata.json`;

// Load on module init (runs once per process lifetime)
const saved = existsSync(DATA_FILE)
  ? JSON.parse(readFileSync(DATA_FILE, 'utf-8'))
  : { defaultValue: 0 };

// Write on every mutation
function save(value: unknown) {
  mkdirSync(DATA_DIR, { recursive: true });
  writeFileSync(DATA_FILE, JSON.stringify(value), 'utf-8');
}

Or use getAppContext() from the SDK which resolves the same path:

import { getAppContext } from '@aura/app-sdk';
const { dataDir } = getAppContext();   // → '/data'

Two storage patterns — don't mix them up

AuraOS has two distinct persistence mechanisms. Use the right one:

NeedUseWhere data lives
App-private state (documents, history, counter values, scrollback)/data filesystemaura-app-data volume, per-instance path
OS-level settings (theme, keymap, workspaces, clock format)KV store (/api/kv/os/<key>)Redis in aura-app-data volume

/data is per-instance, isolated, and only visible to your app. The KV store is shared OS infrastructure — apps that write there need the correct permission (system.kv.os.*) and the data is visible to every app that reads the same namespace.

Do not write app state to the KV store. Use it only for OS-wide settings that the shell or other apps need to react to. For everything else — documents, user preferences private to your app, caches, session state — write to /data.

/data is also distinct from /run/context: that path holds OS-managed environment variables and secrets injected by Aura Context and is read-only to your app. Write your own persistent data to /data.

What survives a restart

StateSurvives?
Files written to /data✅ yes
KV store values✅ yes
In-memory variables / globalThis singletons❌ no
Active instances and activities❌ no (need relaunch)

If your app holds user-facing state in a globalThis singleton without writing it to /data, that data is lost every time the container restarts — either from a crash, an aura app restart, or a code change. The reference apps Notepad, Counter, and Terminal all persist their state to /data as working examples of this pattern.

Need a non-Astro framework?

The com.aura.docs app you're reading right now runs on Next.js + fumadocs via runtime: 'raw'. Set runtime: "raw" in the manifest, ship an entrypoint.sh that execs your framework on $APP_PORT, serve /api/lifecycle/{onCreate,onStart,onResume,health} from inside the framework, and the rest is identical to an Astro app. Until that gets its own page, the docs app is the working reference — apps/com.aura.docs/ in the repo.

Reference apps to learn from

AppPattern
com.aura.terminalMulti-instance shell + PTY over WebSocket.
com.aura.notepadMulti-activity shared state (one backend, many views).
com.aura.counterMulti×multi archetype: N instances, M activities per.
com.aura.settingsContent provider + theme + permissions.
com.aura.consoleBackground WebSocket log persistence.
com.aura.exampleScaffold reference (minimal Astro).
com.aura.docsRaw runtime (Next.js + fumadocs).
  • Publish an App — ship it to a registry so others can find and install it from the app store.
  • Develop in the App Sandboxaura jump, Claude Code inside the sandbox, log tailing.
  • Core Concepts — the mental model behind every field above.
  • SDK Reference — every osClient.* method and loose helper export with a copy-paste example.
  • CLI Reference — every aura <cmd> with usage and key flags.