> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revyl.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Signed Store Builds

> Sign a release build and upload it to App Store Connect or Google Play

Revyl can sign your release build with your own certificate or keystore and
upload it to App Store Connect or Google Play. You add a signing step and a
deploy step to a build profile. Your `xcodebuild` or `gradlew` command does not
change.

<Note>Signing and deploy steps only run in remote builds. Run the profile with
`--remote`.</Note>

## iOS

<Steps>
  <Step title="Create an App Store Connect API key">
    1. Open [App Store Connect](https://appstoreconnect.apple.com) and go to
       **Users and Access** > **Integrations** > **App Store Connect API**.
    2. Click **Generate API Key**. Give it the **App Manager** role.
    3. Download the `.p8` file. It can only be downloaded once.
    4. Note the **Key ID** and the **Issuer ID** shown on the same page.
  </Step>

  <Step title="Export your distribution certificate">
    1. Open **Keychain Access** on the Mac that holds the certificate.
    2. Under **My Certificates**, right-click the **Apple Distribution**
       certificate and click **Export**.
    3. Save it as a `.p12` and set a password. Make sure the private key is
       included.

    If any target in your project uses manual signing in Xcode, also download its
    App Store provisioning profile from the
    [Apple Developer portal](https://developer.apple.com/account/resources/profiles/list).
    Targets set to **Automatically manage signing** do not need one.
  </Step>

  <Step title="Add the credentials to Revyl">
    ```bash theme={null}
    base64 -i distribution.p12  | revyl build secret set IOS_DIST_CERT --stdin
    base64 -i AuthKey_ABC123.p8 | revyl build secret set ASC_API_KEY --stdin
    revyl build secret set IOS_DIST_CERT_PASSWORD

    # Only for manually signed targets
    base64 -i App.mobileprovision | revyl build secret set IOS_APP_PROFILE --stdin
    ```
  </Step>

  <Step title="Add the steps to your build profile">
    Put `ios-signing` before your build commands and `app-store-connect-deploy`
    after them. Use your own Key ID and Issuer ID.

    ```yaml theme={null}
    build:
      profiles:
        release:
          ios:
            app_id: <your iOS app UUID>
            setup_commands:
              - yarn install --immutable
              - npx --yes pod-install
            build_commands:
              - ios-signing:
                  certificate: IOS_DIST_CERT
                  certificate_password: IOS_DIST_CERT_PASSWORD
                  api_key: ASC_API_KEY
                  key_id: ABC123DEF4
                  issuer_id: 57246542-96fe-1a63-e053-0824d011072a
              - xcodebuild archive -workspace ios/App.xcworkspace -scheme App -configuration Release -destination generic/platform=iOS -archivePath build/App.xcarchive $REVYL_XCODEBUILD_SIGNING_ARGS
              - xcodebuild -exportArchive -archivePath build/App.xcarchive -exportOptionsPlist "$REVYL_EXPORT_OPTIONS" -exportPath build $REVYL_XCODEBUILD_SIGNING_ARGS
              - app-store-connect-deploy:
                  api_key: ASC_API_KEY
                  key_id: ABC123DEF4
                  issuer_id: 57246542-96fe-1a63-e053-0824d011072a
            output_path: build/App.ipa
    ```

    For manually signed targets, add `provisioning_profiles: [IOS_APP_PROFILE]`
    to `ios-signing`.
  </Step>

  <Step title="Run the build">
    Increase `CFBundleVersion` if you have already uploaded this build number,
    then:

    ```bash theme={null}
    revyl build --profile release --platform ios --remote
    ```

    The build appears in TestFlight after Apple finishes processing it.
  </Step>
</Steps>

### `ios-signing` inputs

| Input                            | Required                               | Description                                                              |
| -------------------------------- | -------------------------------------- | ------------------------------------------------------------------------ |
| `certificate`                    | Yes                                    | Secret name of the base64 `.p12`                                         |
| `certificate_password`           | No                                     | Secret name of the `.p12` password                                       |
| `api_key`, `key_id`, `issuer_id` | Yes, unless every target has a profile | Secret name of the `.p8`, plus the Key ID and Issuer ID as plain values  |
| `provisioning_profiles`          | Only for manually signed targets       | Secret names of base64 `.mobileprovision` files                          |
| `export_method`                  | No                                     | `app-store-connect` (default), `ad-hoc`, `enterprise`, or `development`  |
| `export_options`                 | No                                     | Extra keys for `ExportOptions.plist`, for example `uploadSymbols: false` |

### `app-store-connect-deploy` inputs

| Input                            | Required | Description                                                 |
| -------------------------------- | -------- | ----------------------------------------------------------- |
| `api_key`, `key_id`, `issuer_id` | Yes      | Same values as above                                        |
| `ipa`                            | No       | Path to the `.ipa`. Defaults to the profile's `output_path` |
| `apple_id`                       | No       | The app's numeric App Store ID                              |

## Android

<Steps>
  <Step title="Get your upload keystore">
    Use the keystore you already sign releases with. Play Console shows its
    expected SHA-256 under **Setup** > **App signing**.

    If you do not have one yet:

    ```bash theme={null}
    keytool -genkeypair -v -keystore upload.jks -alias upload -keyalg RSA -keysize 2048 -validity 10000
    ```
  </Step>

  <Step title="Create a Google Play service account">
    1. In [Google Cloud Console](https://console.cloud.google.com/iam-admin/serviceaccounts),
       create a service account and download a JSON key for it.
    2. In [Play Console](https://play.google.com/console), go to
       **Users and permissions** > **Invite new users**.
    3. Enter the service account's email address and give it release access to
       your app.
  </Step>

  <Step title="Add the credentials to Revyl">
    ```bash theme={null}
    base64 -i upload.jks | revyl build secret set ANDROID_KEYSTORE --stdin
    revyl build secret set GOOGLE_PLAY_SERVICE_ACCOUNT --stdin < service-account.json
    revyl build secret set ANDROID_KEYSTORE_PASSWORD
    ```
  </Step>

  <Step title="Add the steps to your build profile">
    Put `android-signing` after your build command and `google-play-deploy` after
    that. `output_path` must point to the one `.aab` or `.apk` your build produces.

    ```yaml theme={null}
    build:
      framework: android
      profiles:
        release:
          android:
            app_id: <your Android app UUID>
            image: android-ubuntu-24.04-jdk-17-ndk-r27b
            build_commands:
              - ./gradlew --no-daemon :app:bundleRelease
              - android-signing:
                  keystore: ANDROID_KEYSTORE
                  keystore_password: ANDROID_KEYSTORE_PASSWORD
              - google-play-deploy:
                  service_account: GOOGLE_PLAY_SERVICE_ACCOUNT
                  package_name: com.example.app
                  track: internal
                  release_status: draft
            output_path: app/build/outputs/bundle/release/app-release.aab
    ```

    `android-signing` signs the file at `output_path` in place, so nothing in your
    Gradle config needs to change. If the keystore holds more than one key, add
    `key_alias`.
  </Step>

  <Step title="Run the build">
    Increase `versionCode` if you have already uploaded this one, then:

    ```bash theme={null}
    revyl build --profile release --platform android --remote
    ```

    With `release_status: draft` the build is uploaded but not released. Release
    it from Play Console, or set `release_status: completed` to release to the
    track's testers straight away.
  </Step>
</Steps>

### `android-signing` inputs

| Input               | Required                                   | Description                                                        |
| ------------------- | ------------------------------------------ | ------------------------------------------------------------------ |
| `keystore`          | Yes                                        | Secret name of the base64 keystore                                 |
| `keystore_password` | Yes                                        | Secret name of the keystore password                               |
| `key_alias`         | Only if the keystore has more than one key | Alias to sign with                                                 |
| `key_password`      | No                                         | Secret name of the key password. Defaults to the keystore password |

### `google-play-deploy` inputs

| Input             | Required | Description                                                 |
| ----------------- | -------- | ----------------------------------------------------------- |
| `service_account` | Yes      | Secret name of the service account JSON                     |
| `package_name`    | Yes      | Application ID, for example `com.example.app`               |
| `track`           | No       | `internal` (default), `alpha`, `beta`, or `production`      |
| `release_status`  | No       | `completed` (default), `draft`, `inProgress`, or `halted`   |
| `aab`             | No       | Path to the `.aab`. Defaults to the profile's `output_path` |
| `mapping_file`    | No       | Path to the R8 mapping file to upload with the build        |

## Troubleshooting

Credentials are checked before your build command runs, so a bad credential
fails in seconds with the reason in the build log.

| Error                                                            | Fix                                                                     |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Certificate password rejected, or certificate has no private key | Re-export the `.p12` from Keychain Access with the private key included |
| Profile does not match the certificate                           | Regenerate the profile for this certificate                             |
| Keystore password rejected, or alias not found                   | Check the values against your current release pipeline                  |
| `output_path` must resolve to exactly one APK or AAB             | Point `output_path` at the single file your build produces              |
| Duplicate build number or version code                           | Bump it and run again                                                   |
| Google Play permission denied                                    | Invite the service account to the app in Play Console                   |

Profiles with signing or deploy steps always build from source; cached build
results are not reused.
