WASM-based applications that run inside Tlon messenger on your Urbit ship.
Mini Apps let developers create lightweight applications that users invoke via /commands in chat. Apps run on the user's own ship (not a central server) and post messages as the user with an [app] prefix.
~sampel-palnet: /water
~sampel-palnet: [water] Logged 1 glass. Total: 3/8
- Native integration: Apps appear as natural chat messages
- User sovereignty: Apps run on your ship, own your data
- Persistent state: Per-channel JSON state storage
- Timers: Schedule delayed callbacks (reminders, games)
- Multi-user: State shared across channel participants
- Simple SDK: TypeScript-like AssemblyScript compiles to WASM
cd cli
npm install
npm linktlon-apps new my-app --template basic
cd my-app
tlon-apps build
tlon-apps devIn dev mode, test your app interactively:
> /my-app help
[my-app] Available commands...
> /my-app hello
[my-app] Hello, ~zod!
tlon-apps deploy ~/.urbit/zodUser types: /water
|
v
+-- %channels (Tlon's chat agent) --+
| Detects message, notifies |
+-----------------------------------+
|
v
+-- %mini-apps agent ---------------+
| 1. Parses /command |
| 2. Finds installed app |
| 3. Runs WASM via urwasm |
| 4. Returns response |
+-----------------------------------+
|
v
Channel shows: [water] Logged 1 glass
tlon-mini-apps/
├── desk/ # Urbit desk
│ ├── app/mini-apps.hoon # Main agent
│ ├── sur/mini-apps.hoon # Type definitions
│ ├── lib/mini-apps.hoon # Helper library
│ └── mar/ # Marks
├── sdk/ # AssemblyScript SDK
│ ├── assembly/index.ts # SDK exports
│ └── examples/ # Example apps
├── compiler-service/ # WASM compile server
└── cli/ # Developer CLI
Your app can call these functions:
// State persistence
function getState(): string; // Load JSON state
function setState(json: string): void; // Save JSON state
// Context
function getShip(): string; // e.g., "~sampel-palnet"
function getChannel(): string; // e.g., "/~sampel/general"
// Randomness
function random(): f64; // 0.0 to 1.0
function randomInt(max: i32): i32; // 0 to max-1
// Timers
function setTimer(delayMs: i64, payload: string): string;
function cancelTimer(timerId: string): void;Your app must export:
// Required: Handle user commands
export function onMessage(input: string): string {
// input = everything after "/appname "
return "Response shown in chat";
}
// Optional: Handle scry queries
export function onQuery(path: string): string {
return "Query response";
}
// Optional: Handle timer callbacks
export function onTimer(id: string, payload: string): string {
return "Timer fired message (or empty)";
}Track daily water intake:
/water - Log a glass
/water stats - View progress
/water goal 10 - Set daily goal
Roll dice in standard notation:
/roll 2d6 - Roll two d6
/roll d20+5 - d20 with modifier
/roll 4d6 drop low - D&D stat roll
Create group polls:
/poll "Lunch?" pizza tacos sushi
/poll vote 1
/poll results
/poll close
Reputation tracking:
/karma - Check your karma
/karma give ~ship - Give karma
/karma top - Leaderboard
Personal note-taking:
/notes add Buy milk
/notes list
/notes search milk
/notes delete 1
tlon-apps new <name> # Create new app
--template <type> # basic|stateful|timer|dice
tlon-apps build # Compile to WASM
--compiler <url> # Remote compiler URL
tlon-apps deploy <pier> # Deploy to ship
--wasm <path> # WASM file path
tlon-apps dev # Local development server
--port <port> # Server port- Copy desk to pier:
cp -r desk/* /path/to/pier/mini-apps/- From dojo:
|merge %mini-apps our %base
|mount %mini-apps
|commit %mini-apps
|install our %mini-apps- The agent subscribes to %channels automatically.
:: Install app
:mini-apps &mini-apps-action [%install 'water' <wasm-octs> <manifest>]
:: Invoke directly
:mini-apps &mini-apps-action [%invoke 'water' 'stats']
:: Uninstall
:mini-apps &mini-apps-action [%uninstall 'water']
:: Enable/disable
:mini-apps &mini-apps-action [%enable 'water']
:mini-apps &mini-apps-action [%disable 'water']:: List all apps
.^((list @t) %gx /=mini-apps=/apps/noun)
:: Get app info
.^(json %gx /=mini-apps=/apps/water/json)
:: Get app state for channel
.^(json %gx /=mini-apps=/apps/water/state/json):: Watch all apps
:~ /apps
:: Watch specific app
:~ /app/water- Use dev mode: Test locally before deploying
- Keep state small: JSON state is stored in agent memory
- Handle errors gracefully: Return user-friendly messages
- Version your state: Plan for state migrations
- Test multi-user: State is shared in channels
For better error visibility when committing Hoon code, install the Clay error trace patch:
git clone https://github.com/arthyn/urbit.git -b clay-error-trace
cp pkg/arvo/sys/vane/clay.hoon <pier>/base/sys/vane/Then in dojo:
|commit %baseNow any failed |commit writes errors to:
<pier>/.urb/put/clay/latest-trace.txt
This gives you a proper dev loop:
Write code → Commit to ship → Check error file → Fix → Repeat
No more hunting through dojo scroll for syntax errors!
- Urbit ship running vere 4.0+ with urwasm
- %channels agent (Tlon messenger)
- Node.js 18+ for CLI and compiler service
MIT