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 as vmath, msg, and print type-check in src/*.ts.
  • no outDir, so generated Lua lands next to its .ts source. 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 concrete outDir to collect the outputs under a separate tree instead.
  • strict: true so 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.

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/*.ts against @defold-typescript/types; no extra extension needed.
  • Local Lua Debugger (tomblind.local-lua-debugger-vscode) — required for debugging: steps through your .ts source 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.json recommends only Local Lua Debugger and marks the Luau LSP as unwanted.
  • settings.json sets Lua.workspace.ignoreDir: ["src"] so that, if you install the optional sumneko Lua server, it does not lint the generated *.ts.script output (which would flag TSTL-emitted self parameters as unused). Hand-written Defold .script files under main/ stay analyzed. The setting is harmless when sumneko is absent.
  • defold-typescript.code-snippets expands an empty script over the lifecycle factories (the TypeScript equivalent of the Defold editor's "new script" templates).
  • launch.json + defold-debug.ts set up a shell-free, Windows-native debug launch path. See Debugging.
  • tasks.json registers defold-typescript: build / defold-typescript: watch tasks 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:

  1. 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.
  2. Then bring the types up to the CLI. The snippet emits whatever the CLI knows about, so a newer hook (such as fixed_update or late_update) errors when the project's resolved @defold-typescript/types lags the CLI that wrote the snippet. Upgrade to the latest and keep them in lockstep: bunx @defold-typescript/cli@latest init . --force repins the managed @defold-typescript/types dependency 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:buildbunx @defold-typescript/cli build. Builds once with the project's CLI.
  • defold-typescript:watchbunx @defold-typescript/cli watch. The watch loop above, as a task.
  • defold-typescript:resolvebunx @defold-typescript/cli resolve. Materializes the native-extension and vendored-library type surfaces from game.project dependencies. watch runs this automatically on every game.project change, so it is mainly a one-off after adding a dependency.
  • defold-typescript:setup-debugbunx @defold-typescript/cli setup-debug. Wires the lldebugger game.project dependency and entry-script bootstrap (see Debugging).
  • defold-typescript:init-agentsbunx @defold-typescript/cli init-agents .. Materialize or refresh the AGENTS.md / CLAUDE.md AI-harness contract.
  • defold-typescript:upgradebunx @defold-typescript/cli@latest init . --force --suppress-install-reminder then bun 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. init now requires an explicit destination, but a mise.toml scaffolded by an older CLI carries a :upgrade task that calls init --force with no path. The first mise run defold-typescript:upgrade therefore fails with a destination folder is required. Run bunx @defold-typescript/cli@latest init . --force once by hand: it rewrites the managed mise.toml blocks (the refresh runs on every init, not just --force) so the dotted :upgrade task lands on disk and the task works from then on.