What the file says about itself
Claude Code writes the declarations with an introduction at the top, and this is it, reproduced as it ships. Until Anthropic published their mods docs with 2.1.287, it was the only guidance that came with the surface.
EARLY ACCESS: this surface may change between releases without notice. Written by the engine each time it loads a mod from a folder the person owns, beside that mod as .claude-plugin/types/claude-code/index.d.ts; written again after an update rather than edited. The first line names the Claude Code version that wrote it. TypeScript 5.4 or newer reads it. `claude plugin validate <dir>` is the other half: it reads a plugin's manifest and its hooks module's source the way the engine will and reports what the module hooks and calls and everything the engine would refuse, before any session loads it.
What is here: the module a hooks module may import types from,
import type { Register, On, EngineInterface } from 'claude-code'
(at run time the import is empty), and the globals a hooks module has: `h` and `Fragment` (what JSX compiles against), the JSX namespace, and the environment's web APIs (URL, TextEncoder, AbortController, crypto.subtle, ...). A hooks module runs in an environment of its own: no DOM, no Node. The module, and every file it imports from the plugin, is named .ts, .tsx, .jsx, .js, .mjs, .cjs, .mts or .cts (a file named otherwise is not loaded) and is an ES module whatever its suffix: there is no `require`. A file of the plugin is imported with an `import` declaration. A module holding `import()` does not load. The elements a render hook draws with (`Box`, `Text`, `Button`, ...) are not globals: they come from the surface's table,
const { Box, Text } = $.ui.resolve(e)
Also here: 'claude-code/testing', the kit a plugin's *.test.ts and *.test.tsx files import under `claude plugin test <dir>`, which runs each in an environment like the one a plugin's hooks run in (no fs, network or process), the plugin loaded from the folder by the engine's own host: `test(name, async ($, on) => { ... })`, where `$` is the engine's own and the hooks `on` registers sit beneath every plugin; with describe, expect, tier, and `mock`, whose clock, store and env answer those nouns beneath the plugins from memory. `$.ui.mount` draws a component through the plugin on the surface the test names (terminal, desktop, vscode or mobile: never assumed) and hands back the drawing to read and act on by key: find an element, press a Button, type into an Input, drive a `Client`'s module (keys, pointer, posts, its frame clock), each act typed by that surface's element table, so one test body run over several surfaces covers the mod's hooks and description on each. It exercises the mod (its hooks, the tree they return under each surface's rules, its `Client` modules), never a surface's paint (Ink's, the desktop page's):
test('notes add up on every surface that takes input', async $ => {
for (const surface of ['terminal', 'desktop'] as const) {
const ui = await $.ui.mount({ plugin: 'notes', surface, ...BAND })
await ui.input({ key: 'new', text: 'milk' })
expect((await ui.find({ key: 'count' }))?.text).toBe('1 note')
await ui.key({ key: 'down', in: 'list' })
await ui.unmount()
}
for (const surface of ['terminal', 'desktop', 'vscode', 'mobile'] as const) {
const ui = await $.ui.mount({ plugin: 'notes', surface, ...HINT })
expect(await ui.find({ type: 'Text', text: /notes/ })).toBeDefined()
await ui.press({ key: 'dismiss' })
}
})
Typing a plugin against it:
export const register: Register = (on, options) => { ... }
or, in a .js module,
/** @type {import('claude-code').Register} */
export const register = (on, options) => { ... }
A tsconfig.json (or jsconfig.json) that fits a hooks module:
{
"compilerOptions": {
"target": "es2023", "lib": ["es2023"], "types": [],
"module": "esnext", "moduleResolution": "bundler",
"strict": true, "noUncheckedIndexedAccess": true,
"noEmit": true, "skipLibCheck": true,
"jsx": "react", "jsxFactory": "h", "jsxFragmentFactory": "Fragment"
},
"include": [".claude-plugin/types", "hooks", "types", "tests"]
}
".claude-plugin/types" is where the engine lays this file (claude-code/) and, beside it, this build's built-in tools (claude-code-tools/), the MCP tools connected when the mod last reloaded (claude-code-mcp/) and one entry per plugin the mod's plugin.json lists under "dependencies" (that plugin's own contract): what it adds to `$` in engine.create, so a plugin you depend on is typed with nothing copied (the include above takes the whole folder). "hooks" is the plugin's hooks/ folder, "types" its own contract and "tests" its test files. `lib` names no DOM: the environment has none, and its `Text` would shadow the element. A mod with no tsconfig of its own has these options without writing them: the engine gives it a tsconfig.json that extends .claude-plugin/types/tsconfig.json, which carries them with that folder as the one type root.
A plugin that adds a noun to `$` ships its own contract: a .d.ts its plugin.json names as "types", exporting the noun's types at its top level and declaring the noun on the engine's interface,
export type Topo = { ... }
declare module 'claude-code' {
interface EngineInterface { topo: Topo }
}
with no import or reference, its exported names led by the noun's PascalCase name; the plugin's own hooks module imports them from it.
Where this comes from
Claude Code carries its own type declarations for the hooks runtime. From 2.1.287 it writes them into .claude-plugin/types/ each time it loads a mod from --plugin-dir, and before that /plugin-types wrote them on request. They are not in the npm package as a file and not in the JavaScript bundle either: Claude Code decompresses an asset embedded in the binary. So documenting the surface used to mean installing a build and running it by hand, which is why nobody did it twice.
The release pipeline reads that asset straight out of the binary it already downloads, parses it, and writes a JSON document beside the rest of the release data. This section renders that document. Which means it is never a release behind, and nobody has to remember anything.
The prose about how to use any of this, what the calls cost, and which ones do not behave the way their shape suggests is the Mods Guide. This section is the inventory.
Mined from build 2.1.319 on 6 Nov 2026 . The declarations are Claude Code's own writing about itself, reproduced here; everything around them is this site's.