On this page
Migrating from ts-defold
Moving a Defold + TypeScript project from the ts-defold
stack (@ts-defold/types, @ts-defold/create) to @defold-typescript/*.
Both stacks compile TypeScript to Lua with TypeScriptToLua
(TSTL) and derive their Defold API types from the same Defold reference docs,
so the code you already wrote — lifecycle callbacks, vmath/go/msg calls,
module imports — carries over with little change. The migration is mostly
tooling and configuration, not a rewrite.
Every cross-stack claim on this page is verified against a source — see What this guide verified at the bottom, which also names what could not be verified and is therefore omitted.
What stays the same
- TypeScriptToLua under the hood. Both stacks transpile with TSTL, so the
language subset, the
@typescript-to-lua/language-extensionshelpers, and thelua-typesambient Lua stdlib are shared concepts. (ts-defold's type bundle references@typescript-to-lua/language-extensions,lua-types/5.1, andlua-types/special/jit-only.) - Ambient engine globals. In both stacks the Defold API arrives as ambient
declarations — you call
go,msg,vmath, etc. without importing them. ts-defold ships this as a@noSelfInFileambientindex.d.ts;@defold-typescriptships it as the ambient@defold-typescript/types. - Same Defold surface. Both type sets are generated from the same Defold reference docs, so symbol coverage is at parity by construction. The documentation-quality differences are catalogued separately in API docs vs. ts-defold-types.
Package and tooling map
| ts-defold | @defold-typescript equivalent |
|---|---|
@ts-defold/types — Defold TypeScript definitions |
@defold-typescript/types — ambient, type-only engine surface |
@ts-defold/type-gen — dev-dep that generates those types |
type generation is internal to @defold-typescript/types; it is never a consumer dependency |
@ts-defold/create — npm init @ts-defold my-game (resolves GitHub tsd-template-* templates, or a local/remote zip) |
bunx @defold-typescript/cli@latest init my-game — built-in templates via init my-game --template <name> (default / minimal) |
template npm run build (compile, no watcher) |
defold-typescript build |
template npm run dev (watch: compile + emit lua/script on save) |
defold-typescript watch |
| — (no first-party editor-diagnostics package verified) | @defold-typescript/tstl-plugin — live transpile diagnostics in the editor |
The full defold-typescript command surface is
init | init-agents | build | watch | wall | setup-debug | resolve | defold
(and defold <resolve|build|bundle>).
In-place adoption is a migration strength
Compared with the verified @ts-defold/create flow, defold-typescript init .
is intentionally optimized for an existing Defold project. When game.project
already exists, the command adds the TypeScript toolchain in place instead of
creating a wrapper folder or a new template tree you have to merge back into your
game. That is the main practical advantage for teams migrating a real project:
assets, collections, scripts, and editor paths stay where they are while the TypeScript
surface is layered on top.
Step-by-step migration
These steps run against an existing ts-defold project (a directory that
already has a game.project).
- Add the defold-typescript toolchain in place. From the project root, run
bunx @defold-typescript/cli@latest init .(the.targets the current folder;initrequires an explicit destination). With agame.projectpresent,initruns in add-TypeScript mode: it writes only the TypeScript surface (atsconfig.json, dev-deps merged intopackage.json, and asrc/main.tsonly if one does not already exist) and refuses to overwrite a conflicting TS config unless you pass--force. Your existingsrc/*.tsandgame.projectare left alone. - Reconcile
tsconfig.json. The defold-typescript scaffold setstarget: ES2022,module: ESNext,moduleResolution: Bundler,types: ["@defold-typescript/types"], andplugins: [{ name: "@defold-typescript/tstl-plugin" }]. Point thetypesarray at@defold-typescript/types(replacing@ts-defold/types) and add thetstl-pluginentry to keep live editor diagnostics. - Build with the defold-typescript CLI. Run
defold-typescript build(ordefold-typescript watchfor the save-loop). The transpiler targets Lua 5.1. Compiled output is written beside the source assrc/**/*.ts.script,*.ts.gui_script,*.ts.render_script(plus*.luaand*.mapsidecars) — see Defold editor for attaching the compiled component. - Keep the editor loop.
@defold-typescript/tstl-pluginsurfaces transpile errors live in the editor without ever failingtsc --noEmit(see Live transpile diagnostics); for step-through debugging usedefold-typescript setup-debugand Debugging.
Your ts-defold project's own
tsconfig.json(its TSTLluaTarget,luaLibImport,outDir, and plugin list) is the other half of this reconcile. Those values are project-specific and are not asserted here — open your template'stsconfig.jsonand map each key to the defold-typescript scaffold above.
Type-surface differences you will hit
Symbol coverage is at parity, but the @defold-typescript surface shapes a few things differently.
These are the porting touch-points; the deep comparison lives in
API docs vs. ts-defold-types and the runtime
narrowing traps in TypeScript gotchas.
- Branded constants. Where ts-defold types a constant as
const X: number,@defold-typescript/typesbrands it (number & { readonly __brand: "ns.X" }) so distinct constants stay nominally distinct. Code that passed a rawnumberwhere a branded constant is expected may need the constant itself, not a literal. - Multi-value returns are typed tuples. Functions whose Lua API returns
several values are typed as
LuaMultiReturn<[…]>(each value named in the type), not collapsed to one return. - Per-kind ambient API walls. Beyond the bare
@defold-typescript/typesentry,@defold-typescriptexposes per-script-kind surfaces —@defold-typescript/types/script,/gui-script,/render-script— so a GUI script only sees the GUI-legal API. - Opaque handles. Most engine handles are branded opaque types rather than
bare aliases;
typeof-narrowing cannot see them (they are Luauserdata). The exception is socket handles (client,master,server,connected,unconnected), emitted as method-bearing interfaces so their documented colon methods (client:send, …) are reachable and the receivers stay distinct.
What this guide verified
@defold-typescript side (this repository): packages and the defold-typescript command
surface from packages/cli/src/dispatch.ts; the init add-TS mode and scaffold
tsconfig.json from packages/cli/src/init.ts; the Lua 5.1 transpiler target
from packages/transpiler/src/session.ts and transpile.ts; the compiled-output
naming from the scaffold .gitignore; the per-kind type entrypoints from
packages/types/package.json.
ts-defold side:
- Type surface — the pinned snapshot
packages/types/test/fixtures/ts-defold-types.index.d.ts(thinknathan/ts-defold-types commit4f0672a, Defold stable 1.12.4, pinned 2026-06-05):@noSelfInFile; references@typescript-to-lua/language-extensions,lua-types/5.1,lua-types/special/jit-only,deprecated.d.ts. - Tooling — verified 2026-06-14 against ts-defold/create
and ts-defold/types: the
npm init @ts-defold <dir> [-- --template <name>]scaffolder, thetsd-template-*GitHub discovery pattern, the templatenpm run build(compile) /npm run dev(watch) scripts, and TypeScriptToLua (TSTL) as the transpiler.
Omitted as unverifiable here: the exact contents of a ts-defold template's
tsconfig.json (its luaTarget, luaLibImport, outDir, plugin list) — the
upstream template's config file could not be read at authoring time, so this
guide does not assert those values; consult your own template's tsconfig.json.