Capacitor quick start

Wire the Otalan Capacitor runtime into an installed app so it can check for OTA updates, install compatible bundles, and confirm successful launches.

Use this page to wire the installed Capacitor app. Publishing is a separate release step covered by .

For Otalan Cloud, Otalan handles bundle validation for you.

Install the runtime packages

Run these commands in your Capacitor app repository:

NPM package: @otalan/capacitor.

# Install the Otalan client and required Capacitor runtime packages with Bun.
bun add @otalan/capacitor @capawesome/capacitor-live-update @capacitor/app @capacitor/core
# Sync native projects after changing Capacitor plugins.
bunx cap sync

Initialize Otalan on startup

Create the app-side environment values. Use an OTA App Key here, never an OTA Publish Key.

VITE_OTALAN_API_URL=https://api.otalan.com
VITE_OTALAN_APP_KEY=otalan_ota_xxx
VITE_OTALAN_APP_ID=com.example.app
VITE_OTALAN_CHANNEL=production
VITE_OTALAN_RUNTIME_VERSION=1.0

Create src/ota-update.ts:

import { initializeUpdater, type InitializedCapacitorUpdater } from '@otalan/capacitor'

let updater: InitializedCapacitorUpdater | undefined

export async function syncOtalanUpdates() {
  if (!updater) {
    updater = await initializeUpdater({
      apiUrl: import.meta.env.VITE_OTALAN_API_URL,
      apiKey: import.meta.env.VITE_OTALAN_APP_KEY,
      appId: import.meta.env.VITE_OTALAN_APP_ID,
      channel: import.meta.env.VITE_OTALAN_CHANNEL,
      runtimeVersion: import.meta.env.VITE_OTALAN_RUNTIME_VERSION || undefined,
      onResume: true,
      onDownloadProgress: (event) => {
        console.log(
          event.bundleId,
          event.progress,
          event.downloadedBytes,
          event.totalBytes,
        )
      },
    })
  }

  // If you only want to check availability without updating yet, call check() and inspect updateAvailable.
  // const { updateAvailable } = await updater.check()

  // sync() checks too, then downloads and installs when an update is available.
  return updater.sync()
}

Call syncOtalanUpdates() from app boot, a settings screen, or any user interaction that should check for updates:

// src/main.ts
import { syncOtalanUpdates } from './ota-update'

void syncOtalanUpdates()

initializeUpdater() does not check for updates immediately. It prepares install confirmation and the optional resume listener. Call syncOtalanUpdates() when you want update work to run.

Keep the OTA bundle lean

Capacitor clients download the selected archive when Otalan serves a new bundle. Keep that archive focused on the web shell: HTML, CSS, JavaScript, and small runtime-critical assets.

Do not bundle product images, videos, large fonts, PDFs, marketing media, or other content-heavy files into the OTA ZIP. Put those files behind your own external hosting and reference them by URL from the app.

Publish the first bundle

After the native build contains this runtime initialization, publish a baseline bundle from the app repository with .

Use the same appId, platform, channel, and runtimeVersion values that the installed app reports.

Verify update and rollback

  1. Install a native build that contains the updater initialization above.
  2. Publish a visible text or style change through otalan bundle and otalan publish.
  3. Fully close and reopen the app, or trigger the app path that calls the updater sync.
  4. Confirm the app loads the new web assets.
  5. Roll back to the first bundle from the dashboard.
  6. Restart or sync again and confirm the app returns to the known-good bundle.

Troubleshooting

  • If no update is returned, check the selected project, appId, platform, channel, runtimeVersion, OTA App Key, and rollout percentage.
  • If a local API or bundle URL fails on device, make sure the native runtime can reach it. Physical devices usually need your machine's LAN IP, Android emulators often need 10.0.2.2, Android HTTP testing may require android:usesCleartextTraffic="true" in the development manifest, and plain HTTP bundle URLs also require allowInsecureBundleUrls: true only in local development.
  • If the native app does not change, run bunx cap sync after plugin changes and install a fresh native build that imports ota-update.ts.
  • If rollback is not visible, confirm the rolled-back bundle targets the same tuple and restart or trigger another updater sync.