On this page
Pinning the Defold API version
@defold-typescript/types ships the latest/current Defold API surface
pre-baked, and generates older versioned surfaces on demand. The default import
is always the current surface; opting into an older version generates that
version's surface on your machine and materializes it locally. Pinning a surface
makes the TypeScript compiler reject calls to engine functions that do not exist
in the Defold version you target, instead of letting them through to fail at
runtime.
The default stays current
If you do nothing, you get the current surface — the same behaviour as before versioning existed:
// tsconfig.json
{
"compilerOptions": {
"types": ["@defold-typescript/types"]
}
}
This is unchanged for existing projects. The default surface tracks the latest generated API.
Opting into a pinned surface
You do not pin a surface through a package subpath export. Versioned surfaces
are not shipped pre-baked in the npm package — only the current-stable
surface is. A non-current version's .d.ts are generated on your machine the
moment you pin it (from that version's Defold reference docs) and
materialized into a project-local .defold-types/<version>/ faux @types
package that your tsconfig.json references.
You select the version with the package.json pin described below; the
toolchain resolves it, generates the matching surface, and repoints
tsconfig.json at the materialized package. A function that exists only on the
current surface is then a compile error against an older pin, while functions
shared across versions continue to type-check.
Recording the project's Defold version
A Defold project pins a fixed engine version, but that version is not stored
anywhere in the project tree — not in game.project, not in build artifacts,
not in editor metadata. You declare it in package.json under the
defold-typescript namespace:
// package.json
{
"defold-typescript": { "defold-version": "1.12.4" }
}
bunx @defold-typescript/cli@latest init <folder> seeds this key with the current-stable version when it
creates or augments a package.json, and leaves an existing pin untouched.
The active version resolves with this precedence:
--defold-version <version>on the command line (highest),- the
package.jsonpin, - the installed Defold editor's
version(lowest-precedence fallback), - the current-stable default.
The installed-editor detection reads the editor bundle's config file from
its conventional per-OS location (for example
/Applications/Defold.app/Contents/Resources/config on macOS,
~/Defold/config on Linux, %LOCALAPPDATA%\Defold\config or
%PROGRAMFILES%\Defold\config on Windows), parses the version = ... line,
and uses that value when no flag or pin is present. The first candidate that
parses wins, and an unknown platform — or no editor installed — reports
detected: null and falls through to the current-stable default. The exact
bundle paths are pinned for live verification against a real install; the
probe mechanics (per-OS candidate order, parse, hit/miss) are unit-tested
synthetically and the production reader is an injectable seam.
The resolved version is reported in --json output as defoldVersion, and the
surface it maps to is reported alongside it as apiSurface. The current-stable
version maps to the default surface (apiSurface: "defold-1.12.4"); a version with a
registered reference-doc target maps to apiSurface: "defold-<version>" (for
example defold-1.9.8); a version with no matching target reports
apiSurface: null. The source that resolved the version is reported as
defoldVersionSource (flag / pin / detected / default), so an agent
script can tell whether the version came from the command line, the
package.json pin, the installed editor, or the hardcoded default.
Materializing the pinned surface
bunx @defold-typescript/cli build does not only report the surface — it materializes
it. The build writes a project-local .defold-types/<surface>/ directory (a faux
@types package with its own index.d.ts and package.json), then repoints
tsconfig.json at it so exactly one surface is the active ambient type surface:
// tsconfig.json (rewritten by build)
{
"compilerOptions": {
"typeRoots": [".defold-types"],
"types": ["defold-1.9.8"]
}
}
How the surface is produced depends on the resolved version:
- Current-stable copies the pre-baked surface that ships in
@defold-typescript/typesinto.defold-types/defold-1.12.4/. No network access. - A pinned non-current version is generated on the fly from that
version's Defold reference docs and written into
.defold-types/<version>/(for example.defold-types/defold-1.9.8/). The reference docs are downloaded once on first use and cached, so later builds are offline. The generated faux package carries acore-types.d.tsthat re-exports the installed@defold-typescript/types/core-types, so its branded engine types stay unified with the ones your code imports rather than minting a nominally distinct copy. It also carriesengine-globals.d.tsand side-effect imports it from the surfaceindex.d.ts, so the engine types (Vector3,Hash,Url, …) are ambient globals — name them with no import, matching the namespace ergonomics (vmath,go, …).
The .defold-types/ directory is generated output, so build adds it to the
project .gitignore. The materialized directory is reported in --json output
as materializedSurface. Re-running build is idempotent.
The pinned versioned surface is materialized in full — build and watch never
narrow it by script kind. Narrowing a directory to one kind is opt-in via the
wall command; see Wall and the per-kind API wall in
Script lifecycle. (Walls today narrow against the
installed @defold-typescript/types subpaths, not the pinned
.defold-types/<version>/ surface.)
If a pinned version cannot be generated — an unknown version, or no network on
first use — the build does not fail. It reports materializedSurface: null,
warns on stderr, leaves tsconfig.json untouched, and exits 0; the default
committed surface stays usable. Having Bun is enough to compile your project.
Pinning a release channel
A Defold release channel picks which build of the engine the reference docs
are fetched from. Three channels are supported — stable (the default),
beta, and alpha. The stable channel is the production release line; the
beta and alpha channels are experimental pre-release surfaces that track
in-development builds and may break at any time. If you do nothing, the
default stays stable; existing projects with no channel pin are unchanged.
You select a channel the same way you select a version — by name — and the
channel rides the same precedence chain. The --channel flag overrides the
package.json pin, and the pin overrides the stable default:
--channel <channel>on the command line (highest),- the
package.jsondefold-typescript.channelpin, - the
stabledefault.
// package.json
{
"defold-typescript": {
"defold-version": "1.12.4",
"channel": "stable"
}
}
bunx @defold-typescript/cli@latest init <folder> does not seed a channel key, so a
project with no pin behaves exactly as today. The resolved channel is reported
in --json output as defoldChannel on init and build. init reports the
default stable without writing the key, so the key stays absent unless you
pin it yourself.
How the channel affects the doc-source fetch:
stabledownloadsref-doc.zipdirectly from the pinneddefold-version's GitHub release assets (releases/download/<version>/ref-doc.zip) — noengine/share/path. This is the only path that touches the GitHub release archive directly.betaandalpharesolve the channel head viad.defold.com/<channel>/info.jsonand downloadarchive/<channel>/<sha1>/engine/share/ref-doc.zip, cached channel-scoped. Each channel's cache directory is independent, so switching channels does not invalidate thestablecache.
Maintainer verification
The public defold-1.9.8 example target is periodically checked with the
advisory, network-touching bun run ref-doc-delta command. It verifies that the
live Defold 1.9.8 reference docs still include label.get_text and still omit
label.set_text. If the command fails, update the registry target or the example
delta; do not ignore the drift.