NuGet Packaging for .NET Bindings
Package .NET binding projects as NuGet packages for distribution, with proper native framework embedding and multi-platform support.
When to Use This Skill
- •After binding projects build successfully and you need to distribute them
- •When creating NuGet packages that include native xcframeworks
- •When setting up multi-TFM packaging for iOS + Mac Catalyst
- •When configuring NuGet metadata, versioning, and publishing
Quick Start
1. Configure Package Metadata
Add NuGet metadata to the binding .csproj:
<PropertyGroup> <PackageId>MyCompany.MyFramework.iOS</PackageId> <PackageVersion>1.0.0</PackageVersion> <Title>MyFramework for .NET iOS</Title> <Description>C# bindings for MyFramework xcframework</Description> <Authors>Your Name</Authors> <Copyright>Copyright © 2025 Your Company</Copyright> <PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageProjectUrl>https://github.com/your-org/your-repo</PackageProjectUrl> <PackageIcon>icon.png</PackageIcon> <PackageReadmeFile>README.md</PackageReadmeFile> <PackageTags>ios;macos;maccatalyst;xamarin;dotnet;bindings</PackageTags> <RepositoryUrl>https://github.com/your-org/your-repo</RepositoryUrl> <RepositoryType>git</RepositoryType> </PropertyGroup>
2. Configure Native Framework Embedding
By default, binding projects embed the native framework in the NuGet. This is the recommended approach for distribution:
<PropertyGroup>
<!-- Default: true — framework is embedded in the NuGet package -->
<!-- Set to false only for local development with project references -->
<NoBindingEmbedding>false</NoBindingEmbedding>
</PropertyGroup>
<ItemGroup>
<NativeReference Include="../Frameworks/MyFramework.xcframework">
<Kind>Framework</Kind>
<SmartLink>True</SmartLink>
</NativeReference>
</ItemGroup>
Embedding behavior:
NoBindingEmbedding | NuGet contains native framework? | App must add NativeReference? |
|---|---|---|
false (default) | ✅ Yes — self-contained NuGet | No |
true | ❌ No — smaller NuGet | Yes — app must reference xcframework |
For NuGet distribution, always use NoBindingEmbedding=false (the default) so consumers don't need the xcframework separately.
3. Multi-TFM Configuration
Target both iOS and Mac Catalyst:
<PropertyGroup> <TargetFrameworks>net10.0-ios;net10.0-maccatalyst</TargetFrameworks> <!-- Or include net9.0 for backward compatibility --> <TargetFrameworks>net10.0-ios;net10.0-maccatalyst;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> </PropertyGroup>
The NuGet will contain platform-specific assemblies under:
lib/
├── net10.0-ios/
│ └── MyFramework.Binding.dll
├── net10.0-maccatalyst/
│ └── MyFramework.Binding.dll
├── net10.0-ios/
│ └── MyFramework.Binding.dll
└── net10.0-maccatalyst/
└── MyFramework.Binding.dll
4. Pack the NuGet
# Pack in Release mode dotnet pack MyFramework.Binding/MyFramework.Binding.csproj -c Release # Output: MyFramework.Binding/bin/Release/MyCompany.MyFramework.iOS.1.0.0.nupkg
5. Verify the Package
# List package contents dotnet nuget locals global-packages -l unzip -l MyFramework.Binding/bin/Release/MyCompany.MyFramework.iOS.1.0.0.nupkg # Or use NuGet Package Explorer (GUI)
Verify the package contains:
- •
lib/net10.0-ios/MyFramework.Binding.dll(and maccatalyst) - •Native framework files (if embedding)
- •Package metadata files
6. Publish
# Push to NuGet.org dotnet nuget push MyFramework.Binding/bin/Release/MyCompany.MyFramework.iOS.1.0.0.nupkg \ --api-key YOUR_API_KEY \ --source https://api.nuget.org/v3/index.json # Push to a private feed dotnet nuget push *.nupkg \ --api-key YOUR_API_KEY \ --source https://pkgs.dev.azure.com/your-org/_packaging/your-feed/nuget/v3/index.json
Complete .csproj Template
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<IsBindingProject>true</IsBindingProject>
<!-- NuGet metadata -->
<PackageId>MyCompany.MyFramework.iOS</PackageId>
<PackageVersion>1.0.0</PackageVersion>
<Title>MyFramework .NET Bindings</Title>
<Description>C# bindings for MyFramework native xcframework</Description>
<Authors>Your Name</Authors>
<Copyright>Copyright © 2025 Your Company</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/your-org/your-repo</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>ios;maccatalyst;dotnet;bindings;myframework</PackageTags>
<RepositoryUrl>https://github.com/your-org/your-repo</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<!-- Generate XML documentation -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- Native framework reference -->
<ItemGroup>
<NativeReference Include="../Frameworks/MyFramework.xcframework">
<Kind>Framework</Kind>
<SmartLink>True</SmartLink>
</NativeReference>
</ItemGroup>
<!-- Package additional files -->
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
<None Include="icon.png" Pack="true" PackagePath="" />
</ItemGroup>
<!-- Binding source files -->
<ItemGroup>
<ObjcBindingApiDefinition Include="ApiDefinition.cs" />
<ObjcBindingCoreSource Include="StructsAndEnums.cs" />
</ItemGroup>
<!-- Dependencies on other binding packages -->
<ItemGroup>
<PackageReference Include="MyCompany.OtherFramework.iOS" Version="1.0.0" />
</ItemGroup>
</Project>
Multi-Framework SDK Packaging
When packaging a multi-framework SDK (like Facebook SDK), you have two options:
Option A: One NuGet per Framework (Recommended)
Each framework gets its own NuGet with dependency references:
MyCompany.SDK.Core.iOS (1.0.0) MyCompany.SDK.Login.iOS (1.0.0) → depends on Core MyCompany.SDK.Share.iOS (1.0.0) → depends on Core MyCompany.SDK.Gaming.iOS (1.0.0) → depends on Core + Share
In the Login binding .csproj:
<ItemGroup> <ProjectReference Include="../Core.Binding/Core.Binding.csproj" /> </ItemGroup>
When packing, NuGet automatically converts ProjectReference to package dependencies.
Option B: Meta-Package
Create a meta-package that pulls in all framework NuGets:
<!-- MyCompany.SDK.iOS.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
<PackageId>MyCompany.SDK.iOS</PackageId>
<PackageVersion>1.0.0</PackageVersion>
<Description>Complete SDK — includes Core, Login, Share, and Gaming</Description>
<!-- No binding files — this is a dependency-only package -->
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MyCompany.SDK.Core.iOS" Version="1.0.0" />
<PackageReference Include="MyCompany.SDK.Login.iOS" Version="1.0.0" />
<PackageReference Include="MyCompany.SDK.Share.iOS" Version="1.0.0" />
<PackageReference Include="MyCompany.SDK.Gaming.iOS" Version="1.0.0" />
</ItemGroup>
</Project>
Versioning Strategy
Semantic Versioning
Follow the native SDK version when possible:
Native SDK v18.0.2 → NuGet 18.0.2 Native SDK v18.1.0 → NuGet 18.1.0
If your bindings have their own fixes independent of the native SDK:
Native SDK v18.0.2, binding fix #1 → NuGet 18.0.2.1 Native SDK v18.0.2, binding fix #2 → NuGet 18.0.2.2
Pre-release Versions
<PackageVersion>18.0.2-preview.1</PackageVersion> <!-- Pre-release --> <PackageVersion>18.0.2-beta.1</PackageVersion> <!-- Beta --> <PackageVersion>18.0.2</PackageVersion> <!-- Stable -->
Package README Template
Include a README.md in the NuGet:
# MyFramework .NET Bindings C# bindings for the MyFramework native iOS/Mac Catalyst xcframework. ## Installation ```bash dotnet add package MyCompany.MyFramework.iOS
Usage
using MyFramework;
var manager = MyManager.SharedInstance;
manager.Configure(new MyConfiguration("app-id", "app-secret"));
Requirements
- •.NET 10.0+ (iOS / Mac Catalyst)
- •iOS 15.0+ / macOS 12.0+
Native SDK Version
This package includes MyFramework native SDK v18.0.2.
## CI/CD Pipeline
### GitHub Actions Workflow
```yaml
name: Pack and Publish
on:
push:
tags: ['v*']
jobs:
pack:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install workloads
run: |
dotnet workload install ios
dotnet workload install maccatalyst
- name: Build
run: dotnet build -c Release
- name: Pack
run: dotnet pack -c Release --no-build
- name: Push to NuGet
run: |
dotnet nuget push **/*.nupkg \
--api-key ${{ secrets.NUGET_API_KEY }} \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
Troubleshooting
Package Too Large
If the NuGet is very large (100MB+) because of embedded xcframeworks:
- •Ensure
SmartLink=Trueto strip unused architectures - •Consider shipping separate packages per framework instead of one mega-package
- •Use
NativeReferencewithIsCxx=Falseif C++ runtime isn't needed
Native Framework Not Found at Runtime
Consumer gets linker errors despite installing the NuGet:
- •Verify
NoBindingEmbeddingisfalse(default) for published packages - •Check that the
NativeReference Includepath is correct relative to the.csproj - •Ensure the xcframework contains the needed slice (ios-arm64, maccatalyst-arm64_x86_64)
Missing Dependencies Between Packages
If Framework B depends on Framework A:
- •The NuGet for B must have a
<PackageReference>to A's NuGet - •Or use
<ProjectReference>during development (auto-converted to package dep when packing)
Symbol Package
For debugging support, generate a symbol package:
<PropertyGroup> <IncludeSymbols>true</IncludeSymbols> <SymbolPackageFormat>snupkg</SymbolPackageFormat> </PropertyGroup>
Push alongside the main package:
dotnet nuget push *.nupkg --source https://api.nuget.org/v3/index.json # .snupkg is pushed automatically to the NuGet symbol server
References
- •references/nuget-metadata.md — Complete NuGet metadata properties reference and conventions for binding packages