Expose fixed iOS actions to Shortcuts
One.AppIntents exposes actions declared at build time to Apple’s
Shortcuts app. Each action accepts an optional string and returns a
string. Declare the action metadata in config, then define its
JavaScript handler in a native setup file so a cold Shortcuts launch
can start the React host and reach it.
On iOS prebuild generates Swift AppIntent structs with shortcut
phrases, and invocations run through the Shortcuts system. On Android
and web defineAction validates its arguments and returns an inert
remover; it registers no system action.
import { one } from 'one/vite'
export default { plugins: [ one({ setupFile: { native: './app-intents-setup.ts' }, native: { app: { name: 'MyApp', ios: { bundleId: 'com.example.myapp', appIntents: { actions: [ { id: 'com.example.myapp.echo', title: 'Echo Text', textParameterTitle: 'Text', shortcutPhrase: 'Echo text in {app}', }, ], }, }, }, }, }), ],}IDs and titles must be nonempty, IDs unique. The phrase must contain
{app} exactly once; prebuild substitutes the app’s name for Shortcuts.
textParameterTitle is optional: without it the action takes no input
and the handler receives null.
import { One } from 'one'
One.AppIntents.defineAction('com.example.myapp.echo', async (text) => { return text ?? ''})The handler must return a string or a promise of one. defineAction
returns an idempotent remover; keep the registration for the app’s
lifetime unless intentionally stopping that action.
The Shortcuts app discovers the generated Swift action types after an
iOS rebuild. Registration cannot change at runtime; native rejects an
action absent from the built manifest. Each invocation delivers once:
actions arriving before JavaScript registers are held, then claimed by
defineAction; later invocations go straight to the active handler.
A missing, thrown, or stalled handler fails the Shortcuts run. Native bounds each invocation to 20 seconds including startup, reporting a timeout past it; a thrown handler reports a handler error. The setup file must import and define handlers early, and prebuild requires it when actions are declared. Siri voice invocation, app entities, typed parameters, and device-specific policy stay outside this API.
| Methods | Type | Description |
|---|---|---|
defineAction(identifier: string, handler: AppIntentHandler) => () => voidHandle an action. Returns an idempotent remover. | (identifier: string, handler: AppIntentHandler) => () => void | Handle an action. Returns an idempotent remover. |
The handler is (text: string | null) => string | Promise<string>.
| Invocation | Type | Description |
|---|---|---|
executionIdstringThis run. Keys completion and timeout. | string | This run. Keys completion and timeout. |
identifierstringThe declared action id. | string | The declared action id. |
textstringThe input text, when the action takes one. | string | The input text, when the action takes one. |
| iOS | Android | Web | |
|---|---|---|---|
| Actions | Shortcuts app | None | None |
| Declare | At build | Ignored | Ignored |
| Handler | Setup file | Inert | Inert |
| Timeout | 20 seconds | Never runs | Never runs |
import { One } from 'one'
function firstSentence(text: string) { const end = text.search(/[.!?]\s/) return end === -1 ? text : text.slice(0, end + 1)}
One.AppIntents.defineAction('com.example.myapp.summarize', async (text) => { if (!text) return 'No text provided.' return firstSentence(text)})Pure string work fits the budget with room to spare. The null branch covers actions declared without a text parameter.
import { One } from 'one'
const NOTES: Record<string, string> = { standup: 'Standup is at 9:30 in room 4.',}
One.AppIntents.defineAction('com.example.myapp.note', async (text) => { const key = text?.trim().toLowerCase() ?? '' return NOTES[key] ?? `No note for "${text ?? ''}".`})The handler closes over module data available at setup time. A missing key returns a helpful string rather than throwing, so the Shortcuts run succeeds with a useful spoken result.
Edit this page on GitHub.