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, or OtaKit.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.