--- name: adhoc-distribution description: Get a test build of a mobile app or game onto specific devices without a store review - Apple ad-hoc / release-testing (register device UDIDs, an ad-hoc provisioning profile, ExportOptions.plist, xcodebuild -exportArchive) and Google Play internal app sharing (upload an AAB or APK, get a share link). Explains when to use ad-hoc vs TestFlight vs Play internal testing vs internal app sharing, the 100 devices per year limit, and the tester setup. Use when the user wants an IPA for a few devices, asks how to register a device or UDID, needs an ExportOptions.plist, or wants a quick Play install link for a build. --- # Ad-hoc builds and internal app sharing CLI: `storebound` (or `bunx storebound` in a project that has it, or `bun /cli/index.ts`). ## Which channel | Channel | Who installs | Review | Limits | Use it for | |---|---|---|---|---| | TestFlight internal (`testflight` skill) | up to 100 App Store Connect users | none | 90-day builds | the team, every build | | TestFlight external | up to 10,000 testers, by email or public link | Beta App Review for the first build of a version | 90-day builds | wider beta | | Apple ad-hoc / release-testing | registered devices only | none | 100 devices per product family per membership year | a client demo, a device lab, testers without App Store Connect access | | Play internal testing track (`play-first-release` skill) | up to 100 testers by email list | none | needs a track release | the team, every build | | Play internal app sharing | anyone allowed to use the link (default: anyone with it) | none | 100 users per link, link valid 60 days | a quick build for one person, any version code, debuggable builds | Prefer TestFlight and Play internal testing. Ad-hoc costs device slots you only get back at the start of a membership year; internal app sharing builds cannot be promoted to any track. ## Apple ad-hoc Config: `apple.teamId` (Membership details), `apple.bundleId`, optional `apple.adhoc`: `method` (`release-testing`, the name since Xcode 15.3, default; `ad-hoc` for older Xcode), `devices` (default `.storebound/devices.csv`), `profile` (a profile name: manual signing; unset: automatic signing). 1. **The owner collects the devices.** UDIDs are personal data: they stay in `.storebound/devices.csv` (gitignored), never in chat, issues or commits. Header line `name,udid,platform`, one device per line, platform `IOS` or `MAC_OS`. The tester finds the UDID in Finder (iPhone connected, click the line under the device name) or Xcode Devices; an Apple silicon Mac needs its Provisioning UDID (System Information > Hardware). 2. `storebound apple adhoc devices` (read-only): what is registered, UDIDs masked, and the slots used per device class. 3. `storebound apple adhoc register --dry`: validates the CSV (40 hex digits, or 8 hex - 16 hex), compares with the registered devices and lists the new ones. Show it to the owner. Every new device uses one of the 100 yearly slots of its product family, also if it is disabled later. Then `storebound apple adhoc register` asks and registers. One device: `--name "Test iPhone" --udid --platform IOS`. It never disables, enables or deletes a device: that is the owner's call on the Apple Developer website. 4. Signing, either: - automatic (no `apple.adhoc.profile`): Xcode creates the profile during export (`-allowProvisioningUpdates`), or - manual: `storebound apple adhoc profile --dry`, then `storebound apple adhoc profile`. It creates an IOS_APP_ADHOC profile for the bundle id with every enabled iOS device and the valid Apple Distribution certificates, and saves `build/adhoc/.mobileprovision` (double-click to install). A profile cannot be changed: when devices were added, a new profile with a dated name is created; set `apple.adhoc.profile` to it. Deleting the old one is the owner's step on the developer website. 5. Archive in Xcode (Product > Archive) or `xcodebuild archive`. 6. `storebound apple adhoc options --dry` shows the ExportOptions.plist (method, teamID, signingStyle, thinning ``, destination export); without `--dry` it writes `build/ExportOptions.plist`. 7. `storebound apple adhoc export --archive build/App.xcarchive --dry` prints the exact `xcodebuild -exportArchive` command; without `--dry` it runs it and lists the `.ipa` in `build/adhoc/`. With `-allowProvisioningUpdates` Xcode may contact Apple to create or refresh signing assets. 8. Install: Apple Configurator, Finder (drag the IPA onto the device) or Xcode Devices. A device that is not in the profile cannot install the build: re-run steps 3, 4 and 7. ## Google Play internal app sharing 1. Owner, once in Play Console > Internal app sharing (under Test and release): who may upload (the service account needs "Release apps to testing tracks" for the app, or a place on the uploader list) and who may download (an email list, or "Anyone you shared the link with can download", the default). 2. `storebound play share --aab app-release.aab --dry` (or `--apk`): file, size, sha256 and package; nothing sent. 3. `storebound play share --aab app-release.aab` asks, uploads and prints the share link and the certificate fingerprint (Google re-signs the build with an internal app sharing key). `--json`: `{ downloadUrl, certificateFingerprint, sha256 }`. Limits: app bundles up to 10 GiB, APKs up to 1 GiB. 4. Each tester, once per device: Play Store > Settings > About > tap "Play Store version" 7 times > turn on Internal app sharing. Then open the link on the device. Testers must be able to see the app's store listing on Google Play (country and track access). The link expires 60 days after the upload; re-upload for a new one. ## The owner decides - Which devices to register (yearly slots) and removing or disabling any device. - Who may download internal app sharing links, and sharing a link at all (anyone with it can install the build). - Deleting old provisioning profiles. ## Out of scope - Enterprise (in-house) distribution, Developer ID / notarized Mac apps, over-the-air install pages (the ExportOptions `manifest` key). - Firebase App Distribution and other third-party beta services.