Release
Build signed store artifacts, prove the exact artifact works, and publish it to Google Play and TestFlight from your machine or from a generated GitHub Actions workflow.
#Path to the stores
absolute mobile doctor releaseabsolute mobile build android|iosabsolute mobile test --release · mobile certifyabsolute mobile publish android|iosRun every command from your app's root, the directory with package.json and absolute.config.ts. Each release lands in its own directory under .absolutejs/mobile/releases, named by the hash of its contents, so the artifact you certify is the artifact you publish.
#Release doctor
The release doctor runs on its own and as part of every build. A failed check stops the build; a warning is reported and does not.
bunx absolute mobile doctor release # every configured platform
bunx absolute mobile doctor release ios --json # one platform, machine-readableSome things no tool can check for you. The doctor lists them as manual review on every run:
#Build and sign
bunx absolute mobile build android src/backend/server.ts
bunx absolute mobile build ios src/backend/server.tsAndroid. Configure a release signingConfig in the Android project in mobile/android, or set all four of these variables and AbsoluteJS signs the bundle with your upload key. The bundle must pass signature verification before it is kept.
export ABSOLUTE_ANDROID_KEYSTORE_PATH="$HOME/.config/absolutejs/upload.jks"
export ABSOLUTE_ANDROID_KEYSTORE_PASSWORD="..."
export ABSOLUTE_ANDROID_KEY_ALIAS="upload"
export ABSOLUTE_ANDROID_KEY_PASSWORD="..."iOS. Builds use Xcode automatic signing and export for App Store Connect. Set your team, and set mobile.ios.version to the marketing version you are shipping. On Linux or Windows, the build runs on a Mac you have paired over SSH.
export ABSOLUTE_IOS_DEVELOPMENT_TEAM="ABCDE12345"
# From Linux or Windows, build on a paired Mac
bunx absolute mobile pair mac studio builder@studio.local
bunx absolute mobile build ios src/backend/server.ts --remote studio--unsigned only for a build you will not publish.#Certify
A certification records that a specific artifact passed its acceptance checks. It is tied to the artifact's digest, so it cannot be reused for a different build. Production releases require one by default: installed evidence for Android and store-delivered evidence for iOS.
# Install the exact release on a device and run the acceptance checks
bunx absolute mobile test android \
--release .absolutejs/mobile/releases/android/amobile_android_RELEASE \
--report
# Bind that evidence to the release
bunx absolute mobile certify .absolutejs/mobile/releases/android/amobile_android_RELEASE \
--evidence path/to/report-dir \
--require installed| Evidence | Platform | What it proves |
|---|---|---|
| installed | Android | The exact AAB is installed through Bundletool and launched, offline and online |
| simulator | iOS | The exact archive runs in the iOS Simulator |
| device | iOS | Installed and run on a physical iPhone or iPad |
| store | iOS | Delivered through TestFlight and run on a device |
For iOS, stronger evidence satisfies weaker requirements: store covers device, and device covers simulator. Set the requirement per release channel and per Google Play track, or set certification: false to turn the gate off.
mobile: {
// ...
release: {
certification: {
channels: {
production: { android: 'installed', ios: 'store' },
beta: { android: 'installed', ios: 'simulator' }
},
googlePlayTracks: { production: 'installed', internal: false }
}
}
}#Publish
Publishing goes through a release module in your project, mobile.release.ts by default. It keeps every release and its certification in your storage, then uploads to the store with @absolutejs/deploy. Credentials stay in the module's environment and are never passed as command-line flags. Google Play uses Application Default Credentials.
// mobile.release.ts
import { S3Client } from '@aws-sdk/client-s3';
import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
import { createGooglePlayReleasePublisher } from '@absolutejs/deploy/google-play';
import { createNativeReleaseRegistry } from '@absolutejs/deploy/native-release';
const store = awsS3BlobStore({
bucket: process.env.RELEASE_BUCKET ?? '',
client: new S3Client({ region: process.env.RELEASE_REGION })
});
export default createGooglePlayReleasePublisher({
receiptStore: store,
registry: createNativeReleaseRegistry({ store })
});TestFlight uses an App Store Connect API key. Keep it in a second module and pick it with --registry.
// mobile.release.ios.ts
import { S3Client } from '@aws-sdk/client-s3';
import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
import { createAppStoreConnectReleasePublisher } from '@absolutejs/deploy/app-store-connect';
import { createNativeReleaseRegistry } from '@absolutejs/deploy/native-release';
const store = awsS3BlobStore({
bucket: process.env.RELEASE_BUCKET ?? '',
client: new S3Client({ region: process.env.RELEASE_REGION })
});
export default createAppStoreConnectReleasePublisher({
auth: {
issuerId: process.env.APP_STORE_CONNECT_ISSUER_ID ?? '',
keyId: process.env.APP_STORE_CONNECT_KEY_ID ?? '',
privateKey: await Bun.file(
process.env.APP_STORE_CONNECT_PRIVATE_KEY_PATH ?? 'AuthKey.p8'
).text()
},
receiptStore: store,
registry: createNativeReleaseRegistry({ store })
});# Google Play: 10% of production, with release notes
bunx absolute mobile publish android src/backend/server.ts \
--certification path/to/certification-dir \
--play-track production \
--play-rollout 0.1 \
--play-notes 'en-US=Faster checkout'
# TestFlight: an external group, submitted for beta review
bunx absolute mobile publish ios src/backend/server.ts \
--registry mobile.release.ios.ts \
--testflight-group 'External Beta' \
--testflight-notes 'en-US=Faster checkout' \
--testflight-submit-review| Flag | Meaning |
|---|---|
| --play-track | internal, alpha, beta, production or a custom track |
| --play-rollout | Fraction of users for a staged rollout, such as 0.1 |
| --play-status | completed, draft, halted or in-progress |
| --play-notes | Release notes as language=text; repeatable |
| --play-update-priority | In-app update priority from 0 to 5 |
| --testflight-group | Beta group name or ID |
| --testflight-notes | What to Test, as locale=text; repeatable |
| --testflight-submit-review | Submit the build for beta app review |
| --release | Publish an existing release directory instead of building |
| --registry | Release module to use; defaults to mobile.release.ts |
The Play version code and the Apple build number are allocated during publishing and recorded, so a retried publish reuses them rather than uploading twice.
#GitHub Actions
One command writes .github/workflows/absolute-mobile.yml with validate, Android and iOS jobs. Pull requests build without any secrets. Release jobs run in a protected absolute-mobile-release environment, sign their certifications through GitHub's OIDC identity, and clean up every credential file when they finish. Add --secret-env for each variable your release module reads.
bunx absolute mobile ci github src/backend/server.ts \
--publish \
--secret-env RELEASE_BUCKET \
--secret-env RELEASE_REGIONCreate the absolute-mobile-release environment under Settings › Environments, require a reviewer, and store these secrets there:
| Android secret | Purpose |
|---|---|
| ABSOLUTE_ANDROID_KEYSTORE_BASE64 | Base64 Android upload keystore |
| ABSOLUTE_ANDROID_KEYSTORE_PASSWORD | Keystore password |
| ABSOLUTE_ANDROID_KEY_ALIAS | Upload key alias |
| ABSOLUTE_ANDROID_KEY_PASSWORD | Upload key password |
| ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 | Google service-account JSON; only to publish to a Play track |
| iOS secret | Purpose |
|---|---|
| ABSOLUTE_IOS_CERTIFICATE_BASE64 | Base64 Apple Distribution .p12 |
| ABSOLUTE_IOS_CERTIFICATE_PASSWORD | .p12 password |
| ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64 | Base64 App Store provisioning profile |
| ABSOLUTE_IOS_KEYCHAIN_PASSWORD | Password for the temporary keychain |
| ABSOLUTE_IOS_DEVELOPMENT_TEAM | Ten-character Apple team ID |
| APP_STORE_CONNECT_ISSUER_ID | App Store Connect API issuer |
| APP_STORE_CONNECT_KEY_ID | App Store Connect API key ID |
| APP_STORE_CONNECT_PRIVATE_KEY_BASE64 | Base64 .p8 key; only to publish to TestFlight |
#Promote without rebuilding
Promotion publishes the artifact a CI run already built and certified. Every step is recorded locally, so an interrupted promotion resumes where it stopped and is never dispatched twice.
# Publish the exact artifact a CI run built and certified, without rebuilding
bunx absolute mobile ci promote android \
--run-id 1234567890 \
--certification path/to/certification-dir \
--play-track production \
--watch --audit
bunx absolute mobile ci promotions # every promotion and its state
bunx absolute mobile ci promote --resume DISPATCH_IDFixes that only change your pages do not need a store release at all. Over-the-air updates ship them to installed apps directly.