What does the @JsExport annotation do in Kotlin/JS, and why do you need it before JavaScript or TypeScript code can call your Kotlin declaration?
answer
- Kotlin mangles JS names by default
- @JsExport = stable name + public surface
- Top-level only (or @file:JsExport)
- Feeds the generated .d.ts
- Only exportable types allowed
basics
~20 sBy default Kotlin renames things when it compiles to JavaScript, so JS code can't find them. Adding @JsExport keeps the real names and generates TypeScript type info, so JS/TS can call your class or function.
solid answer
~40 sKotlin/JS uses an internal name-mangling scheme so Kotlin symbols are not reliably reachable from hand-written JavaScript. @JsExport marks a top-level declaration (function, class, object, property) so the compiler emits it with a stable, predictable name and includes it in the module's public surface. When the `dts` / TypeScript-declaration generation is enabled, exported declarations also appear in the generated `.d.ts` file, giving TypeScript consumers real types. Without @JsExport you'd have to rely on `dynamic` access or guess mangled names. @JsExport only works on top-level declarations (or members of an exported class) and restricts you to an 'exportable' subset of Kotlin types — if you export something with a non-exportable signature, the compiler reports an error or warning.
code
kotlin · 11 lines@JsExport
class Greeter(val prefix: String) {
fun greet(name: String): String = "$prefix, $name"
}
// Generated .d.ts roughly:
// export class Greeter {
// constructor(prefix: string)
// readonly prefix: string
// greet(name: string): string
// }go deeper
Knows @JsExport makes a declaration callable from JS and that names are otherwise mangled.
Explains the public-surface + stable-name mechanics, top-level scope, and that it feeds the .d.ts.
Adds the exportable-type restriction, @file:JsExport, and the relationship to the TS-declaration Gradle option.
Frames @JsExport as the contract boundary for publishing a typed npm/JS API and reasons about API stability across the mangling boundary.
## The problem @JsExport solves When Kotlin compiles to JavaScript, the compiler **mangles** (renames) symbols. Mangling means the Kotlin name `calculateTotal` may become something like `calculateTotal_abc123$` in the emitted JS to avoid clashes and support overloads. Hand-written JavaScript or TypeScript has no way to know that mangled name, so the declaration is effectively invisible from outside Kotlin. ## What @JsExport does `@JsExport` is an annotation you put on a **top-level** declaration — a function, class, `object`, or property — to tell the compiler: - emit it with a **stable, un-mangled name** that matches the Kotlin name (or the `@JsName` override), - include it in the module's **public JS surface**, and - (when TypeScript declaration generation is on) emit it into the generated **`.d.ts`** file so TS consumers get real types. ```kotlin @JsExport fun greet(name: String): String = "Hello, $name" @JsExport class Counter(var value: Int) { fun increment() { value++ } } ``` From JavaScript (assuming module name `app`): ```js import { greet, Counter } from 'app' console.log(greet('Ada')) const c = new Counter(0) c.increment() ``` ## Generating the .d.ts The TypeScript declaration file is produced when the Gradle option for TS declarations is enabled (`generateTypeScriptDefinitions()` on the JS/browser/nodejs target). Only `@JsExport`ed declarations land in it. ## Scope and limits - Applies to **top-level** declarations; you cannot put it on an arbitrary nested member directly, but members of an exported class are exported with it. - You can annotate a whole **file** with `@file:JsExport` to export everything top-level in that file. - Only **exportable types** are allowed in signatures (primitives, `String`, `Boolean`, exported classes, `Array`, function types, etc.). `Long`, non-exported Kotlin classes, and most collection interfaces are not exportable. ## Why not just use `dynamic`? You *can* reach mangled symbols via `dynamic`, but you lose all type safety and the stable contract. `@JsExport` is the supported, type-checked way to publish an API to the JS/TS world.
- Can you put @JsExport on a private top-level function?No useful effect — only public declarations form the exportable surface; the export contract is about the public API, and private members are not exported.
- How do you export every top-level declaration in a file at once?Use the file-level annotation `@file:JsExport` at the very top of the file, before the package declaration.
Without @JsExport, Kotlin symbols are like apartments with scrambled door numbers — the building (module) knows them but visitors can't find them. @JsExport posts the real, stable number on the door.
saying these in an interview costs you the question
- Claiming @JsExport works on any nested member directly
- Thinking JS can call un-exported Kotlin by its Kotlin name without dynamic
- Confusing @JsExport with @JsName (renaming) — they do different jobs
- Believing @JsExport alone produces a .d.ts without enabling TS declaration generation
- Saying you can export Long or arbitrary Kotlin collections