Hide chapters

iOS App Distribution & Best Practices

First Edition · iOS 14.4 · Swift 5.3 · Xcode 12.4

Section I: iOS App Distribution & Best Practices

Section 1: 17 chapters
Show chapters Hide chapters

Section II: Appendices

Section 2: 2 chapters
Show chapters Hide chapters

5. Internal Distribution
Written by Pietro Rea & Keegan Rush

You’re on the way to the kitchen at work when you bump into the company’s CEO. You chat about your ongoing iOS project, which piques her interest. She’d like to check out the project on her iPhone. What should you do next?

She already has the App Store version, but that doesn’t include what you’re working on. Unless she’s a developer, you shouldn’t ask her to download Xcode and build from the source. She may not have a Mac, and even if she does, downloading Xcode is a big time commitment and not a practical option. So how can she see a current snapshot of the app on her own device?

You might remember Apple’s third-party software philosophy from Chapter 1, “The App Store”: Distribution of any kind of third-party software, even private distribution outside the App Store, has officially-supported distribution methods that Apple expects you to use. Fortunately, you have several options for internal distribution.

This chapter covers ad hoc distribution, a manual way of achieving limited distribution to a small group, usually for testing and gathering feedback.

Types of internal distribution

If you’re still wondering what internal distribution means, you can mentally substitute internal with private. Internal or private distribution includes all types of distribution outside the App Store. The App Store remains the only way of achieving public distribution on iOS and related platforms.

Internal distribution is a large topic. Before diving into ad hoc distribution, it’s worth surveying all the internal distribution methods so you know what the best available method is for you. Which internal distribution method you should choose depends on your end goal, who’s going to use the app and who’s going to buy the app.

Note: To learn more about officially-supported internal distribution methods, watch the session App Distribution — From Ad-hoc to Enterprise from WWDC 2019:

Personal Team distribution

When you register as an Apple developer, you automatically get access to Personal Team, which is a bare-bones version of the full Apple Development Program.

Personal Team allows you to distribute apps to a few devices using Xcode without having to pay for the full enrollment. Personal Team distribution is meant for learning and getting started with the SDKs. Builds you distribute this way expire after a couple of days.

Ad hoc distribution

When you enroll and pay for the full Apple Developer Program, either as an individual or as an organization, you gain the ability to achieve limited distribution to a small group by using ad hoc distribution.

Ad hoc distribution is not a long-term or scalable distribution solution. You can only distribute apps to up to 100 devices per product family per year and builds expire yearly. Developers often use ad hoc distribution to deploy alpha, internal-facing builds, usually as part of a build pipeline.


TestFlight is Apple’s officially supported method for distributing beta — that is, external-facing — builds for testing and gathering feedback. Since Apple makes TestFlight, it’s deeply integrated with the App Store and App Store Connect.

With TestFlight, you can have up to 100 internal testers and up to 10,000 external testers, making it a good option if you run into the 100-device limit with ad hoc distribution. Keep in mind, however, that TestFlight builds are only active for 90 days so it’s not a long-term solution. Chapter 6, “TestFlight” covers this method in detail.

In-house distribution

In-house distribution allows large organizations to develop and deploy internal-use apps for employees. To use it, you need to apply to the Apple Developer Enterprise Program, which is separate from the regular program, and pay an additional 299 U.S. dollars per year.

In-house distribution also allows large-scale alpha build distribution without having to worry about the 100-device limit. Most of this chapter also applies to in-house distribution of enterprise builds.

Custom App Distribution

For a long time, in-house distribution was the only supported way to achieve large-scale internal deployments. Nowadays, Apple prefers you to use Custom App Distribution. In-house distribution still exists to support edge cases where Custom App Distribution doesn’t apply.

Unlike in-house distribution, Custom App Distribution leverages the App Store’s tools and infrastructure, like TestFlight and App Store Connect. If you’re distributing an internal app for a large organization, Custom App Distribution should be at the top of your list.

Distributing ad hoc builds

This chapter builds on the previous chapter, so feel free to go back to Chapter 4, “Code Signing & Provisioning” if you get stuck on any of the topics from that chapter. In this chapter, instead of creating a distribution provisioning profile to generate an App Store build, you’ll create an ad hoc provisioning profile to generate an ad hoc build.

