On this page
Code editor setup
Open the Defold project folder in VSCode or any editor with TypeScript support. Use the folder that contains game.project, src/, and tsconfig.json.
What tsconfig.json provides
The generated tsconfig.json sets:
types: ["@defold-typescript/types"]so Defold globals such asvmath,msg, andprinttype-check insrc/*.ts.- no
outDir, so generated Lua lands next to its.tssource. Lifecycle-factory files become Defold components (src/main.ts->src/main.ts.script), while helper-only imports become Lua modules (src/util.ts->src/util.lua). Set a concreteoutDirto collect the outputs under a separate tree instead. strict: trueso project code gets normal TypeScript checks.
VSCode's built-in TypeScript support reads this file automatically when the project folder is open.
Hover documentation
The generated type declarations carry the Defold reference docs inline as JSDoc. Hovering a Defold symbol in src/*.ts shows its description straight from the engine API docs — functions also list each parameter (@param) and their return (@returns), plus a worked @example code block when the engine docs ship one; constants, variables, and component properties show a summary line. The core consumer-facing modules (vmath, go, msg, sprite) render their @example blocks as idiomatic TypeScript — see TypeScript vs Lua for the idiom mapping — while the remaining modules fall back to the engine's Lua sample until translated. No extra extension required, since it rides on VSCode's built-in TypeScript support. Autocomplete surfaces the same text. Undocumented engine symbols simply show their signature with no description.
Hover docs reach beyond the generated signatures: the overloaded facades (msg.post, go.get/go.set) and the hand-authored helpers (isMessage, onMessage) carry the same summary + @param + @returns + @example blocks, so the receive-side message helpers hover as richly as the generated namespace API.
Recommended editor extensions
You edit TypeScript, not Lua, and the build runs from the CLI — so the required extension stack is minimal:
- TypeScript support: built into VSCode. This is what type-checks
src/*.tsagainst@defold-typescript/types; no extra extension needed. - Local Lua Debugger (
tomblind.local-lua-debugger-vscode) — required for debugging: steps through your.tssource via the emitted source maps. See Debugging for the launch path.
Warning
Defold Kit (astronachos.defold) is not needed. This toolchain ships its own ambient Defold types and a CLI build loop, so the Kit's annotation sync is redundant — and its setup wizard overwrites files this toolchain manages. init no longer recommends it.
Defold runs standard Lua 5.1 (LuaJIT), not Luau. If you want to read the generated Lua, sumneko Lua (sumneko.lua) adds a Lua language server — optional, and no longer auto-recommended, since you author in TypeScript.
Warning
Avoid the Roblox Luau Language Server (johnnymorganz.luau-lsp) in a Defold project: it reports Failed to load sourcemap.json and bogus diagnostics, because it expects a Roblox/Rojo sourcemap.json — an artifact this toolchain never produces. The *.ts.script.map files next to your output are TypeScriptToLua (TSTL) source maps, unrelated to Roblox's sourcemap.json.
init scaffolds a .vscode/ folder that encodes this:
extensions.jsonrecommends only Local Lua Debugger and marks the Luau LSP as unwanted.settings.jsonsetsLua.workspace.ignoreDir: ["src"]so that, if you install the optional sumneko Lua server, it does not lint the generated*.ts.scriptoutput (which would flag TSTL-emittedselfparameters as unused). Hand-written Defold.scriptfiles undermain/stay analyzed. The setting is harmless when sumneko is absent.defold-typescript.code-snippetsexpands an empty script over the lifecycle factories (the TypeScript equivalent of the Defold editor's "new script" templates).launch.json+defold-debug.tsset up a shell-free, Windows-native debug launch path. See Debugging.tasks.jsonregistersdefold-typescript: build/defold-typescript: watchtasks with a shared problem matcher, so transpile errors land in the editor's Problems panel.
These files merge additively into any .vscode/ config you already have, so your own recommendations, settings, snippets, and launch configs are preserved.
Script snippets
In any .ts file, type one of these prefixes and accept the completion to scaffold a whole empty script over defineScript / defineGuiScript / defineRenderScript:
| Prefix | Scaffolds |
|---|---|
def-ts-defineScript-inferred-self |
script, state inferred from init |
def-ts-defineScript-typed-self |
script, explicit Self type |
def-ts-defineGuiScript-inferred-self |
GUI script, state inferred from init |
def-ts-defineGuiScript-typed-self |
GUI script, explicit Self type |
def-ts-defineRenderScript-inferred-self |
render script, state inferred from init |
def-ts-defineRenderScript-typed-self |
render script, explicit Self type |
The two variants mirror the two self-typing idioms (see Script lifecycle). The inferred variant returns an inline object from init, and TSelf follows that return — the documented default. The typed variant declares a named Self type and passes it explicitly as defineScript<Self>(…), the escape hatch for when you want to name the state shape up front. Render snippets omit on_input, which RenderScriptHooks does not expose.
Keeping snippets and types in sync
An expanded snippet emits every lifecycle hook the installed @defold-typescript/types declares. If your editor reports <hook> does not exist in type 'ScriptHooks' (and the parameters of that method turn implicitly any), work through it in this order:
- Restart the TypeScript server first. A stale language-server process keeps the old type surface in memory after a dependency upgrade, so the error survives even once the right types are on disk. In VS Code, run TypeScript: Restart TS Server from the command palette. This clears most false positives.
- Then bring the types up to the CLI. The snippet emits whatever the CLI knows about, so a newer hook (such as
fixed_updateorlate_update) errors when the project's resolved@defold-typescript/typeslags the CLI that wrote the snippet. Upgrade to the latest and keep them in lockstep:bunx @defold-typescript/cli@latest init . --forcerepins the managed@defold-typescript/typesdependency to the CLI's own version.
Edit TypeScript under src/. Treat generated .ts.script/.ts.gui_script/.ts.render_script component files, helper .lua modules, and their .map siblings next to your sources as build output; the scaffolded .gitignore keeps them out of version control.
Run the watch loop
Keep Defold open on the same project folder, and in the editor's integrated terminal run watch:
bunx @defold-typescript/cli watch
It rebuilds Lua when files under src/ change; run the game from the Defold editor after each rebuild. See Watch for the incremental-session behavior, the extension-surface reconciliation, and the mise task equivalent (also listed below).
Opinionated mise.toml
init scaffolds (or additively merges into) an opinionated mise.toml with the following tasks. The block is marker-fronted, so re-running init refreshes the managed tasks without disturbing your own [tools] or [tasks.*] entries.
defold-typescript:build—bunx @defold-typescript/cli build. Builds once with the project's CLI.defold-typescript:watch—bunx @defold-typescript/cli watch. The watch loop above, as a task.defold-typescript:resolve—bunx @defold-typescript/cli resolve. Materializes the native-extension and vendored-library type surfaces fromgame.projectdependencies.watchruns this automatically on everygame.projectchange, so it is mainly a one-off after adding a dependency.defold-typescript:setup-debug—bunx @defold-typescript/cli setup-debug. Wires the lldebuggergame.projectdependency and entry-script bootstrap (see Debugging).defold-typescript:init-agents—bunx @defold-typescript/cli init-agents .. Materialize or refresh theAGENTS.md/CLAUDE.mdAI-harness contract.defold-typescript:upgrade—bunx @defold-typescript/cli@latest init . --force --suppress-install-reminderthenbun install(the second command reinstalls, so the install reminder is suppressed). The deliberate upgrade path.
Build and watch carry no version tag, so inside an installed project bunx resolves the @defold-typescript/cli that init scaffolds as a pinned devDependency — the build runs the version locked alongside the pinned @defold-typescript/types. Upgrade is the one task that intentionally pulls @latest: init . --force re-pins both managed deps to the new CLI's version, and bun install reinstalls.
Upgrading from a pre-
init .project.initnow requires an explicit destination, but amise.tomlscaffolded by an older CLI carries a:upgradetask that callsinit --forcewith no path. The firstmise run defold-typescript:upgradetherefore fails witha destination folder is required. Runbunx @defold-typescript/cli@latest init . --forceonce by hand: it rewrites the managedmise.tomlblocks (the refresh runs on everyinit, not just--force) so the dotted:upgradetask lands on disk and the task works from then on.