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:

json
{
"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#

json
"media": {
"audioPlayback": true,
"videoPlayback": true,
"cameraCapture": true,
"microphoneCapture": true,
"cameraUsageDescription": "Camera is used for video capture.",
"microphoneUsageDescription": "Microphone is used for audio capture."
}
KeyWhat it enables
audioPlayback / videoPlaybackPlay audio / video.
cameraCapture / microphoneCaptureCapture from the camera / microphone. On Android, microphoneCapture also enables voice recording, see below.
cameraUsageDescription / microphoneUsageDescriptionThe 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.

js
const id = await host.recorder.start() // begins a new recording
await host.recorder.pause()
await host.recorder.resume()
const { peak } = { peak: await host.recorder.amplitude() } // 0..32767, for a waveform
const memo = await host.recorder.stop() // { id, url, durationMs, sizeBytes, createdAtMs }
 
await host.recorder.state() // 'idle' | 'recording' | 'paused'
await host.recorder.list() // stored recordings, newest first
await 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 getUserMedia audio stream while recording; take your level meter from host.recorder.amplitude() instead.
  • A clip too short to encode is discarded and stop() reports the failure, rather than leaving an unplayable entry in list().

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)#

json
"contacts": { "read": true, "write": true }

read reads contacts; write creates/updates/deletes them.

photos: photo/video library (Android)#

json
"photos": { "readImages": true, "readVideos": true }

Read the device's photo and video library (distinct from live media capture).

telephony: calling (Android)#

json
"telephony": { "placeCalls": true, "readCallState": true, "defaultDialer": false }
KeyWhat it enables
placeCallsPlace outgoing calls.
readCallStateRead call state.
defaultDialerAct as the device's default dialer.

messaging: SMS (Android)#

json
"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)#

json
"alarm": { "alarms": true }

Schedule real alarms that ring over the lock screen and survive reboot.

launcher: home launcher (Android)#

json
"launcher": { "homeLauncher": true, "launches": ["com.acme.other"] }

Let your app be the device home screen.

KeyWhat it enables
homeLauncherOffer your app as the device's default home screen.
launchesPackage ids your app may open.
appDrawerList every launchable installed app with host.launcher.listApps(), without naming each in launches.
manageAppsHand 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)#

json
"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)#

json
"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.

json
"host": { "actions": true, "vault": true, "fs": true }
KeyWhat it enables
actionsAgent action helpers (host.actions).
envRead non-secret env values (window.host.env). Required to use the top-level env block.
vaultSecure secret storage (window.host.vault).
fs / pathFilesystem access / path helpers.
intentAndroid intents (launch / take-pending).
authSigns the user in with ICE and keeps the session (window.host.auth). Needed for single sign-on across apps.
passkeyNative passkey sign-in (window.host.passkey). Requires auth.
mcpCall MCP tools on your systems of record through the runtime (window.host.mcp).
consentList, grant, and revoke the scopes your app may use on those systems.
eventsReceive agent action deep links and run the matching registered action.
pushPush notifications.
processRun long-lived subprocesses and stream their output (window.host.process). Desktop only; mobile sandboxes can't start processes.
shellRun a shell command and return its output. Desktop only.

Location (host.location)#

json
"host": { "location": { "whenInUse": true, "precise": true } }

Device position while your app is in the foreground, available as navigator.geolocation on desktop, Android, and iOS.

KeyWhat it does
whenInUseRead the position while the app is in use.
precisetrue 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.
usageDescriptionThe 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.