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:
appIdplatformchannelruntimeVersionbundleId
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
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
Recommended bundle naming
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-updatesstill 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 themanifest.jsonfile - if you want a repeatable export-and-publish pipeline, move to
@otalan/cli
Preflight checklist
Before you upload, confirm:
- you built or exported the exact assets for the intended target
- the dashboard ZIP is
.otalan/bundle/bundle-<bundle-id>.zipfromotalan bundleand contains only runtime assets - the tuple values you plan to publish are correct
- the bundle ID is new for that target
- 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.