Skip to content

Getting Started

Terminal window
bun add @capgo/capacitor-widget-kit
bunx cap sync
import { CapgoWidgetKit } from '@capgo/capacitor-widget-kit';

For Live Activities and WidgetKit extensions, configure the native app first:

  • Use iOS 17+ for interactive Live Activity buttons when possible.
  • Add NSSupportsLiveActivities to the app Info.plist when using ActivityKit.
  • Add the same App Group to the app target and the widget extension target.
  • Set CapgoWidgetKitAppGroup in both Info.plist files to the shared App Group identifier.
<key>CapgoWidgetKitAppGroup</key>
<string>group.app.capgo.widgetkit.exampleapp.widgetkit</string>
const { supported, reason } = await CapgoWidgetKit.areActivitiesSupported();
if (!supported) {
console.log('WidgetKit bridge unavailable:', reason);
}

Use this mode when the widget can render resolved SVG. The plugin stores state, resolves placeholders, applies tap actions, switches SVG frames, and keeps timer state consistent.

const { activity } = await CapgoWidgetKit.startTemplateActivity({
activityId: 'workout-session-1',
openUrl: 'myapp://workout/session-1',
state: {
title: 'Chest Day',
frame: 'summary',
restDurationMs: 90000,
},
definition: {
id: 'workout-card',
timers: [
{
id: 'rest',
durationPath: 'state.restDurationMs',
},
],
actions: [
{
id: 'next-frame',
eventName: 'widget.frame.changed',
frameMutations: [
{
op: 'next',
path: 'frame',
surface: 'lockScreen',
},
],
},
{
id: 'toggle-rest',
eventName: 'widget.timer.toggled',
timerMutations: [
{
op: 'toggle',
timerId: 'rest',
},
],
},
],
layouts: {
lockScreen: {
width: 100,
height: 40,
frameIdPath: 'state.frame',
frames: [
{
id: 'summary',
hotspots: [{ id: 'switch', actionId: 'next-frame', x: 0, y: 0, width: 100, height: 40 }],
svg: `<svg viewBox="0 0 100 40"><text x="6" y="22">{{state.title}}</text></svg>`,
},
{
id: 'timer',
hotspots: [{ id: 'pause-play', actionId: 'toggle-rest', x: 0, y: 0, width: 100, height: 40 }],
svg: `<svg viewBox="0 0 100 40"><text x="6" y="22">{{timers.rest.remainingText}}</text></svg>`,
},
],
},
},
},
});

Native widgets can trigger the same actions through their hotspot/action wiring. The app can also run them directly:

await CapgoWidgetKit.performTemplateAction({
activityId: activity.activityId,
actionId: 'toggle-rest',
sourceId: 'app-pause-play-button',
});

Actions emit events so the app can process widget interactions after launch or resume:

const { events } = await CapgoWidgetKit.listTemplateEvents({
activityId: activity.activityId,
unacknowledgedOnly: true,
});
for (const event of events) {
console.log('Widget event:', event.eventName, event.state, event.timers);
}
await CapgoWidgetKit.acknowledgeTemplateEvents({
activityId: activity.activityId,
});
await CapgoWidgetKit.updateTemplateActivity({
activityId: activity.activityId,
state: {
title: 'Back Day',
frame: 'summary',
restDurationMs: 120000,
},
});
await CapgoWidgetKit.endTemplateActivity({
activityId: activity.activityId,
state: { title: 'Workout complete', frame: 'summary' },
});

Frame mutations write the active frame id into state. A layout can then read it with frameIdPath.

OperationBehavior
setSet a specific frame id. Plain strings are treated as literal frame ids; {{...}} templates are resolved first.
nextMove to the next frame from frameIds or the frames declared on surface.
previousMove to the previous frame.
toggleToggle between the first two available frames, or between the current frame and frameId.

Invalid frame ids are ignored when the mutation has a known selectable frame list, so state stays aligned with the rendered surface.

Timer mutations target a named timer from definition.timers.

OperationBehavior
start / restartStart from zero using the current duration.
pauseStore elapsed time and clear startedAt.
resumeResume only paused timers. Stopped timers stay stopped until an explicit start or restart.
togglePause a running timer or resume a paused timer.
resetClear elapsed time and return to idle.
stopClear runtime progress and mark the timer stopped.
setDurationRecompute status after a duration change.

Timer bindings are available to SVG as {{timers.<id>.remainingText}}, {{timers.<id>.elapsedMs}}, {{timers.<id>.status}}, and related fields.

Use this mode when the widget UI is built in native code. The plugin gives the app and widget a shared session record and a message queue.

const { session } = await CapgoWidgetKit.startWidgetSession({
widgetId: 'native-session-1',
kind: 'workout-controls',
state: { isRunning: true, selectedSetId: 'set-1' },
metadata: { accent: '#00d69c' },
});
await CapgoWidgetKit.updateWidgetSession({
widgetId: session.widgetId,
merge: true,
state: { isRunning: false },
});
const { sessions } = await CapgoWidgetKit.listWidgetSessions();
console.log('Known widget sessions:', sessions);

Messages cover work that needs a later response, such as a widget asking the app to sync data.

const { message } = await CapgoWidgetKit.sendWidgetMessage({
widgetId: session.widgetId,
direction: 'widgetToApp',
name: 'syncWorkoutSet',
payload: { setId: 'set-1' },
expectsResponse: true,
});
await CapgoWidgetKit.acknowledgeWidgetMessages({
messageIds: [message.messageId],
});
await CapgoWidgetKit.completeWidgetMessage({
messageId: message.messageId,
response: { synced: true },
});

To fail the job, pass error instead of response:

await CapgoWidgetKit.completeWidgetMessage({
messageId: message.messageId,
error: 'Network unavailable',
});

completeWidgetMessage is idempotent. If the message is already completed or failed, repeated calls return the existing message snapshot.

await CapgoWidgetKit.stopWidgetSession({
widgetId: session.widgetId,
state: { isRunning: false },
});
GroupAPIs
CapabilityareActivitiesSupported, getPluginVersion
SVG activity lifecyclestartTemplateActivity, updateTemplateActivity, endTemplateActivity, getTemplateActivity, listTemplateActivities
SVG actions and eventsperformTemplateAction, listTemplateEvents, acknowledgeTemplateEvents
Native widget sessionsstartWidgetSession, updateWidgetSession, stopWidgetSession, getWidgetSession, listWidgetSessions
Native widget messagessendWidgetMessage, listWidgetMessages, acknowledgeWidgetMessages, completeWidgetMessage

The full type reference lives in the plugin repository at src/definitions.ts.