docs · authoring
Types.
Rune ships TypeScript types not by maintaining a hand-written API package but by reflecting your live classpath at runtime and emitting .d.ts files. If a plugin is on the server, your IDE knows about its classes. If it isn't, it doesn't. The types match reality.
What gets generated, and when
On every server start, after the Paper plugin enables and before any script loads, the host walks the live classpath and writes three things:
plugins/Rune/types/bukkit.d.ts— every public class underorg.bukkit.*,io.papermc.paper.*, andnet.kyori.adventure.*.plugins/Rune/types/events.d.ts— every subclass oforg.bukkit.event.Eventthat the reflector finds, including those contributed by other plugins, plus theEventsenum that backs@EventHandler(Events.X).plugins/Rune/types/<alias>.d.ts— one per plugin declared in yourrune.jsonc'spluginsmap, emitted from the plugin's package.
The files are committed to disk so your editor can pick them up without running the server. They're regenerated on every start so they stay accurate as you add or remove plugins.
Wiring them into tsconfig
The scaffolded tsconfig.json already includes the right paths. If you're hand-rolling one, point TypeScript at the types directory and let it discover everything.
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"types": ["@rune/sdk"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"**/*.ts",
"../../types/**/*.d.ts"
]
}The experimentalDecorators flag is needed for @Listener / @Command / friends to type-check. Rune uses TC39 stage-3 decorators at runtime; the legacy TS-decorator flag is just for type-checking compatibility today and will go away once TS 5.9 stabilizes stage-3 emit.
Author-owned types.d.ts
If your Rune exposes its own globals (ward exposes ward.registerTransaction(...) on the global scope), declare them in a types.d.ts at your project root. TypeScript will merge them with the host-generated declarations.
// ward/types.d.ts
declare global {
const ward: WardApi;
}
interface WardApi {
registerTransaction<A extends TransactionArgs>(
handler: string,
impl: TransactionHandler<A>,
): void;
unregisterTransaction(handler: string): void;
}
export {}; // make this a module so 'declare global' worksWhat's typed and what isn't
The reflector tries hard to be accurate but has limits.
Typed
- Public constructors, methods, and fields on every reflected class.
- Enum values as union types — autocomplete on
bukkit.Materiallists every block and item. - Generic parameters with their declared bounds (so
List<Player>from Java surfaces asJavaList<Player>in TS).
Not typed
- Anonymous and synthetic classes (anything Java's
Class#isSynthetic()reports). - Methods on package-private classes — they exist at runtime if you can reach them, but autocomplete won't suggest them.
- Reflective access via
rune.callStaticwith a string FQN. By design — you're telling the host you know what you're doing.
Adding types for a plugin not in rune.jsonc
If you want intellisense for a plugin you're not formally declaring (perhaps for one-off interop), write the types yourself in types.d.ts using the global proxy. The runtime call path is unaffected.
declare const someplugin: {
Api: {
getInstance(): {
getThing(id: string): SomeThing | null;
};
};
};
interface SomeThing {
getName(): string;
}