Before moving on, make sure you have the following set up:

  1. An Apple ID enrolled in the Apple Developer Program. You also need to be logged into Xcode with the same Apple ID.

  2. An App ID registered in Apple’s developer portal for the app. You should already have this set up from Chapter 3, “Submitting Your First App for Review”. If not, go back to that chapter to learn how to create one.

  3. A distribution certificate registered in the developer portal and saved to Keychain Access on your Mac. If you don’t already have one, Chapter 4, “Code Signing & Provisioning”, can help.

  4. An iOS device. This can be any iPhone or iPad running iOS 13.4 or newer, to match the sample app’s deployment target.

Registering a device

In ad hoc distribution, you can only distribute builds to a maximum of 100 devices per device family per year. Apple enforces the device limit by requiring you to register each device’s unique device ID (UDID), in the developer portal.

Going back to the scenario from the beginning of the chapter, if you want to distribute an internal build to a colleague using ad hoc distribution, the first thing you need to do is collect their device’s UDID.

Finding the UDID

A UDID is a sequence of letters and numbers that form a unique identifier. Your iPhone, iPad and all other Apple devices have a UDID that no other Apple device shares.

When faced with the task of retrieving a UDID, a common reaction is to head to the device’s Settings app. This is where you find the device’s model number, serial number and IMEI number. However, none of those is the UDID. For privacy and security reasons, there’s no way to see the UDID on the physical device or in the Settings app.

You can retrieve a UDID by connecting your device to a Mac and using either Finder, Xcode, Xcode Server or Apple Configurator 2. You could also use a hosted Mobile Device Management (MDM) provisioning profile and skip the wired connection, but MDM is out of scope for this book. Using Finder is the simplest option, so this section covers how to get a UDID that way.

First, connect your iOS device to your Mac using a cable. Open Finder on your Mac and click your device’s name on the left sidebar. Unless you changed your device’s name, the default device name follows the pattern Your Name’s Device Model (e.g. Ray’s iPhone XS Max).

Finder opens a device management pane. Unless you’re also a fan of F. Scott Fitzgerald, the device name you see will be different from the one in the screenshot.

Note: Apple moved device management from iTunes to Finder in macOS 10.15 (Catalina). Head to iTunes instead of Finder if your Mac is running 10.14 (Mojave) or earlier.

The text label directly below the device name doesn’t look clickable, but it is. Click it.

Open sesame! Clicking the text field reveals the information you’re looking for. Right-click the text label and select Copy UDID.

Typically, the hardest part about registering UDIDs is not anything you have to do, but rather getting others to follow these instructions so they can send you their device’s UDID. If you’re in charge of internal ad hoc distribution, you can save a lot of time by documenting the steps in this section in your own words somewhere easily accessible, like an internal wiki or a Google doc.

Adding a UDID to the developer portal

Now that you’ve located the UDID, it’s time to add it to the developer portal. Log in to the developer portal at In the sidebar, click Devices. Next to the heading Devices, click the + button to register your UDID.

Under the heading Register a Device, leave the default platform as iOS, tvOS, watchOS. Next, enter a Device Name that’ll help you remember the device. Finally, under Device ID (UDID), paste the UDID you previously copied.

The device name you enter in the developer portal doesn’t have to match the device’s name in Finder. In fact, it’s better to make the device name on the developer portal more descriptive so you can tell devices apart in the future without having to manually compare UDIDs.

Since people tend to upgrade their devices over time, aim to include at least the device’s owner and model in the Device Name field. For example, use Ray Wenderlich’s iPhone XS Max instead of Ray’s iPhone.

Click Continue to register the device. Next, click Devices in the left menu to verify that the device you just added appears in the list of registered devices.

Creating the provisioning profile

Now that you have a new device registered in the developer portal, it’s time to create an ad hoc provisioning profile.

You can think of a provisioning profile as a set of answers to questions the system asks about your app. An ad hoc provisioning profile is no different than a regular one, except it answers one additional question.

An ad hoc provisioning profile also answers the question where can you run? with an embedded list of device UDIDs.

A distribution provisioning profile, like the one you created in Chapter 4, “Code Signing & Provisioning”, doesn’t need to answer this question because, in this context, the term “distribution” implies distribution on the App Store. If someone can get the app on the App Store, it’s allowed to run.

Note: If you need a refresher on provisioning profiles, refer to Chapter 4, “Code Signing & Provisioning”.

Go back to the developer portal. In the sidebar, click Profiles. Then, next to the heading Profiles, click the + button to create a new provisioning profile.

Under Distribution, select Ad Hoc. Click Continue.

Select Emitron’s App ID and your distribution certificate on the next two screens. Once you reach the third step, select the device you registered in the last section.

Name your provisioning profile Emitron Ad Hoc Provisioning Profile. Click Generate.

