How to Publish a .NET MAUI App to Google Play and the Apple App Store

Step-by-step guide to publishing your .NET MAUI app to Google Play and the Apple App Store in 2026. Covers keystore signing, provisioning profiles, privacy manifests, and store console setup with .NET 10.

You've built a polished .NET MAUI app, tested it on emulators and devices, and now it's time to ship it to real users. Publishing to Google Play and the Apple App Store is one of the most frequently asked questions in the .NET MAUI community — and honestly, for good reason. The process involves code signing, provisioning profiles, privacy manifests, store console configuration, and platform-specific build commands that are surprisingly easy to get wrong on the first try.

This guide walks you through every step of publishing a .NET MAUI app to both stores in 2026, using .NET 10 and the latest tooling. Whether this is your first release or you're migrating from Xamarin.Forms, you'll find concrete commands, project file snippets, and a pre-publication checklist that eliminates the guesswork.

Pre-Publication Checklist

Before you touch any store console, make sure your project meets these baseline requirements. Trust me — skipping any of them will cost you time later.

Application Identifier

Your application identifier (also called package name on Android and bundle identifier on iOS) must be globally unique. It follows the reverse-domain convention and is set in your .csproj file:

<PropertyGroup>
  <ApplicationId>com.yourcompany.yourapp</ApplicationId>
  <ApplicationDisplayVersion>1.0.0</ApplicationDisplayVersion>
  <ApplicationVersion>1</ApplicationVersion>
</PropertyGroup>

Important: Once your app is published, you can't change the ApplicationId without creating an entirely new store listing. Choose it carefully.

App Icon and Splash Screen

.NET MAUI handles icon and splash screen generation from a single SVG source, which is genuinely one of the nicer parts of the framework. Verify that your project file references them correctly:

<ItemGroup>
  <MauiIcon Include="Resources\AppIcon\appicon.svg"
            ForegroundFile="Resources\AppIcon\appiconfg.svg"
            Color="#512BD4" />
  <MauiSplashScreen Include="Resources\Splash\splash.svg"
                     Color="#FFFFFF"
                     BaseSize="128,128" />
</ItemGroup>

On Android 12 and above, the splash icon gets displayed inside a circular mask, so keep important artwork within the center two-thirds of the canvas. After changing icons, do a full clean-and-rebuild — cached resources often cause stale icons on devices, and that's a headache you don't need.

Version Numbers

Both stores require incremented version numbers for every new upload:

  • ApplicationDisplayVersion (e.g., 1.2.0) is the human-readable version shown to users.
  • ApplicationVersion (e.g., 3) is a monotonically increasing integer used internally by the stores. Google Play and Apple will reject uploads if this number isn't higher than the previous build.

Release Configuration Sanity Check

Make sure your Release build configuration disables debug symbols, enables linking, and sets the correct runtime identifier. A quick way to verify:

dotnet build -f net10.0-android -c Release --no-incremental
dotnet build -f net10.0-ios -c Release --no-incremental

Fix any warnings or errors before proceeding. Trimming and AOT compilation in Release mode can surface code that works fine in Debug but fails when the linker removes seemingly unused types. I've lost a few hours to this one myself.

Publishing to Google Play (Android)

Android publishing revolves around two things: signing your app with a private keystore and uploading a signed Android App Bundle (AAB) to the Google Play Console. Let's break it down.

Step 1: Create a Keystore

A keystore is a cryptographic certificate database that Android uses to verify the identity of the app publisher. Create one using keytool from the JDK:

keytool -genkey -v \
  -keystore myapp.keystore \
  -alias myappkey \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000

Set the validity to a high number (in days). Google Play will reject keystores that expire too soon, and if you lose this file you'll never be able to push updates to the same listing.

Seriously — store it in a secure location and back it up. This isn't one of those things you can just regenerate.

Step 2: Configure Signing in the Project File

Rather than passing signing parameters on every CLI invocation, define them conditionally in your .csproj:

<PropertyGroup Condition="$(TargetFramework.Contains('-android')) and
               '$(Configuration)' == 'Release'">
  <AndroidKeyStore>true</AndroidKeyStore>
  <AndroidSigningKeyStore>myapp.keystore</AndroidSigningKeyStore>
  <AndroidSigningKeyAlias>myappkey</AndroidSigningKeyAlias>
  <AndroidSigningKeyPass>env:ANDROID_SIGNING_PASSWORD</AndroidSigningKeyPass>
  <AndroidSigningStorePass>env:ANDROID_SIGNING_PASSWORD</AndroidSigningStorePass>
</PropertyGroup>

The env: prefix reads the password from an environment variable, so it never appears in source control or build logs. One gotcha worth mentioning: for AAB builds, use the file: prefix instead, pointing to a text file containing the password, because the env: prefix isn't supported when the package format is AAB.

Step 3: Build the Signed AAB

Run the publish command from your project directory:

dotnet publish -f net10.0-android -c Release

This produces two AAB files in bin/Release/net10.0-android/publish/. The one with -Signed in the filename is what you upload. If you need an APK for ad-hoc testing, both formats are generated by default in release builds.

