Capabilities
By default your app has no special device permissions. Request only what it needs by
adding capability blocks under permissions in waid.config.json. WAID then declares
the matching OS permissions and wires up the native glue. Every flag defaults off.
media and host (including location) work on desktop, Android, and iOS, with
the exceptions noted in each block. The other blocks are Android features today;
the iOS and desktop packagers ignore them.
Each block below goes inside permissions:
{ "name": "wildlife-journal", "dist": "dist", "permissions": { "media": { "cameraCapture": true }, "host": { "vault": true } }}Capability blocks used to sit at the top level of the file. They still parse there,
but each prints a deprecation warning on every build. waid migrate-config moves
them under permissions; the capability ids do not change, so permissions already
granted on an installed app are unaffected.
media: audio, video, camera, microphone#
"media": { "audioPlayback": true, "videoPlayback": true, "cameraCapture": true, "microphoneCapture": true, "cameraUsageDescription": "Camera is used for video capture.", "microphoneUsageDescription": "Microphone is used for audio capture."}| Key | What it enables |
|---|---|
audioPlayback / videoPlayback | Play audio / video. |
cameraCapture / microphoneCapture | Capture from the camera / microphone. On Android, microphoneCapture also enables voice recording, see below. |
cameraUsageDescription / microphoneUsageDescription | The permission-prompt text on Apple platforms (defaults to a product-name string). |
Voice recording (host.recorder)#
microphoneCapture also gives an Android app the host.recorder API. There is no
separate capability key: recording needs exactly the microphone permission this
flag already grants.
const id = await host.recorder.start() // begins a new recordingawait host.recorder.pause()await host.recorder.resume()const { peak } = { peak: await host.recorder.amplitude() } // 0..32767, for a waveformconst memo = await host.recorder.stop() // { id, url, durationMs, sizeBytes, createdAtMs } await host.recorder.state() // 'idle' | 'recording' | 'paused'await host.recorder.list() // stored recordings, newest firstawait host.recorder.remove(id)Recordings are encoded to AAC .m4a in app-private storage, so they need no
storage permission and are invisible to other apps. stop() returns a file://
URL you can hand straight to an <audio> element.
Three behaviours to design around:
- The first
start()raises the permission prompt and fails. Nothing else in a recorder app triggers one. The grant is asynchronous, so the user records on the next tap, so treat it as a prompt, not an error. - The microphone is exclusive. Don't hold a
getUserMediaaudio stream while recording; take your level meter fromhost.recorder.amplitude()instead. - A clip too short to encode is discarded and
stop()reports the failure, rather than leaving an unplayable entry inlist().
On iOS and desktop the API exists but reports NotSupportedError for capture, while
list() and state() degrade quietly so a recorder UI still renders.
contacts: device contacts (Android)#
"contacts": { "read": true, "write": true }read reads contacts; write creates/updates/deletes them.
photos: photo/video library (Android)#
"photos": { "readImages": true, "readVideos": true }Read the device's photo and video library (distinct from live media capture).
telephony: calling (Android)#
"telephony": { "placeCalls": true, "readCallState": true, "defaultDialer": false }| Key | What it enables |
|---|---|
placeCalls | Place outgoing calls. |
readCallState | Read call state. |
defaultDialer | Act as the device's default dialer. |
messaging: SMS (Android)#
"messaging": { "send": true, "read": true, "receive": true, "defaultSmsApp": false }Send, read, and receive SMS; defaultSmsApp makes your app the default SMS app (plain
SMS, no MMS).
alarm: background alarms (Android)#
"alarm": { "alarms": true }Schedule real alarms that ring over the lock screen and survive reboot.
launcher: home launcher (Android)#
"launcher": { "homeLauncher": true, "launches": ["com.acme.other"] }Let your app be the device home screen.
| Key | What it enables |
|---|---|
homeLauncher | Offer your app as the device's default home screen. |
launches | Package ids your app may open. |
appDrawer | List every launchable installed app with host.launcher.listApps(), without naming each in launches. |
manageApps | Hand off uninstall and app-info screens to the system with host.launcher.requestUninstall() / openAppInfo(). |
launches entries are literal package-id strings. If you rename another app's
packageName, update the references here by hand; they aren't auto-updated.
storage: all files access (Android)#
"storage": { "allFilesAccess": true }Browse and modify all device storage, as a file manager would (Android
MANAGE_EXTERNAL_STORAGE). This is separate from the photo library in photos.
Google Play reviews this permission closely, so turn it on only when your app is a
file manager or needs equivalent access.
haptics: vibration (Android)#
"haptics": { "enabled": true }Lets navigator.vibrate drive the device vibrator (Android VIBRATE).
host: host functions#
Native host functions your app can call. Available on desktop, Android, and
iOS. shell is desktop only: Android and iOS builds fail if it is set.
"host": { "actions": true, "vault": true, "fs": true }| Key | What it enables |
|---|---|
actions | Agent action helpers (host.actions). |
env | Read non-secret env values (window.host.env). Required to use the top-level env block. |
vault | Secure secret storage (window.host.vault). |
fs / path | Filesystem access / path helpers. |
intent | Android intents (launch / take-pending). |
auth | Signs the user in with ICE and keeps the session (window.host.auth). Needed for single sign-on across apps. |
passkey | Native passkey sign-in (window.host.passkey). Requires auth. |
mcp | Call MCP tools on your systems of record through the runtime (window.host.mcp). |
consent | List, grant, and revoke the scopes your app may use on those systems. |
events | Receive agent action deep links and run the matching registered action. |
push | Push notifications. |
process | Run long-lived subprocesses and stream their output (window.host.process). Desktop only; mobile sandboxes can't start processes. |
shell | Run a shell command and return its output. Desktop only. |
Location (host.location)#
"host": { "location": { "whenInUse": true, "precise": true } }Device position while your app is in the foreground, available as
navigator.geolocation on desktop, Android, and iOS.
| Key | What it does |
|---|---|
whenInUse | Read the position while the app is in use. |
precise | true asks for street-level accuracy. false asks for approximate location on every platform. Leave it out for approximate on Android and full precision on Apple. |
usageDescription | The text shown in the Apple permission prompt. WAID writes a default if you leave it empty. |
Put secrets in the runtime vault, never in waid.config.json. The env block is
for non-secret values only.