Click Download. You can also preview ad hoc provisioning profiles with Finder’s Quick Look preview. The only difference is the added PROVISIONED DEVICES section, which contains the embedded list of registered UDIDs.

Click the .mobileprovision file you just downloaded. Press Space to activate the preview.

Now, close the preview and double-click the provisioning profile to install it. This makes it available to Xcode’s build process.

The fact that registered UDIDs are part of ad hoc provisioning profiles has an important implication that developers often overlook: Every time you register a new UDID, you need to create a new internal build.

How so? Unfortunately, you can’t simply swap the provisioning profile in past builds. So if you need to distribute an internal build to someone new, you need a brand-new provisioning profile that includes all previously registered devices, plus any new ones from the new person. You then need to create a new build using the new provisioning profile. Only then will the new person be able to install the app.

Creating an ad hoc build

So far in this book, you’ve used an empty shell as your sample project. In this chapter, you’ll upgrade to the real iOS app, codenamed Emitron, and use that to create an ad hoc build for internal distribution.

Open this chapter’s resources folder and double-click /emitron/Emitron/Emitron.xcodeproj in the starter folder to open the project in Xcode.

You’re not part of the development team, so you don’t have access to the real App ID. Instead, swap it out for the App ID you created in Chapter 3, “Submitting Your First App for Review”.

In Project navigator, click the Emitron project file. Make sure the emitron target is selected and open the Signing & Capabilities tab. Scroll down to Signing (Release) and change Bundle Identifier to the one you registered in the developer portal back in Chapter 3, “Submitting Your First App for Review”.

The next screenshot has com.raywenderlich.raywenderlich as the bundle identifier, but this value will be different, in your case.

Now, open the Build Settings tab. Scroll down to the Signing section. Click Combined below the tabs to see a simpler version of build settings.

This project has two build configurations: Debug and Release. Ad hoc distribution always uses the Release build configuration. For the following three build settings, click the disclosure indicator and only modify the build setting for the Release build configuration.


  1. Change Code Signing Identity to Apple Distribution.
  2. Change Development Team to the team you’re logged into. Your distribution certificate and App ID should both be registered in this team’s developer portal.
  3. Change Provisioning Profile to Emitron Ad Hoc Provisioning Profile, which is the same profile you downloaded and installed earlier.

Verify that your build settings look like those in the next screenshot. Your team name will be different from the one you see.

Select Any iOS Device (arm64) as the build destination, then select Product ▸ Archive from the menu to start the build process.

Once Xcode finishes archiving, it shows you Organizer. The top entry is the archive you just finished building.

Click Distribute App. On the next screen, select Ad Hoc as the method of distribution

On the following screen, leave the default distribution certificate selected. Next to, click the drop-down and select Emitron Ad Hoc Provisioning Profile.

Click Next. On the review screen, click Export. On the modal that appears, give your export a name and a destination. Once saved, open the archive folder to inspect its contents.

For all intents and purposes, the .ipa file is the app. IPA stands for iPhone Package Archive and it contains the app binary and all the resources it needs to run. You can ignore the other three files in the archive folder, for now.

Apple supports two methods for installing ad hoc builds on registered devices: manually and wirelessly.

Installing the app manually

Installing an ad hoc build manually is similar to retrieving a device’s UDID. You need physical access to the device. You also need to connect the device to a computer.

You can either borrow the registered device and do it yourself or you can distribute the IPA along with some instructions. Since the IPA is just a file, you can share it as you would any other file: by attaching it to an email, copying it to a USB drive, uploading it to Dropbox and so on.

Regardless of how you choose to share the IPA, it’s worth re-emphasizing that only registered devices can install ad hoc IPAs. A common source of frustration is sharing an ad hoc build to someone who doesn’t show up in the list of registered UDIDs, so make sure you’re clear on who’s in and who’s out.

Installing the IPA with Finder

For this section, imagine you’re on the receiving end of the ad hoc IPA. Plug the device you registered earlier into a Mac running Catalina or newer. Use iTunes instead of Finder if you’re running macOS 10.14 (Mojave) or older.

Open Finder. In the left menu, click your device’s name. Even if you search through every tab on the Device Management screen, you won’t see anything that mentions installing IPAs. So how do you do it?

Much like finding a UDID, installing an IPA is a bit of a hidden feature. Simply drag raywenderlich.ipa onto the device management screen. Once a green plus icon appears, drop the file to begin installation.

Unlock your device after installation completes. Tap the new raywenderlich icon on your home screen to verify that you can launch the app without any issues.

If the app finished copying but you can’t find it on your home screen, try restarting your device. There’s a caching issue that affects IPA installations on devices running iOS 14.

