Kotlin Help

JS plain objects compiler plugin

The JavaScript (JS) plain objects compiler plugin (js-plain-objects) lets you create and copy plain JS objects in a type-safe way.

Here you can find information about plain JS objects and how to use the js-plain-objects compiler plugin for your Kotlin/JS projects.

Plain JS objects

A plain object is a simple JS object created via an object literal ({}) that contains data properties. Many JS APIs accept/return Plain JS objects for configuration or data exchange.

With the js-plain-objects plugin, you declare a Kotlin external interface to describe the object shape and annotate it with @JsPlainObject. The compiler then generates convenient functions to build and copy such objects while preserving Kotlin type safety.

Enable the plugin

Add the js-plain-objects plugin to your project's Gradle configuration file, as the following Kotlin DSL shows:

// build.gradle.kts plugins { kotlin("multiplatform") version "2.2.20" kotlin("plugin.js-plain-objects") version "2.2.20" } kotlin { js { browser() // or nodejs() } }
// build.gradle plugins { id 'org.jetbrains.kotlin.multiplatform' version '2.2.20' id 'org.jetbrains.kotlin.plugin.js-plain-objects' version '2.2.20' } kotlin { js { browser() // or nodejs() } }

Declare a plain object type

Once you have enabled the js-plain-objects plugin, you can declare a plain object type. Annotate an external interface with @JsPlainObject. For example:

@JsPlainObject external interface User { val name: String val age: Int // You can use nullable types to declare a property as optional val email: String? }

When the plugin processes such an interface, it generates a companion object with two helper functions for creating and copying objects:

@JsPlainObject external interface User { val name: String val age: Int val email: String? // Generated by the plugin @JsExport.Ignore companion object { inline operator fun invoke(name: String, age: Int, email: String? = NOTHING): User = js("({ name: name, age: age, email: email })") inline fun copy(source: User, name: String = NOTHING, age: Int = NOTHING, email: String? = NOTHING): User = js("Object.assign({}, source, { name: name, age: age, email: email })") } }

From the previous example:

  • name and age are declared without a nullability mark, so they are required.

  • email is declared as nullable, so it's optional and can be skipped during creation.

  • The operator invoke builds a new plain JS object with the provided properties.

  • The copy function creates a new object by shallow-copying source and overriding any specified properties.

  • The companion is marked with @JsExport.Ignore to avoid leaking these helpers into JS exports.

Use plain objects

Create and copy objects using the generated helpers:

fun main() { val user = User(name = "Name", age = 10) val copy = User.copy(user, age = 11, email = "some@user.com") println(JSON.stringify(user)) // { "name": "Name", "age": 10 } println(JSON.stringify(copy)) // { "name": "Name", "age": 11, "email": "some@user.com" } }

The Kotlin code compiles to JavaScript:

function main () { var user = { name: "Name", age: 10 }; var copy = Object.assign({}, user, { age: 11, email: "some@user.com" }); println(JSON.stringify(user)); // { "name": "Name", "age": 10 } println(JSON.stringify(copy)); // { "name": "Name", "age": 11, "email": "some@user.com" } }

Any JavaScript objects created with this approach are safe. When you use a wrong property name or value type, you encounter compile-time errors. This approach is also zero-cost since the generated code is inlined as a simple object literal and Object.assign calls.

What's next

Learn more about interoperability with JavaScript in the Use JavaScript code from Kotlin and dynamic type documentation.

04 September 2025