Step 4: Set Up Google Play Console

  1. Sign in to the Google Play Console with your developer account ($25 one-time registration fee).
  2. Click Create app and fill in the app name, default language, app type (app or game), and whether it's free or paid.
  3. Complete the Store listing: short description (80 chars max), full description (4,000 chars max), app icon (512x512 PNG), feature graphic (1024x500), and at least 2 phone screenshots (1080x1920 recommended, JPEG or 24-bit PNG, no alpha, max 8 MB each).
  4. Fill in the Content rating questionnaire, Target audience, and Data safety sections. Google won't approve your listing without these.

Step 5: Upload and Release

  1. Navigate to Release > Production (or a testing track for beta testing) and click Create new release.
  2. Upload the signed AAB file.
  3. Google Play will analyze the bundle and report any issues. Common ones include missing targetSdkVersion (must be API 35 or higher for new apps in 2026) and unsigned bundles.
  4. Add release notes and click Review release, then Start rollout.

The first upload must always be done manually through the console. After that, you can automate subsequent uploads via the Google Play Developer Publishing API or your CI/CD pipeline.

Publishing to the Apple App Store (iOS)

iOS publishing is — how do I put this diplomatically — more involved. Apple requires provisioning profiles, distribution certificates, and a privacy manifest. Every step here assumes you have an active Apple Developer Program membership ($99/year).

Step 1: Create a Distribution Certificate

  1. Open Keychain Access on your Mac and go to Certificate Assistant > Request a Certificate from a Certificate Authority. Save the certificate signing request (CSR) to disk.
  2. In the Apple Developer Portal, go to Certificates, Identifiers & Profiles. Click the + button and select Apple Distribution.
  3. Upload your CSR file. Apple will generate a .cer file. Download it and double-click to install it in Keychain Access.
  4. Export the certificate as a .p12 file for use in CI/CD pipelines later.

Step 2: Register an App ID and Create a Provisioning Profile

  1. In the Apple Developer Portal, go to Identifiers and click +. Select App IDs, choose App, and enter your bundle identifier (this must match ApplicationId in your .csproj).
  2. Enable any capabilities your app uses (Push Notifications, Sign in with Apple, etc.). These must also be declared in your Entitlements.plist.
  3. Go to Profiles, click +, select App Store Distribution, choose your App ID and distribution certificate, then download the resulting .mobileprovision file.
  4. Double-click the provisioning profile to install it, or drag it into Xcode.

Step 3: Add the Privacy Manifest

Since May 2024, Apple requires every iOS app to include a PrivacyInfo.xcprivacy file. Here's the thing — the .NET runtime itself uses required-reason APIs (file timestamps, disk space, user defaults, system boot time), so your app will be rejected without this manifest even if your own code doesn't call those APIs directly.

Create a file named PrivacyInfo.xcprivacy in Platforms/iOS/:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>NSPrivacyAccessedAPITypes</key>
  <array>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <array>
        <string>C617.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <array>
        <string>35F9.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <array>
        <string>E174.1</string>
      </array>
    </dict>
    <dict>
      <key>NSPrivacyAccessedAPIType</key>
      <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
      <key>NSPrivacyAccessedAPITypeReasons</key>
      <array>
        <string>CA92.1</string>
      </array>
    </dict>
  </array>
  <key>NSPrivacyCollectedDataTypes</key>
  <array/>
  <key>NSPrivacyTracking</key>
  <false/>
</dict>
</plist>

Then reference it in your .csproj:

<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier(
  '$(TargetFramework)')) == 'ios'">
  <BundleResource Include="Platforms\iOS\PrivacyInfo.xcprivacy"
                  LogicalName="PrivacyInfo.xcprivacy" />
</ItemGroup>

If your app or any third-party SDK collects user data, you'll also need to declare that in the NSPrivacyCollectedDataTypes array. Make sure you review every NuGet package for its own privacy manifest.

Step 4: Build the IPA

On a Mac (or a cloud-hosted macOS agent), run:

dotnet publish -f net10.0-ios -c Release \
  -p:ArchiveOnBuild=true \
  -p:CodesignKey="Apple Distribution: Your Name (TEAMID)" \
  -p:CodesignProvision="YourApp AppStore Profile"

This produces an .ipa file in bin/Release/net10.0-ios/publish/. The ArchiveOnBuild=true flag tells the build to create a distributable archive rather than a development build.

If you prefer Visual Studio for Mac or Windows (with a paired Mac), you can use the Archive menu option and then the Distribute dialog to sign and save or upload the IPA directly.

Step 5: Create an App Record in App Store Connect

  1. Sign in to App Store Connect and click My Apps > + to create a new app.
  2. Select the platform (iOS), enter the app name, choose your bundle ID (must match your project), and set the primary language and SKU.
  3. Fill in the App Information, Pricing, and App Privacy sections.
  4. Under the App Store tab, add screenshots (1290x2796 for 6.7-inch, 1242x2208 for 5.5-inch), a description, keywords, and a support URL.

Step 6: Upload and Submit