Note: Similar to retrieving a UDID, you can also install an IPA manually using Xcode, Xcode Server or Apple Configurator 2.

You’ve learned how to collect a device’s UDID, how to register it on the developer portal, how to create an ad hoc provisioning profile and how to install an ad hoc IPA manually using Finder. It all technically works but there’s one problem: Your internal users have to jump over hurdle after hurdle just to try out your internal builds.

When you’re using ad hoc builds to gather feedback, that kind of friction directly translates to low usage and little feedback. Luckily, there’s one more thing you can try: Apple also supports over the air (OTA) internal distribution.

Installing the app wirelessly

You can think of OTA internal distribution as a bespoke App Store that you build and maintain yourself. You handle uploading builds, hosting the IPAs and creating the App Store front end. All a user has to do is point a mobile web browser to your web page and click a link to begin the download.

OTA distribution requires an HTTPS-enabled web server. Setting one up is out of scope for this book, so this section uses GitHub Pages for demonstration. However, you should use your own web server whenever possible.

GitHub Pages is a free way to host your own website. GitHub statically generates and hosts a website based on the files you push to a GitHub repo. All you need is a GitHub account and some basic knowledge of Git.

To get started, create a new repository in GitHub named, where USERNAME is your GitHub user name. You can read more in GitHub’s guide, Getting Started with Github Pages: Before moving on, make sure you can access your web page at

Exporting for OTA distribution

Go back to Xcode’s Organizer window. Select the last archive you built. Click Distribute, select Ad Hoc as the method of distribution and click Next.

On the second screen, select the same options as before, except this time also click the checkbox next to Include manifest for over-the-air installation.

The next screen asks you to fill out three different URLs. The first one specifies the location of the IPA on your web server. The second and third URL specify the location of images of different sizes.

Fill out the URL fields like this:

Don’t forget to replace USERNAME with your GitHub username.

Click Next. Specify the same distribution certificate and provisioning profile as before. Click Next and save the archive to your computer.

The archive folder is almost identical to the one from earlier in the chapter, except now it also includes a file named manifest.plist. You need to host this XML file alongside the IPA so iOS can find, download and install the internal build.

Building a web page for OTA distribution

If you followed the Github Pages guide, you should have an HTTPS-enabled website at powered by a folder on your computer. Navigate to this folder and create a folder named raywenderlich.

Next, copy the images folder from this chapter’s resources into the new raywenderlich folder. Also copy raywenderlich.ipa and manifest.plist into the same folder.

Now, open your favorite text editor and create a file named index.html. Save it in the raywenderlich folder and add the following HTML:

<html lang="en-US">
      <meta charset="UTF-8">
      <title>Internal Ad hoc Builds</title>
      <h1>raywenderlich iOS app</h1>
      <h2>Version 1.06 (Ad hoc)</h2>
          Publish Date: Dec 27, 2020
          <a href="itms-services://?action=download-manifest&url=">Install Build</a>
          <a href="">Release Notes</a>

Replace USERNAME in the second <li> tag with your GitHub username.

This short HTML snippet is a simple, yet complete, webpage. It lists out build details for version 1.06, along with a link to download the app (second <li> item) and a link to the release notes (third <li> item).

The most important piece is the URL in the second list item: itms-services://?action=download-manifest&url=

This special URL uses the custom scheme itms-services instead of https. iOS intercepts this URL scheme and looks for the manifest file specified in the URL parameter url. The manifest at this location then tells iOS where to find the IPA.

When you’re done, the structure of the raywenderlich folder should look like this:

Time to test if your internal App Store works! Commit and push your changes to GitHub, then wait a few minutes for GitHub to publish your new webpage.

Next, open in mobile Safari on your registered device, replacing USERNAME with your GitHub username.

It’s not the most avant-garde web design in the world, but it gets the job done. Click Install Build to install the internal ad hoc build.

Distributing builds with third-party services

OTA wireless distribution is easier than distributing and installing IPAs manually. You simplified everything for the end-user, but at the price of making more work for yourself. Now, you have to maintain a web server. If you add features over time, you can quickly find yourself supporting a non-trivial web app.

Enterprising readers might see an opportunity. Doesn’t everyone have the same problem? Couldn’t you create a web app to help others distribute their IPAs internally?

That’s exactly what started happening over a decade ago. Two of the most popular third-party OTA solutions were TestFlight and HockeyApp. Apple acquired TestFlight in 2014. Likewise, Microsoft acquired HockeyApp in 2014 and integrated it into Visual Studio App Center, or App Center for short.

