Preview Links
A preview link opens an uploaded bundle in the real app on one phone, without releasing it. Scan the QR code, tap Open in the app, and the app downloads that bundle and reloads into it. Use it to check a change on a device, get a client's sign-off, or let an agent share “here is my change”.
Previews need @otakit/capacitor-updater 3.2 or later. Only web code can be previewed; native changes still need a store build.
Set up the app once
Turn preview links on in the builds that should accept them, for example internal or staging builds:
// capacitor.config.ts
plugins: {
OtaKit: {
appId: "YOUR_OTAKIT_APP_ID",
previewLinks: true
}
}The app also needs a custom URL scheme, such as myapp. Many apps already have one. On iOS add it to Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array><string>myapp</string></array>
</dict>
</array>On Android add an intent filter to the main activity in AndroidManifest.xml:
<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="myapp" /> </intent-filter>
The plugin handles myapp://otakit-preview?… links itself; no app code is needed, and other links keep reaching your app. On iOS it receives links through Capacitor's ApplicationDelegateProxy, which the default AppDelegate already forwards to.
Create a preview link
From the dashboard: the QR button on a bundle row. The first link asks for the app's URL scheme and remembers it. From the CLI:
# Upload and create a link in one step otakit upload --preview # Or preview a bundle you already uploaded otakit preview <bundle-id> --expires 24h --scheme myapp otakit preview --list otakit preview --revoke <preview-id>
The CLI prints the link and a QR code. Links last 7 days by default (1h, 24h, 7d or 30d), and an app can have 20 active links.
What the tester sees
- The link opens a page with the bundle version, expiry, a QR code on desktop, and the buttons Open in the app and Exit preview.
- Opening it downloads the bundle like any update, with signature checks, and reloads the app. The app still calls
notifyAppReady(); a bundle that fails to start rolls back and ends the preview. - The app returns to its normal release when the tester taps Exit preview, when your code calls
OtaKit.stopPreview(), or when the link expires or is revoked. The app notices that on its next update check (launch, resume, orOtaKit.update()). If the channel has nothing to download, it returns to the bundle it ran before the preview, or to the one built into the app. - Nobody else is affected: a preview is not a release, does not appear as a channel, and does not count towards release health or auto-revert. Its downloads count as downloads.
const { preview } = await OtaKit.getState(); // { startedAt, version } or null
if (preview) showBanner(`Preview ${preview.version}`, () => OtaKit.stopPreview());
OtaKit.addListener('previewFailed', ({ reason }) => {
// 'unavailable': expired, revoked, or built for another runtime version
// 'busy': another update did not finish in time; open the link again
});Security
Each link carries a random 130-bit token and works only for the bundle it was created for. Anyone with the link and a build that accepts previews can open it, so keep previewLinks off in builds that should never show unreleased work (such a build also ends a preview an earlier build started), and revoke links you no longer need.
API and agents
See the REST API for the preview endpoints. Agents use create_preview and revoke_preview; see MCP & Agent Skills.