
Build plugin publishing native libraries as NuGet packages, auto-generating C# bindings and reverse bindings, shipping a fixed-ABI runtime, and providing a configurable bind DSL.
A plugin that allows you to publish your Kotlin/Native libraries as NuGet packages to be consumed by .NET projects.
https://www.youtube.com/watch?v=DywUS-qYn6o
Read the announcement: Bring your KMP library to NuGet
0.x and experimental. Anything can change between versions.
The generated bindings are the public API of your NuGet package. Your consumers see your version, never the plugin's. A plugin upgrade that changes how Kotlin renders into C# breaks them at your version, not ours.
Pin the plugin version. Diff the generated Interop.cs when you bump it.
// build.gradle.kts
plugins {
kotlin("multiplatform")
id("io.github.xxfast.kotlin.native.nuget") version "<version>"
}
kotlin {
mingwX64 { binaries { sharedLib { baseName = "mycatlib" } } }
macosArm64 { binaries { sharedLib { baseName = "mycatlib" } } }
}
nuget {
publish {
packageId = "MyCatLib"
version = "1.0.0"
authors = "yourname"
description = "My Kotlin/Native library"
rootPackage = "com.example.cats"
}
}Applying the plugin also adds nuget-runtime, a small Kotlin/Native library carrying the fixed
nuget_* ABI (handles, errors, collections, callbacks, coroutines), as a dependency and exports it
into your shared library; you never reference it yourself. It's the project's first published
artifact that's a real klib rather than a JVM jar, which makes it indexable on
klibs.io. See ADR-127.
// Kotlin
interface Pet { val name: String; fun speak(): String }
enum class Mood { HAPPY, SLEEPY, GRUMPY }
abstract class Animal(override val name: String) : Pet
class Cat(name: String, val lives: Int = 9) : Animal(name) {
var brother: Cat? = null
var mood: Mood = Mood.SLEEPY
val toys: List<Toy> = listOf(Toy("Mouse", "Gray"))
val onMeow: () -> String = { "Meow! My name is $name" }
override fun speak(): String = "Meow!"
}
data class Toy(val name: String, val color: String)
class Box<T>(val item: T)
fun owner(name: String): String? = if (name == "Oreo") "Isuru" else null// C# (auto-generated)
using var oreo = new Cat("Oreo", 9);
oreo.Name; // "Oreo"
oreo.Speak(); // "Meow!"
oreo.Brother = new Cat("Mylo", 9); // nullable object setter
oreo.Mood = Mood.Happy; // enums
using var toy = new Toy("Mouse", "Gray");
toy.ToString(); // "Toy(name=Mouse, color=Gray)"
toy.Equals(toy.Copy("Ball", "Red")); // data class equality + copy
IReadOnlyList<Toy> toys = oreo.Toys; // collections
using var box = new Box<string>("hello"); // generics
string? owner = CatKt.Owner("Oreo"); // nullable returns
using var onMeow = oreo.OnMeow;
onMeow.Invoke(); // lambdas
IPet pet = oreo; // interface polymorphism
Animal animal = oreo; // abstract class hierarchyBy default the processor bridges every public declaration it can see in the module, not just the ones under rootPackage. rootPackage only names the generated C# namespace. If your module has unrelated public API, scope it with publish { include(...); exclude(...) }.
Not everything bridges. The forward direction has a defined bridgeable subset; declarations outside it are skipped with a named diagnostic rather than silently dropped or miscompiled. See Publishing Kotlin to C# for the full mapping and its limits.
public class Template
{
public Template(string template) { ... } // constructor
public string Name { get; set; } // instance property
public string Apply(string name) { ... } // instance method
public static Template Parse(string template) { ... } // static method
public void Use(Action<Template> action) { ... } // IDisposable pattern
public static string Render(Template template, string name) { ... } // static method
} // build.gradle.kts
nuget {
dependencies {
dependency("TestDependency", version = "1.0.0") {
bind {
include("Test.Text") // C# namespaces to bind
alias("Test.Text", "sample.text") // C# namespace to Kotlin package
}
}
}
}// Kotlin (auto-generated)
val template = Template("Hello, {name}") // constructor
template.name = "Oreo" // instance property
template.apply("Oreo") // instance method
Template.parse("Hello, {name}") // static -> companion object
template.use { Template.render(it, "Oreo") } // handles are AutoCloseableFull docs: xxfast.github.io/kotlin-native-nuget
bind {} DSL and what binds todaynuget {} DSL reference
[!TIP] See the architecture overview for how the bridge is built, ROADMAP.md for what's next, and docs/adr/ for the architecture decision records.
A plugin that allows you to publish your Kotlin/Native libraries as NuGet packages to be consumed by .NET projects.
https://www.youtube.com/watch?v=DywUS-qYn6o
Read the announcement: Bring your KMP library to NuGet
0.x and experimental. Anything can change between versions.
The generated bindings are the public API of your NuGet package. Your consumers see your version, never the plugin's. A plugin upgrade that changes how Kotlin renders into C# breaks them at your version, not ours.
Pin the plugin version. Diff the generated Interop.cs when you bump it.
// build.gradle.kts
plugins {
kotlin("multiplatform")
id("io.github.xxfast.kotlin.native.nuget") version "<version>"
}
kotlin {
mingwX64 { binaries { sharedLib { baseName = "mycatlib" } } }
macosArm64 { binaries { sharedLib { baseName = "mycatlib" } } }
}
nuget {
publish {
packageId = "MyCatLib"
version = "1.0.0"
authors = "yourname"
description = "My Kotlin/Native library"
rootPackage = "com.example.cats"
}
}Applying the plugin also adds nuget-runtime, a small Kotlin/Native library carrying the fixed
nuget_* ABI (handles, errors, collections, callbacks, coroutines), as a dependency and exports it
into your shared library; you never reference it yourself. It's the project's first published
artifact that's a real klib rather than a JVM jar, which makes it indexable on
klibs.io. See ADR-127.
// Kotlin
interface Pet { val name: String; fun speak(): String }
enum class Mood { HAPPY, SLEEPY, GRUMPY }
abstract class Animal(override val name: String) : Pet
class Cat(name: String, val lives: Int = 9) : Animal(name) {
var brother: Cat? = null
var mood: Mood = Mood.SLEEPY
val toys: List<Toy> = listOf(Toy("Mouse", "Gray"))
val onMeow: () -> String = { "Meow! My name is $name" }
override fun speak(): String = "Meow!"
}
data class Toy(val name: String, val color: String)
class Box<T>(val item: T)
fun owner(name: String): String? = if (name == "Oreo") "Isuru" else null// C# (auto-generated)
using var oreo = new Cat("Oreo", 9);
oreo.Name; // "Oreo"
oreo.Speak(); // "Meow!"
oreo.Brother = new Cat("Mylo", 9); // nullable object setter
oreo.Mood = Mood.Happy; // enums
using var toy = new Toy("Mouse", "Gray");
toy.ToString(); // "Toy(name=Mouse, color=Gray)"
toy.Equals(toy.Copy("Ball", "Red")); // data class equality + copy
IReadOnlyList<Toy> toys = oreo.Toys; // collections
using var box = new Box<string>("hello"); // generics
string? owner = CatKt.Owner("Oreo"); // nullable returns
using var onMeow = oreo.OnMeow;
onMeow.Invoke(); // lambdas
IPet pet = oreo; // interface polymorphism
Animal animal = oreo; // abstract class hierarchyBy default the processor bridges every public declaration it can see in the module, not just the ones under rootPackage. rootPackage only names the generated C# namespace. If your module has unrelated public API, scope it with publish { include(...); exclude(...) }.
Not everything bridges. The forward direction has a defined bridgeable subset; declarations outside it are skipped with a named diagnostic rather than silently dropped or miscompiled. See Publishing Kotlin to C# for the full mapping and its limits.
public class Template
{
public Template(string template) { ... } // constructor
public string Name { get; set; } // instance property
public string Apply(string name) { ... } // instance method
public static Template Parse(string template) { ... } // static method
public void Use(Action<Template> action) { ... } // IDisposable pattern
public static string Render(Template template, string name) { ... } // static method
} // build.gradle.kts
nuget {
dependencies {
dependency("TestDependency", version = "1.0.0") {
bind {
include("Test.Text") // C# namespaces to bind
alias("Test.Text", "sample.text") // C# namespace to Kotlin package
}
}
}
}// Kotlin (auto-generated)
val template = Template("Hello, {name}") // constructor
template.name = "Oreo" // instance property
template.apply("Oreo") // instance method
Template.parse("Hello, {name}") // static -> companion object
template.use { Template.render(it, "Oreo") } // handles are AutoCloseableFull docs: xxfast.github.io/kotlin-native-nuget
bind {} DSL and what binds todaynuget {} DSL reference
[!TIP] See the architecture overview for how the bridge is built, ROADMAP.md for what's next, and docs/adr/ for the architecture decision records.