In the following section, you’ll use App Center to see how OTA distribution works with a third-party solution.

OTA distribution with AppCenter

The first thing you need to do is register for a new App Center account. Head to on your browser.

You can register and log in with the same GitHub account from the previous section or you can register using an existing Facebook or Google account. If none of those options suit you, you can also create a new Microsoft account.

After successfully logging in, click Add new app. Fill out the details for the raywenderlich app.

Under App name, type raywenderlich. Under Release Type, click the drop-down and select Alpha. You can leave OS and Platform set to the defaults.

Click Add New App. In the left menu, click Distribute. Next, click New Release.

On the following screen, click Upload .ipa file and select the .ipa file from earlier.

App Center guides you through a five-step process to finalize your internal release. Don’t worry, you’re almost done. Here are the settings you need to choose:

  1. Notes: You can optionally add release notes, which will show up in App Center and in your build notifications. Leave the field blank for now. Click Next.
  2. Destinations: Select the default Collaborators distribution group, which currently only includes you. The users in your distribution group should always match the registered devices in the developer portal. Click Next.
  3. Devices: You don’t need to do anything in this step. Click Next.
  4. Review: Review your release’s details. Click Distribute.

Notifications are one of the many benefits you get with a third-party service like App Center. Check the email you used for your new account — App Center notified you of the new release.

You might be wondering why the button says Add device. Third-party services like App Center try to be helpful by offering device management separate from Apple’s developer portal. As long as you know which devices you registered, you don’t need this feature.

Open this email on your registered device. Tap Add device, which takes you to App Center. Log in and dismiss the modal that asks if you want to register your device. Tap emitron under My Apps.

Tap Install to begin downloading the build. An alert shows up telling you that App Center would like to install Emitron. Tap Install.

The download begins and you soon have the raywenderlich app installed on your registered device.

Getting internal builds via App Center might not seem like a significant upgrade from your GitHub Pages OTA web page. However, App Center actually did a lot on your behalf:

  • Authentication & authorization: Distributing internal builds on a publicly-accessible web page is generally not a good idea. A bad actor could break your code signature, run your IPA on jailbroken devices or disassemble your binary to inspect your code. A third-party service like App Center limits the distribution of your ad hoc builds to users you trust.
  • Build notifications: Every user in a distribution group receives an email notification whenever you publish a new build. There’s no need to handcraft emails and attach IPAs anymore. Integrating with App Center’s iOS SDK also lets you receive in-app build notifications. For more details, refer to the official documentation:
  • Zero maintenance: You don’t have to write or maintain any HTML. App Center is a fully-featured web app that can accommodate diverse workflows without having to write or maintain any code yourself.

App Center’s API, which you didn’t get to see in this chapter, is also one of App Center’s primary benefits. You can use App Center’s API to programmatically upload builds as part of your build pipeline. Refer to Chapter 14, “Continuous Integration”, to learn more about setting up your build pipeline.

Choosing a distribution method

You’ve now seen three different methods to distribute ad hoc internal builds: manually, wirelessly with your own solution and wirelessly with a third-party service. It’s tempting to think that third-party services always win on features.

That’s not necessarily the case, however. Each distribution method has advantages and disadvantages. Understanding how ad hoc distribution works lets you make an informed decision based on your particular use case.

For example, if you’re building a side project and don’t need internal testing, managing a third-party service is overkill. You might be better off sharing one-off IPAs manually, should you ever need to.

If you’re deciding between different internal distribution options, wait until you read Chapter 6, “TestFlight”, before finalizing your decision. TestFlight is tightly integrated with Apple’s system. It doesn’t offer as much flexibility as ad hoc distribution, but it could be a good fit for your app.

Key points

  • Apple supports several internal distribution methods: personal team, ad hoc, TestFlight, in-house and Custom Apps.
  • Ad hoc distribution lets you distribute internal builds to a maximum of 100 registered devices per device family per year.
  • You can register a device in Apple’s developer portal using the device’s UDID.
  • Generating an ad hoc provisioning profile requires an App ID, a distribution certificate and a list of registered UDIDs.
  • You can distribute an ad hoc build by manually sharing the .ipa file with registered users. They can install it by connecting a registered device to a Mac and using Finder, Xcode or Apple Configurator 2.
  • You can also distribute an IPA wirelessly by hosting it yourself on an HTTPS-enabled web server. You need a manifest .plist specifying the location of the IPA on the server, as well as a special itms-services URL.
  • Third-party services like App Center and Firebase can make OTA distribution easier by providing authentication, build notifications and API integrations.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.