Generate a bundle

Prepare a release artifact that will pass Otalan's validation pipeline, including the extra considerations required for Expo-based publish flows.

Otalan accepts one release artifact shape in the dashboard: a ZIP archive that contains the web or Expo-exported assets for a single release target.

This page explains how to prepare that artifact cleanly so Otalan can validate and activate it without surprises.

For dashboard UI publishing, the recommended path is still to run otalan bundle in the app repository and upload the generated .otalan/bundle/bundle-<bundle-id>.zip file. For Expo releases, keep the generated .otalan/bundle/manifest.json ready for the manifest field too.

.otalan/ is generated output. Add it to .gitignore; publish reads it from the current local or CI workspace after otalan bundle runs.

Define the release inputs first

Before you package anything, decide the tuple and release identifiers you are targeting:

  • appId
  • platform
  • channel
  • runtimeVersion
  • bundleId

Example:

appId         = com.company.app1
platform      = ios
channel       = production
runtimeVersion = 3.4.0
bundleId      = 3.4.0-web.5

The archive itself does not encode all of these values. They become meaningful only when you publish the archive through the Otalan release flow.

Build the frontend output

Otalan does not build your application for you. You must first run the build or export step that produces the assets the client will actually load.

For Capacitor-style web bundles

Build your web app with your normal Bun workflow:

# Build the Capacitor web assets.
bun run build

The result is usually a directory such as dist/, www/, or .output/public/, depending on your stack.

Keep web bundles minimal

The OTA bundle should contain the app shell, not the content library. Keep HTML, CSS, JavaScript, manifests, and small runtime-critical assets in the generated output.

Do not embed product images, videos, large fonts, PDFs, marketing media, or other heavy files in JavaScript, CSS, base64 strings, or the ZIP itself. Host those files externally and reference them by URL so a text or layout update does not force users to redownload unrelated media.

For Expo with expo-updates

Export the update artifacts using the Expo workflow your app already uses. In practice, that often means a command in the expo export family.

When publishing manually through the dashboard, choose the Expo runtime in the publish form and keep the generated Otalan .otalan/bundle/manifest.json ready. Paste its content or drop the manifest.json file onto the manifest field so Otalan can validate the Expo release metadata.

If you want Otalan to handle that repository-aware packaging flow for you, prefer @otalan/cli.

Otalan does not block older runtime versions by version number. Official support currently covers Expo 54, 55, and 56 for Expo publish flows. Other runtimes and older Expo or Capacitor versions might work or might not, but they are not supported for the moment.

Where this page fits

This page is about the release artifact itself: what goes into the ZIP, what stays out, and what Otalan validates before activation.

Use for the first publish path, for automation behavior, and for dashboard operation.

Package only the generated assets

For dashboard upload, use the CLI output at .otalan/bundle/bundle-<bundle-id>.zip. If you are preparing an archive outside the dashboard and CLI path, package the generated output directory into one archive.

Create the ZIP from the generated output directory, not from the repository root.

Example:

cd dist
zip -r ../otalan-bundle.zip .

The important rule is simple:

  • include only the generated runtime assets
  • do not include the whole repository
  • do not wrap the output in an extra parent directory unless your runtime explicitly expects it

What Otalan validates

Every uploaded ZIP is validated before activation. Otalan rejects the archive if the content is unsafe or structurally invalid.

Otalan checks for:

  • path traversal attempts such as ../
  • absolute paths
  • duplicate archive entries
  • encrypted entries
  • source maps
  • symbolic links
  • nested ZIP archives
  • archive size that exceeds configured limits
  • extracted size that exceeds configured limits
  • excessive file counts or path lengths
  • extreme compression ratios

Compression-ratio checks only apply once the extracted entry or archive size reaches 64 KB, so tiny optimized assets are not rejected solely for compressing well.

The default minimum billable ZIP size is 50 KB for analytics and quotas. Valid smaller archives are accepted and counted as 50 KB there.

Storage accounting is based on what Otalan keeps in managed object storage. Capacitor releases count the stored archive. Expo releases count the stored archive plus the extracted Expo launch asset and listed asset objects that Expo downloads from the manifest.

The archive should be boring. Boring is good here.

Practical packaging rules

Use these rules to keep the validation path clean:

  • package compiled assets only
  • keep product media, videos, large documents, and other externally hosted content outside the bundle
  • avoid local editor artifacts
  • do not include shell scripts or installers
  • do not include another ZIP inside the bundle
  • keep bundle naming predictable and human-readable

Use a bundle ID that helps operators understand both the runtime line and the iteration count.

A practical pattern is:

<runtimeVersion>-web.<increment>

Example:

3.4.0-web.5

That pattern makes rollback and release history easier to understand at a glance.

Expo-specific notes

For Expo releases, keep these points in mind:

  • the exported assets still need to be packaged into the ZIP you publish
  • the runtime-facing manifest is served by Otalan, but expo-updates still owns the client-side install flow
  • the generated manifest tells Expo where to fetch the release assets
  • upload .otalan/bundle/bundle-<bundle-id>.zip, choose Expo in the dashboard publish form, and paste the generated Otalan manifest JSON or drop the manifest.json file
  • if you want a repeatable export-and-publish pipeline, move to @otalan/cli

Preflight checklist

Before you upload, confirm:

  1. you built or exported the exact assets for the intended target
  2. the dashboard ZIP is .otalan/bundle/bundle-<bundle-id>.zip from otalan bundle and contains only runtime assets
  3. the tuple values you plan to publish are correct
  4. the bundle ID is new for that target
  5. you have the generated Otalan manifest JSON ready for Expo releases

If those five points are true, the dashboard publish step becomes straightforward and Otalan has a clean archive to validate.