<!-- source: https://docs.codemagic.io/yaml-quick-start/building-a-dotnet-maui-app/ -->
<!-- last modified: 2025-05-26 -->

# .NET MAUI apps

> How to build a .NET MAUI app with codemagic.yaml

This guide will illustrate all of the necessary steps to successfully build and publish a .NET MAUI app with Codemagic. It will cover the basic steps such as build versioning, code signing and publishing.

You can find a complete project showcasing these steps in our [sample projects repository](https://github.com/codemagic-ci-cd/codemagic-sample-projects/tree/main/dotnet-maui/dotnet-maui-android-ios/).

## Adding the app to Codemagic
The apps you have available on Codemagic are listed on the Applications page. Click **Add application** to add a new app.

1. If you have more than one team configured in Codemagic, select the team you wish to add the app to.
2. Connect the repository where the source code is hosted. Detailed instructions that cover some advanced options are available [here](../../getting-started/adding-apps).
3. Select the repository from the list of available repositories. Select the appropriate project type.
4. Click **Finish: Add application**


## Creating codemagic.yaml


In order to use `codemagic.yaml` for build configuration on Codemagic, it has to be committed to your repository. The name of the file must be `codemagic.yaml` and it must be located in the root directory of the repository. Detailed explanation can be found [here](../yaml/yaml-getting-started).



> 
> **Tip**
> You can find codemagic.yaml examples in [Codemagic Sample Projects](https://github.com/codemagic-ci-cd/codemagic-sample-projects/) repository.
> 



If you prefer to write your `codemagic.yaml` file from scratch, you can start with this minimal configuration.


```yaml

workflows:
    sample-workflow:
        name: Codemagic Sample Workflow
        max_build_duration: 120
        instance_type: mac_mini_m2

```





> 
> **Tip**
> You can have more than one workflow in the same `codemagic.yaml` file. If you are building for both the Android and iOS, simply enter both workflows as:
> 



```yaml

workflows:
    android-workflow-id:
        name: Android Sample Workflow
        # .......    
        # .......
        # .......  
    ios-workflow-id:
        name: iOS Sample Workflow
        # ......

```



Scan for the `codemagic.yaml` file by selecting a branch to scan and clicking the **Check for configuration** file button at the top of the page. Note that you can have different configuration files in different branches.


## Install .NET SDK
In order to build .NET apps in Codemagic, you first need to install the .NET SDK. The easiest way to do it is by downloading and running the official Install script.


```yaml

  environment:
    vars:
      DOTNET_PATH: $CM_BUILD_DIR/dotnet
      DOTNET: $CM_BUILD_DIR/dotnet/dotnet
  scripts:
    - name: Install .NET SDK
      script: | 
        wget https://dot.net/v1/dotnet-install.sh
        chmod +x dotnet-install.sh
        ./dotnet-install.sh --install-dir $DOTNET_PATH

```


## Install MAUI
Once you have the .NET SDK installed, you can proceed to install the required .NET workloads.





### Option: AndroidIt is recommended to create a dedicated App Store Connect API key for Codemagic in [App Store Connect](https://appstoreconnect.apple.com/access/integrations/api). To do so:

1. Log in to App Store Connect and navigate to **Users and Access > Integrations >> App Store Connect API**.
2. Click on the + sign to generate a new API key.
3. Enter the name for the key and select an access level. We recommend choosing `App Manager` access rights, read more about Apple Developer Program role permissions [here](https://help.apple.com/app-store-connect/#/deve5f9a89d7).
4. Click **Generate**.
5. As soon as the key is generated, you can see it added to the list of active keys. Click **Download API Key** to save the private key for later. Note that the key can only be downloaded once.



> 
> Take note of the **Issuer ID** above the table of active keys as well as the **Key ID** of the generated key as these will be required when setting up the Apple Developer Portal integration in the Codemagic UI.
> 





#### Adding the App Store Connect API key to Codemagic

1. Open your Codemagic Team settings, go to **Team integrations** > **Developer Portal** > **Manage keys**.
2. Click the **Add key** button.
3. Enter the `App Store Connect API key name`. This is a human readable name for the key that will be used to refer to the key later in application settings.
4. Enter the `Issuer ID` and `Key ID` values.
5. Click on **Choose a .p8 file** or drag the file to upload the App Store Connect API key downloaded earlier.
6. Click **Save**.

#### Adding the code signing certificate




Codemagic lets you upload code signing certificates as PKCS#12 archives containing both the certificate and the private key which is needed to use it. When uploading, Codemagic will ask you to provide the certificate password (if the certificate is password-protected) along with a unique **Reference name**, which can then be used in the `codemagic.yaml` configuration to fetch the specific file.





%!s(<nil>)

### Option: Upload certificate



1. Open your Codemagic Team settings, go to  **codemagic.yaml settings** > **Code signing identities**.
2. Open **iOS certificates** tab.
3. Upload the certificate file by clicking on **Choose a .p12 or .pem file** or by dragging it into the indicated frame.
4. Enter the **Certificate password** and choose a **Reference name**.
5. Click **Add certificate**






### Option: Generate new certificate



If you have added the **App Store Connect API key** to Codemagic, you can also generate a new `Apple Development` or `Apple Distribution` certificate.

1. Open your Codemagic Team settings, go to  **codemagic.yaml settings** > **Code signing identities**.
2. Open **iOS certificates** tab.
3. Click **Generate certificate**.
4. Provide a **Reference name** for the certificate.
5. Choose the **Certificate type**.
6. Select the **App Store Connect API key** to use.
7. Click **Create certificate**.

Once the certificate has been created, Codemagic will allow you to download the certificate and provides the password for it.

After downloading, please follow the steps in the **Upload certificate** tab to upload the certificate to Codemagic.



> 
> **Note**: The certificate can be downloaded **only once**, right after creating it.
> 


<br>


> 
> **Note**: Apple limits the number of `Apple Distribution` certificates to 3. If you have already reached the maximum number of certificates, the following error will be displayed:
> 
> 
> ```bash
> 
> There is a problem with the request entity - You already have a current Distribution certificate or a pending certificate request.
> 
> ```
> 
> 
> Similar errors can also arise in rarer cases with `Apple Development` certificates. To resolve the error, either remove some old certificate from the Apple Developer Portal or upload an existing certificate manually.
> 








### Option: Fetch from Developer Portal



Existing signing certificates previously generated by Codemagic can be automatically fetched from Apple Developer Portal based on your team's App Store Connect API key.

Fetching a certificate that was not generated by Codemagic is not possible because each certificate is linked with a private signing key to which Codemagic has no access.

1. Open your Codemagic Team settings, go to  **codemagic.yaml settings** > **Code signing identities**.
2. Open **iOS certificates** tab.
3. Click **Fetch certificate**.
4. Select a certificate from the **Development certificates** or **Distribution certificates** list.
5. Click **Fetch selected**.








#### Adding the provisioning profile



Codemagic allows you to upload a provisioning profile to be used for the application or to fetch a profile from the Apple Developer Portal.

The profile's type, team, bundle id, and expiration date are displayed for each profile added to Code signing identities. Furthermore, Codemagic will let you know whether a matching code signing certificate is available in Code signing identities (a green checkmark in the **Certificate** field) or not.





### Option: Upload a profile



You can upload provisioning profiles with the `.mobileprovision` extension, providing a unique **Reference name** is required for each uploaded profile.

1. Open your Codemagic Team settings, go to  **codemagic.yaml settings** > **Code signing identities**.
2. Open **iOS provisioning profiles** tab.
3. Upload the provisioning profile file by clicking on **Choose a .mobileprovision file** or by dragging it into the indicated frame.
4. Enter the **Reference name** for the profile.
5. Click **Add profile**.





> 
> **Note:** If your app contains app extensions, an additional provisioning profile is required for each extension. Codemagic will use the bundle identifier to find the relevant provisioning profiles. If your bundle identifier is `com.example.app`, the matching profiles are the ones with `com.example.app` and `com.example.app.*` as bundle identifier.
> 





### Option: Fetch from Developer Portal



You can automatically fetch the provisioning profiles from the Apple Developer Portal based on your team's App Store Connect API key. The bundle identifier is listed for every available profile along with it's name.

The profiles are displayed grouped by category: `Development profiles`, `Ad Hoc profiles`, `App Store profiles`, and `Enterprise profiles`. For each selected profile, it is necessary to provide a unique **Reference name**, which can be later used in `codemagic.yaml` to fetch the profile.

1. Open your Codemagic Team settings, go to  **codemagic.yaml settings** > **Code signing identities**.
2. Open **iOS provisioning profiles** tab.
3. Click **Fetch profiles**
4. Select the desired profile(s) and enter a **Reference name** for each one.
5. Click **Download selected**. (scroll down if necessary)







<br>


> 
> **Note:** When you make essential changes to a provisioning profile, such as modifying the app ID, adding/removing capabilities from the profile identifier, or changing the certificates assigned to that profile, the provisioning profile becomes invalid. In such situations, you need to generate a new provisioning profile with these updates and then re-upload it to Codemagic.
> 

 



#### Referencing certificates and profiles in codemagic.yaml

To fetch all uploaded signing files matching a specific distribution type and bundle identifier during the build, define the `distribution_type` and `bundle_identifier` fields in your `codemagic.yaml` configuration. Note that it is necessary to configure **both** of the fields.


```yaml

workflows:
  ios-workflow:
    name: iOS Workflow 
    # ....
    environment:
      ios_signing:
        distribution_type: app_store # or: ad_hoc | development | enterprise
        bundle_identifier: com.example.id

```




> 
> **Note:** If you are publishing to the **App Store** or you are using **TestFlight**  to distribute your app to test users, set the `distribution_type` to `app_store`. 
> 
> When using a **third party app distribution service** such as Firebase App Distribution, set the `distribution_type` to `ad_hoc`
> 



When defining the bundle identifier `com.example.id`, Codemagic will fetch any uploaded certificates and profiles matching the extensions as well (e.g. `com.example.id.NotificationService`).






## Configure scripts to build the app
Add the following scripts to your `codemagic.yaml` file in order to prepare the build environment and start the actual build process.
In this step you can also define the build artifacts you are interested in. These files will be available for download when the build finishes. For more information about artifacts, see [here](../yaml/yaml-getting-started/#artifacts).


### Build versioning

If you are going to publish your app to App Store Connect or Google Play, each uploaded artifact must have a new version satisfying each app store’s requirements. Codemagic allows you to easily automate this process and increment the version numbers for each build. For more information and details, see [here](../configuration/build-versioning).

In .NET MAUI, app version and build number are determined by `ApplicationDisplayVersion` and `ApplicationVersion` properties set either in the `.csproject` file or passed via command line. Using Codemagic CLI, you can easily fetch the latest published build version from the respective store and automatically increment it for each new build.

### Code signing
In order to code sign the app, the appropriate properties must be configured either in the `csproject` file or passed via command line.

#### Android
When using the default Codemagic code signing method (codesigning identities), the required environment variables will already be available for use in command line arguments.

#### iOS
When using the default Codemagic code signing method (codesigning identities), the correct certificate and provisioning profile will be automatically prepared. However, since the dotnet build command requires referencing the signing certificate and the provisioning profile by name, two additional script commands are necessary to get those values. Alternatively, you can enter the correct values as strings manually.

### Build script example
This is an example of the final script, including build versioning, code signing and the app building steps.





### Option: Android
Codemagic offers a wide array of options for app publishing and the list of partners and integrations is continuously growing. For the most up-to-date information, check the guides in the **Configuration > Publishing** section of these docs.
To get more details on the publishing options presented in this guide, please check the [Email publishing](../yaml-publishing/email), the [Google Play Store](../yaml-publishing/google-play) publishing and the [App Store Connect](../yaml-publishing/app-store-connect).

#### Email publishing
If the build finishes successfully, release notes (if passed), and the generated artifacts will be published to the provided email address(es). If the build fails, an email with a link to build logs will be sent.

If you don’t want to receive an email notification on build success or failure, you can set `success` to `false` or `failure` to `false` accordingly.

```yaml

workflows:
  sample-workflow-id:
    environment: 
      # ...
    scripts: 
      # ...
    publishing: 
      email:
        recipients:
          - user_1@example.com
          - user_2@example.com
        notify:
          success: true
          failure: false

```


#### Publishing to Google Play and App Store




%!s(<nil>)

### Option: Android
Publishing apps to Google Play requires you to set up a service account in Google Play Console and save the content of the `JSON` key file to a secret environment variable as explained above in **Android Build Versioning** steps 1-5.
Configuring Google Play publishing is simple as you only need to provide credentials and choose the desired track. If the app is in `draft` status, please also include the `submit_as_draft: true` or promote the app status in Google Play.

```yaml

react-native-android:
  # ... 
  publishing:
    # ...
    google_play:
      credentials: $GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS
      track: internal
      submit_as_draft: true

```






%!s(<nil>)

### Option: iOSCodemagic enables you to automatically publish your iOS or macOS app to [App Store Connect](https://appstoreconnect.apple.com/) for beta testing with [TestFlight](https://developer.apple.com/testflight/) or distributing the app to users via App Store. Codemagic uses the **App Store Connect API key** for authenticating communication with Apple's services. You can read more about generating an API key from Apple's [documentation page](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api).

Please note that:

1. for App Store Connect publishing, the provided key needs to have [App Manager permission](https://help.apple.com/app-store-connect/#/deve5f9a89d7),
2. and in order to submit your iOS application to App Store Connect, it must be code signed with a distribution [certificate](https://developer.apple.com/support/certificates/).

The following snippet demonstrates how to authenticate with and upload the IPA to App Store Connect, submit the build to beta tester groups in TestFlight and configure releasing the app to App Store. See additional configuration options for App Store Connect publishing [here](https://github.com/codemagic-ci-cd/cli-tools/blob/master/docs/app-store-connect/publish.md).



> **Note:** Please note that you will need to create an **app record** in App Store Connect before you can automate publishing with Codemagic. It is recommended to upload the very first version of the app manually. Suppose you have set up an **app record** but have not manually uploaded the app's first version. In that case, manual configuration of the settings must be done on App Store Connect after the build is complete, such as uploading the required screenshots and providing the values for the privacy policy URL and application category. 




```yaml

# Integration section is required to make use of the keys stored in 
# Codemagic UI under Apple Developer Portal integration.
integrations:
  app_store_connect: <App Store Connect API key name>

publishing:
  app_store_connect:
    # Use referenced App Store Connect API key to authenticate binary upload
    auth: integration 

    # Configuration related to TestFlight (optional)

    # Optional boolean, defaults to false. Whether or not to submit the uploaded
    # build to TestFlight beta review. Required for distributing to beta groups.
    # Note: This action is performed during post-processing.
    submit_to_testflight: true 

    # Optional boolean, defaults to false. Set to true to automatically expire 
    # previous build in review or waiting for review in Testflight before
    # submitting a new build to beta review. Expired builds will no longer be available for testers.
    # Note: This action is performed during post-processing.
    expire_build_submitted_for_review: true

    # Specify the names of beta tester groups that will get access to the build 
    # once it has passed beta review.
    beta_groups: 
      - group name 1
      - group name 2
    
    # Configuration related to App Store (optional)

    # Optional boolean, defaults to false. Whether or not to submit the uploaded
    # build to App Store review. Note: This action is performed during post-processing.
    submit_to_app_store: true

    # Optional boolean, defaults to false. Set to true to cancel the previous 
    # submission (if applicable) when submitting a new build to App Store review.
    # This allows automatically submitting a new build for review if a previous submission exists.
    # Note: This action is performed during post-processing.
    cancel_previous_submissions: true
    
    # Optional, defaults to MANUAL. Supported values: MANUAL, AFTER_APPROVAL or SCHEDULED
    release_type: SCHEDULED

    # Optional. Timezone-aware ISO8601 timestamp with hour precision when scheduling
    # the release. This can be only used when release type is set to SCHEDULED.
    # It cannot be set to a date in the past.
    earliest_release_date: 2021-12-01T14:00:00+00:00 
    
    # Optional. The name of the person or entity that owns the exclusive rights
    # to your app, preceded by the year the rights were obtained.
    copyright: 2021 Nevercode Ltd
  
    # Optional boolean. Whether or not to release an App Store version update in phases.
    # With this option turned on, your version update will be released over a 7-day period
    # to a percentage of your users (selected at random by their Apple ID) with automatic
    # updates turned on. Learn more from 
    # https://developer.apple.com/help/app-store-connect/update-your-app/release-a-version-update-in-phases.
    # If not specified, then App Store version default phased release configuration is reused.
    phased_release: true

```





## Conclusion
Having followed all of the above steps, you now have a working `codemagic.yaml` file that allows you to build, code sign, automatically version and publish your project using Codemagic CI/CD.
Save your work, commit the changes to the repository, open the app in the Codemagic UI and start the build to see it in action.


Your final `codemagic.yaml` file should look something like this:


```yaml

workflows:
  maui-ios:
    name: Dotnet MAUI iOS
    max_build_duration: 120
    instance_type: mac_mini_m2
    integrations:
      app_store_connect: codemagic-api
    environment:
      ios_signing:
        distribution_type: app_store
        bundle_identifier: io.codemagic.maui.weather
      vars:
        DOTNET_PATH: $CM_BUILD_DIR/dotnet
        DOTNET: $CM_BUILD_DIR/dotnet/dotnet
        APP_STORE_APPLE_ID: 6444530615
        BUNDLE_ID: "io.codemagic.maui.weather"
    scripts:
      - name: Install dotnet sdk
        script: | 
          wget https://dot.net/v1/dotnet-install.sh
          chmod +x dotnet-install.sh
          ./dotnet-install.sh --install-dir $DOTNET_PATH
      - name: Install MAUI
        script: | 
          $DOTNET nuget locals all --clear 
          $DOTNET workload install ios maui \
            --source https://aka.ms/dotnet6/nuget/index.json \
            --source https://api.nuget.org/v3/index.json      
      - name: Set Info.plist values
        script: | 
          # Automatically fill out the encryption compliance setting

          PLIST=$CM_BUILD_DIR/src/WeatherTwentyOne/Platforms/iOS/Info.plist
          PLIST_BUDDY=/usr/libexec/PlistBuddy
          $PLIST_BUDDY -c "Add :ITSAppUsesNonExemptEncryption bool false" $PLIST
      - name: Set build version and build the app
        script: | 
          LATEST_BUILD_NUMBER=$(app-store-connect get-latest-testflight-build-number "$APP_STORE_APPLE_ID")
          if [ -z $LATEST_BUILD_NUMBER ]; then
            UPDATED_BUILD_NUMBER=$BUILD_NUMBER
          else
            UPDATED_BUILD_NUMBER=$(($LATEST_BUILD_NUMBER + 1))
          fi
          
          CERT_NAME=$(keychain list-certificates | jq -r '.[] | .common_name')
          PROFILE_NAME=$(find ~/Library/MobileDevice/Provisioning\ Profiles -name "*.mobileprovision" -execdir sh -c '/usr/libexec/PlistBuddy -c "print :Name" /dev/stdin <<< $(security cms -D -i {})' \;)
          
          cd src
          $DOTNET publish -f net6.0-ios \
            -c Release \
            -p:BuildIpa=True \
            -p:ApplicationDisplayVersion="1.0.0" \
            -p:ApplicationVersion=$UPDATED_BUILD_NUMBER \
            -p:RuntimeIdentifier=ios-arm64 \
            -p:CodesignKey="$CERT_NAME" \
            -p:CodesignProvision="$PROFILE_NAME" \
            -o ../artifacts
    artifacts:
        - ./artifacts/*.ipa
    publishing:
      app_store_connect:
        auth: integration
    
    
  maui-android:
    name: Dotnet MAUI Android
    max_build_duration: 120
    instance_type: mac_mini_m2
    environment:
      android_signing:
        - codemagic-key
      groups:
        - google_play
      vars:
        DOTNET_PATH: $CM_BUILD_DIR/dotnet
        DOTNET: $CM_BUILD_DIR/dotnet/dotnet
        PACKAGE_NAME: "io.codemagic.maui.weather"
    scripts:
      - name: Install dotnet sdk
        script: | 
          wget https://dot.net/v1/dotnet-install.sh
          chmod +x dotnet-install.sh
          ./dotnet-install.sh --install-dir $DOTNET_PATH
      - name: Install MAUI
        script: | 
          $DOTNET nuget locals all --clear 
          $DOTNET workload install android maui \
            --source https://aka.ms/dotnet6/nuget/index.json \
            --source https://api.nuget.org/v3/index.json      
      - name: Build
        script: | 
          LATEST_BUILD_NUMBER=$(google-play get-latest-build-number --package-name "$PACKAGE_NAME")
          if [ -z $LATEST_BUILD_NUMBER ]; then
            UPDATED_BUILD_NUMBER=$BUILD_NUMBER
          else
            UPDATED_BUILD_NUMBER=$(($LATEST_BUILD_NUMBER + 1))
          fi
          
          cd src
          $DOTNET publish -f net6.0-android \
            -c Release \
            -p:AndroidKeyStore=True \
            -p:AndroidSigningKeyStore=$CM_KEYSTORE_PATH \
            -p:AndroidSigningKeyAlias=$CM_KEY_ALIAS \
            -p:AndroidSigningKeyPass=$CM_KEY_PASSWORD \
            -p:AndroidSigningStorePass=$CM_KEYSTORE_PASSWORD \
            -p:ApplicationVersion=$UPDATED_BUILD_NUMBER \
            -p:ApplicationDisplayVersion="1.0.0" \
            -o ../artifacts
    artifacts:
        - /Users/builder/clone/artifacts/*Signed.aab
    publishing:
      google_play:
        credentials: $GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS
        track: internal
        submit_as_draft: true

```


## Next steps
While this basic workflow configuration is incredibly useful, it is certainly not the end of the road and there are numerous advanced actions that Codemagic can help you with.

We encourage you to investigate [Running tests with Codemagic](../yaml-testing/testing) to get you started with testing, as well as additional guides such as the one on running tests on [Firebase Test Lab](../yaml-testing/firebase-test-lab) or [Registering iOS test devices](../yaml-testing/ios-provisioning).

Documentation on [using codemagic.yaml](../yaml/yaml-getting-started) teaches you to configure additional options such as [changing the instance type](../yaml/yaml-getting-started/#instance-type) on which to build, or configuring builds to be [automatically triggered](https://docs.codemagic.io/yaml/yaml-getting-started/#triggering) on repository events.


%!s(<nil>)