You have three upload options:

  • Transporter (available on macOS and Windows): Open Transporter, sign in with your Apple ID, drag in the .ipa, and click Deliver. Transporter validates the package and highlights any issues before uploading.
  • Visual Studio Distribute dialog: Select Upload to Store, enter your Apple ID and an app-specific password, and Visual Studio handles the rest.
  • xcrun altool (CLI): xcrun altool --upload-app -f YourApp.ipa -t ios -u [email protected] -p @keychain:AC_PASSWORD

After uploading, the build appears under the TestFlight tab in App Store Connect. You can distribute it to internal testers immediately or add external testers after a brief Apple review. When you're ready for production, select the build under the App Store tab and click Submit for Review.

Common Pitfalls and Troubleshooting

So, you followed all the steps and something still went wrong? Don't worry — here are the issues that trip up nearly everyone.

Android: "Keystore was tampered with, or password was incorrect"

This usually means the alias or password in your publish command doesn't match what was used during keytool -genkey. Verify with:

keytool -list -v -keystore myapp.keystore

Double-check the alias name and try re-entering the password. It's almost always a typo.

Android: "Upload failed — your APK or AAB needs to target API level 35 or higher"

Google Play requires new apps to target the latest SDK. In your .csproj, ensure you have:

<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-android'))">
  21.0
</SupportedOSPlatformVersion>

And verify that the Android SDK build tools for API 35+ are installed via the SDK Manager. This catches a lot of people off guard after a fresh install.

iOS: "No matching provisioning profile found"

Ensure the bundle identifier in your .csproj exactly matches the App ID registered in the Apple Developer Portal. Also confirm the provisioning profile is installed on the build machine. On macOS, run:

ls ~/Library/MobileDevice/Provisioning\ Profiles/

If the profile is missing, re-download it from the Developer Portal or use fastlane match to automate profile management.

iOS: "App Store rejection — missing privacy manifest"

Even a blank .NET MAUI app uses required-reason APIs from the .NET runtime. Make sure the PrivacyInfo.xcprivacy file exists in Platforms/iOS/ and is referenced as a BundleResource in your .csproj. After adding it, do a clean rebuild to ensure it's included in the archive. This one trips up a lot of developers — you'd think a framework-level requirement would be handled automatically, but here we are.

Both Platforms: Linker Removes Necessary Code

Release builds use the IL trimmer to reduce app size. If your app crashes at runtime but works perfectly in Debug, you likely need to preserve types the linker is stripping. Add a TrimmerRootDescriptor or use the [DynamicallyAccessedMembers] attribute on reflection-heavy code.

For a quick workaround, set <PublishTrimmed>false</PublishTrimmed>, but be aware this increases your app size significantly.

Post-Publication: What Comes Next

Getting your first version live is just the beginning. Here are a few things worth setting up early:

  • Crash reporting: Integrate App Center, Sentry, or Firebase Crashlytics so you can catch production crashes before users leave bad reviews. (Nothing kills momentum like one-star ratings you didn't see coming.)
  • CI/CD automation: Wire up GitHub Actions or Azure DevOps to build, sign, and upload on every tagged commit. Our CI/CD for .NET MAUI guide covers this in detail.
  • Staged rollouts: Google Play supports percentage-based rollouts. Use them to catch issues before they affect your entire user base.
  • TestFlight for beta testing: Distribute pre-release builds to up to 10,000 external testers through TestFlight before submitting to the App Store.

Frequently Asked Questions

Can I build a .NET MAUI iOS app on Windows?

You need a Mac somewhere in the pipeline. Visual Studio on Windows can pair with a remote Mac over the network for iOS builds, and cloud CI services like GitHub Actions and Azure DevOps offer macOS-hosted agents. But there's no way around it — you can't fully build and sign an iOS app without macOS and Xcode.

Do I need separate developer accounts for Google Play and the App Store?

Yes. Google Play requires a Google Developer account ($25 one-time fee). The Apple App Store requires an Apple Developer Program membership ($99/year). They're completely independent accounts managed through separate consoles.

What is the difference between an APK and an AAB?

An APK (Android Package) is a self-contained installation file. An AAB (Android App Bundle) is an upload format that lets Google Play generate device-optimized APKs, reducing download sizes for users. Google Play requires AABs for new apps. The .NET MAUI publish command generates both formats by default in release builds.

How do I update my app after the first release?

Increment the ApplicationVersion integer and optionally bump ApplicationDisplayVersion. Build and sign a new AAB or IPA, then upload it through the respective store console or your CI/CD pipeline. For Google Play, subsequent uploads can be automated via the Developer Publishing API. For iOS, use Transporter or your pipeline to push to TestFlight and then promote to production.

Why was my iOS app rejected even though it works perfectly?

The most common rejection reasons for .NET MAUI apps in 2026 are a missing or incomplete privacy manifest, incorrect or missing screenshot dimensions, insufficient app description, and missing metadata like a privacy policy URL. Apple also rejects apps that crash during review, so always test the exact IPA you upload on a physical device before submission.

About the Author Editorial Team

Our team of expert writers and editors.