Publish a Kotlin/Native library as NuGet
Configure a Kotlin Multiplatform module, package its native binaries and generated C# API, then add the resulting MyCatLib package to a .NET project.
1. Apply the plugin and build shared libraries
Apply Kotlin Multiplatform and the NuGet plugin, then give every supported native target the same shared-library baseName:
Replace <version> with the version shown in Getting started, and keep it pinned. Only configured targets in the supported target table that this host can link are added to the package this way; a RID built on another host can still be added via prebuiltRuntimes, see One package for every platform below.
Ordinary compilation succeeds; only C-export at link time fails. This is KT-62984, a Kotlin/Native backend bug triggered by plugin-generated IR that carries no klib origin, not anything specific to this plugin. Marking the annotated declarations internal does not help: the crashing IR is generated by the other plugin, not by your code.
The workaround is to exclude the offending plugin from the target's kotlinCompilerPluginClasspath<Target> configuration:
2. Configure the NuGet package
Publish every public declaration below rootPackage into the corresponding C# namespace:
For example, add this API under src/nativeMain/kotlin/com/example/cats/Cat.kt:
The root package maps to the MyCatLib C# namespace. After adding the NuGet package to a .NET project, use the generated API like this:
See Publishing Kotlin to C# for the supported Kotlin constructs and their generated C# shapes. The complete package metadata options are in the nuget {} DSL reference.
3. Build and consume the package
Build the package from the project root:
The package is written to build/nuget/MyCatLib.1.0.0.nupkg. See Gradle tasks for the complete output layout.
Add that directory as a NuGet package source, then add a PackageReference from the C# consumer. For example, create a NuGet.Config beside the .NET solution:
Adjust the relative feed path for your directory layout. Keep the package reference version in sync with publish {}. NuGet brings in the generated C# source and selects the native binary for the consumer's runtime identifier.
A framework-dependent C# build also needs a runtime identifier so MSBuild copies the matching native asset beside the host. For a cross-platform project, use the SDK host RID:
Do not hardcode one host RID into a cross-platform test project. For deployment, select the RID of the target environment.
One package for every platform
No single host can link every RID: a macOS host links macosArm64 and cross-compiles mingwX64, but a Windows host can only link mingwX64, and neither can link the other's Apple- or Windows-only targets. prebuiltRuntimes merges another host's already-linked runtimes/ output into this host's own pack, so one host still produces one publishable package instead of each CI leg shipping a package that only covers its own platform.
A typical two-host flow: the Windows leg runs packNuget and uploads its staged runtimes/ folder as a CI artifact, then the macOS leg downloads that artifact into a local directory and points at it before running its own packNuget:
The resulting package carries a runtimes/<rid>/native/ folder for both the locally linked RID and the prebuilt one:
See The nuget {} DSL for the full validation rules (an empty prebuilt directory, a RID declared both locally and prebuilt, an unknown RID name) and ADR-093 for the design rationale.
4. Iterate locally without bumping the version
Without snapshot versioning, every local Kotlin change needs a version bump, or the consumer's packNuget + delete-from-cache + dotnet restore --force --no-cache dance, because NuGet treats a version as immutable and serves the cached copy of the previous build otherwise. Add snapshot = true to publish {} to skip that entirely:
Every packNuget now mints a fresh 1.0.0-snapshot.<epochMillis> identity and writes build/MyCatLibVersions.props, pinning it under a sanitized property name (see The nuget {} DSL for the naming rule):
Import that props file from the .NET consumer, guarded with Exists() so the build still works before the first pack, then reference the package by the property instead of a literal version:
Adjust the relative path to the props file for your directory layout, and to a shared Directory.Build.props if the consumer has several projects. dotnet restore now picks up the new version on every build, no cache clearing required. Successive snapshot packs accumulate .nupkg files in build/nuget; this is harmless, and clean removes them.
5. Publish the package to a feed
The local package source above is useful while developing and testing the library. To distribute MyCatLib, publish the generated package to NuGet.org or a private NuGet feed instead.
packNuget creates the .nupkg, but does not upload it. With the .NET SDK installed and credentials for the destination feed, push the package with dotnet nuget push:
For a private feed, replace --source with that feed's endpoint and use credentials accepted by the feed. Consumers then reference MyCatLib with the same normal PackageReference shown above and restore it from the published feed. They do not need the mycatlib-local entry in their NuGet.Config.