This guide walks you through creating a minimal Chattree plugin from scratch. No prior Chattree knowledge required.
The easiest way to create, compile, test, and package a plugin is using the official @chattree/cli. No global installation is required:
# 1. Create a new plugin with interactive scaffolding (AssemblyScript, Rust, or WAT)
npx @chattree/cli create plugin my-plugin
# 2. Enter project directory
cd my-plugin
# 3. Compile source code to WebAssembly and auto-synchronize SHA-256 checksum
npx @chattree/cli build
# 4. Simulate & test plugin execution locally in terminal
npx @chattree/cli test
# 5. Validate manifest contract and security boundaries
npx @chattree/cli validate
# 6. Package into a ready-to-use .chattree-plugin bundle
npx @chattree/cli package
The generated .chattree-plugin file is ready to install in the Chattree app (Settings → Plugins → Importar plugin) or publish to the Marketplace.
If you prefer building without the CLI, follow the step-by-step instructions below.
.wasm binaries:
wat2wasm — simplest, no toolchain neededcargo build --target wasm32-unknown-unknownwasi-sdktinygo build -target wasmzip, PowerShell Compress-Archive, or Node.js)No Chattree source code required.
You only need the public SDK documentation and a WASM compiler.
my-hello-plugin/
├── plugin.json ← manifest
├── plugin.wat ← source (optional — human-readable WAT)
└── plugin.wasm ← compiled binary
plugin.json)Every plugin needs a plugin.json manifest.
This is the contract between your plugin and Chattree.
{
"schemaVersion": 1,
"id": "com.example.hello",
"version": "1.0.0",
"name": "Hello Plugin",
"description": "Greets the user. Example minimal plugin.",
"author": "Your Name",
"primaryCommand": "hello",
"aliases": [],
"type": "imported",
"capabilities": [],
"tags": ["demo", "hello"],
"runtime": "wasm",
"entrypoint": "hello",
"wasmPath": "plugin.wasm",
"wasmSha256": "REPLACE_WITH_ACTUAL_SHA256",
"runtimeApiVersion": "1.0.0",
"maxMemoryPages": 2,
"timeoutMs": 5000,
"fuelLimit": 50000000,
"tools": [
{
"id": "com.example.hello.hello",
"command": "hello",
"displayName": "Hello",
"description": "Greets you: /hello <name>",
"capabilities": []
}
]
}
Key points:
id must be a globally unique reverse-domain identifier — use your own domain.primaryCommand is the slash-command (without the /).entrypoint is the exported WASM function name that Chattree will call.capabilities is the list of Host API capabilities your plugin needs (see capabilities.md).wasmSha256 must be the SHA-256 hex digest of plugin.wasm.The simplest way to start is WebAssembly Text format (WAT).
Chattree supports two ABI architectures for communicating with plugins via linear memory:
ABI v2 provides dynamic memory allocation and supports payloads up to 16 MB (12 MB by default):
chattree_allocate(size: i32) -> i32chattree_deallocate(ptr: i32, size: i32)chattree_dispatch(req_ptr: i32, req_len: i32) -> i64(out_ptr << 32) | out_len0 while waiting for Host API calls, and resume via resume(res_ptr, res_len) -> i64.For simple plugins with small payloads:
| Offset | Content |
|---|---|
0 (or inputOffset) | Input string (UTF-8, written by Chattree) |
inputLen | End of input |
1024 (or outputOffset) | Output string starts here (written by your WASM) |
Your exported function receives (i32 inputPtr, i32 inputLen) and must:
[inputPtr … inputPtr+inputLen]outputOffset (default 1024)i32Tip: If your ABI v1 plugin needs to receive payloads larger than 1024 bytes without implementing ABI v2, you can specify
"outputOffset": 32768(or another offset) inplugin.json.
(module
;; Export at least 1 memory page (64 KB)
(memory (export "memory") 2)
;; Pre-load the greeting prefix at offset 1024
(data (i32.const 1024) "Hello, ")
;; exported function: (inputPtr, inputLen) -> outputLen
(func (export "hello") (param $in_ptr i32) (param $in_len i32) (result i32)
;; Copy input (the user's name) after "Hello, " at offset 1031
(memory.copy
(i32.const 1031) ;; dst: 1024 + len("Hello, ")
(local.get $in_ptr)
(local.get $in_len)
)
;; Return total output length: 7 + inputLen
(i32.add (local.get $in_len) (i32.const 7))
)
)
# Install wabt (https://github.com/WebAssembly/wabt)
wat2wasm plugin.wat -o plugin.wasm
# macOS / Linux
sha256sum plugin.wasm
# Windows (PowerShell)
Get-FileHash plugin.wasm -Algorithm SHA256
Update the wasmSha256 field in plugin.json with the result.
Your tools array in plugin.json declares every slash-command.
For a single /hello command:
"tools": [
{
"id": "com.example.hello.hello",
"command": "hello",
"displayName": "Hello",
"description": "Greets you by name: /hello <name>",
"capabilities": []
}
]
tools[].command is the user-facing slash command. Each tool entry can optionally specify tools[].entrypoint to invoke a dedicated WASM export; if omitted, the runtime falls back to the top-level entrypoint (or tools[].command) and injects "command", "api", and "toolId" into the JSON envelope so multi-command plugins can route calls cleanly.
If your plugin only processes text and returns a response, no capabilities are needed.
If your plugin needs to access device data (network, notes, camera, etc.),
list the required capabilities:
"capabilities": ["network.http", "storage.private"]
See the full list in capabilities.md.
If your plugin needs to call a Host API (e.g., save something to storage or make an HTTP request), your WASM must:
chattree.host_call function from the host.host_call(reqPtr, reqLen, resPtr, resMaxLen).(import "chattree" "host_call"
(func $host_call (param i32 i32 i32 i32) (result i32))
)
{
"api": "storage",
"operation": "set",
"payload": { "key": "counter", "value": "42" }
}
{
"success": true,
"data": { "stored": true, "key": "counter", "value": "42" }
}
See host-api.md for all operations and their exact inputs/outputs.
A .chattree-plugin file is a standard ZIP archive containing:
plugin.json ← manifest (required)
plugin.wasm ← compiled WASM binary (required for runtime="wasm")
icon.png ← optional icon (512×512 PNG recommended)
The official CLI tool automatically synchronizes the wasmSha256 checksum and bundles the files at root level:
npx @chattree/cli package
# Or for a specific directory:
npx @chattree/cli package path/to/my-hello-plugin/
Ensure plugin.json and plugin.wasm are at the root of the ZIP archive:
# Linux / macOS
zip -9 my-plugin.chattree-plugin plugin.json plugin.wasm
# Windows (PowerShell)
Compress-Archive -Path plugin.json, plugin.wasm -DestinationPath my-plugin.zip
Rename-Item my-plugin.zip my-plugin.chattree-plugin
Important: The ZIP archive must contain
plugin.jsonat the root (not inside a nested subdirectory).
Transfer my-plugin.chattree-plugin to your Android device, then:
.chattree-plugin file.Capabilities are enforced by Chattree’s permission gateway when the plugin calls the Host API. Consent prompts and OS permission flows depend on the capability, grant state, platform, and app version. Keep the declared set minimal and handle denied or unavailable operations. Confirm the current settings UI in the app release you target; labels and navigation can change.
In any Chattree conversation, type:
/hello World
Chattree will:
/hello.plugin.wasm.hello function with input "World" written at offset 0.Hello, WorldEvery Chattree plugin can return pre-formatted Markdown directly to the user (instead of raw JSON or plain unformatted text).
When your exported function or resume() writes formatted Markdown into the output buffer:
###, blockquotes >, bold **, bullet lists, emojis like 📅, 📝, 📊, and tables).{ ... } or [ ... ]), Chattree automatically bypasses the second LLM generation pass.### 📅 Evento agendado
> **Título:** Reunión con el equipo
> **Inicio:** 2026-10-29 15:00
> **Fin:** 2026-10-29 15:30
*Agendado correctamente en tu calendario.*
ui.components)Beyond Markdown, plugins requesting the ui.components capability can return rich interactive UI cards directly into the conversation.
By returning a JSON object with "type": "card", your plugin can display:
success, warning, error, info, neutral, white, mono, monochrome, primary) with optional icons (note, alarm, etc.).key_value grids and list items (title + subtitle preview) up to 40 items.hideText): Optional "hideText": true card property to render only the visual card without duplicating a text bubble above it.command: Triggers another slash command (commands starting with / prompt the user with a confirmation modal).url: Opens an external link safely in the browser.copy: Copies values directly to the clipboard.{
"type": "card",
"displayText": "Server status: Operational",
"ui": {
"title": "Database Cluster",
"subtitle": "PostgreSQL 16 High-Availability",
"elements": [
{ "type": "badge", "text": "ONLINE", "variant": "success" },
{ "type": "progress", "value": 82, "label": "Storage Used: 82%" },
{
"type": "key_value",
"items": [
{ "label": "Primary Node", "value": "pg-primary-01" },
{ "label": "Replicas", "value": "2 in sync" },
{ "label": "Connections", "value": "142 / 200" }
]
},
{ "type": "divider" },
{
"type": "button_row",
"buttons": [
{ "label": "View Metrics", "action": "url", "payload": "https://monitor.example.com" },
{ "label": "Failover", "action": "command", "payload": "/cluster failover", "variant": "danger" }
]
}
]
}
}
Chattree registers declared tool metadata, but natural-language invocation is mediated by the app’s intent interpreter and is not guaranteed for every provider or command. An intentContract can describe valid actions and fields (see plugin-manifest.md); test each supported provider and retain a usable slash-command path. Do not rely on a specific model JSON shape as a public runtime ABI.
chattree-repo directory; examples assume that repository is your current working directory.