Skip to main content
On this page

TypeScript support

TypeScript is a first-class language in Deno. Put TypeScript in a file and run it. There is no compiler to install and no build step, and configuration is optional: a tsconfig.json works if you already have one, but you don't need to write one to get started.

main.ts
function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("world"));
>_
deno run main.ts

Deno also ships the type checker: deno check plays the role of tsc --noEmit, with strict mode on by default. The same checker runs inside your editor through the Deno language server, so type errors surface as you type.

How TypeScript runs in Deno Jump to heading

Deno treats executing TypeScript and type-checking it as two separate concerns:

  • Execution: when you deno run a TypeScript file, Deno strips the types and hands the resulting JavaScript to V8. This is fast, happens in-process, and is cached, but it does not look at whether the types are correct.
  • Type checking: deno check (or deno run --check) runs the real TypeScript compiler over your code without executing it.

This means type errors do not stop your code from running unless you ask. Type-checking a whole module graph is expensive, so Deno keeps it out of the hot edit-run loop and lets you invoke it where it earns its cost: in your editor (via the Deno language server), in CI, and in deno test, which type-checks by default.

A consequence worth knowing: Deno does not write JavaScript next to your sources. The stripped output goes into an internal cache instead (you can see its location listed as "Emitted modules cache" in the output of deno info), so there is no outDir, no dist/ directory, and no source map configuration to manage. Error stack traces still point at your .ts sources directly. If you genuinely need .js files on disk (for example, to ship a library to consumers running Node or a bundler), reach for a separate tool: deno transpile or deno pack.

Coming from Node.js Jump to heading

If you write TypeScript for Node.js today, most of your toolchain has a built-in replacement:

In your Node.js project In Deno
tsx, ts-node, or node with type stripping deno run main.ts
tsc --noEmit deno check
tsconfig.json Optional. Auto-detected if present; Deno-first projects use compilerOptions in deno.json
typescript as a devDependency Nothing to install; the checker ships inside the deno binary
@types/node Built in and version-overridable; node: imports are typed
ESLint + Prettier with TypeScript plugins deno lint and deno fmt handle .ts natively

Three differences trip up Node.js developers most often:

Imports use real file extensions. In Deno you write import { greet } from "./greet.ts", using the actual extension of the file on disk. Under tsc you would instead write ./greet.js to import a file named greet.ts, or opt into allowImportingTsExtensions, which tsc only permits together with noEmit. Deno needs neither workaround, since it never emits. See Modules for how resolution works, including npm packages.

Deno runs the full TypeScript language. Node's built-in type stripping only handles syntax it can erase. Features that generate runtime code (enums, namespaces with runtime values, and parameter properties) require Node's experimental --experimental-transform-types flag, which is still present as of Node 24. Deno runs all of them with no flag.

Most of your tsconfig.json configures output files Deno never writes. Options like target, module, outDir, esModuleInterop, and sourceMap shape emitted JavaScript, which Deno keeps in its internal cache rather than writing to disk. Deno warns about these ignored options, and the migration table in the configuration reference says what to do with each one. For most, the answer is "delete it".

If you are moving a whole project over, the Migrating from Node.js guide covers the rest (dependencies, package.json, npm scripts).

Type checking Jump to heading

Deno type-checks in strict mode by default. It also turns on noImplicitOverride, which tsc leaves off even under strict. The compiler options table lists every default.

Check your code without running it using deno check:

>_
# Check the whole project
deno check

# Check a specific file or directory
deno check main.ts
deno check src/

# Also type-check remote modules and npm dependencies
deno check --all main.ts

# Type-check JavaScript files too, without adding @ts-check to each one
deno check --check-js main.js

deno check exits non-zero on errors, so it works as-is in CI. The --check-js flag shown above is covered in type checking JavaScript below. deno check can also check code blocks in JSDoc comments and markdown files; see documentation tests.

To type-check before execution, add --check to deno run:

>_
deno run --check main.ts

# Include remote modules and npm packages in the check
deno run --check=all main.ts

With this flag, a type error stops the process before any code runs. You can resolve the error, suppress it with a // @ts-expect-error or // @ts-ignore comment, or drop the flag.

deno test and deno bench type-check by default. Pass --no-check to skip it:

>_
deno test --no-check

Faster type checking with the native compiler (tsgo) Jump to heading

TypeScript's native compiler, written in Go and often around 10 times faster than the JavaScript tsc, is integrated into Deno behind an unstable flag. Unlike running the standalone tsgo binary, Deno's integration understands Deno's module resolution and types, so jsr: and npm: specifiers and the Deno global all resolve as usual.

Enable it for a single command with the DENO_UNSTABLE_TSGO environment variable or the --unstable-tsgo flag:

>_
DENO_UNSTABLE_TSGO=1 deno check main.ts
deno check --unstable-tsgo main.ts

Or turn it on for a whole project in deno.json:

deno.json
{
  "unstable": ["tsgo"]
}

Caution

This is an unstable, preview feature. The native compiler is not yet feature-complete, so some programs that type-check with the default compiler may report different results. Don't rely on it for CI or release builds yet, and please report issues.

Configuring TypeScript compiler options Jump to heading

Deno's defaults are strict and modern, so most projects need no configuration at all. When you do want to change the checker's behavior, set the options under compilerOptions, either in deno.json or in a tsconfig.json (see using an existing tsconfig.json). For a Deno-first project, keeping them in deno.json means one config file instead of two:

deno.json
{
  "compilerOptions": {
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true
  }
}

The full list of supported options and their defaults is in the configuration reference. For JSX settings (React, Preact, and friends), see the JSX reference.

Using an existing tsconfig.json Jump to heading

You do not have to translate your configuration to try Deno. Each workspace directory containing a deno.json or package.json is probed for a tsconfig.json; if one exists, Deno automatically uses it for type checking and the language server, no flags needed. Since Deno 2.1, jsconfig.json is also auto-detected when a package.json is present, which is useful for JavaScript-only projects.

For example, an existing Node.js project with this tsconfig.json:

tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "jsx": "react-jsx",
    "lib": ["dom", "esnext"],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*"]
}

will be picked up automatically by deno check and the Deno language server. If a deno.json with its own compilerOptions is added later, those take precedence. Emit-related options are ignored (with a warning), since Deno does not write output files.

For Deno-first projects, prefer compilerOptions in deno.json over a separate tsconfig.json. See Configuring TypeScript for supported fields, precedence rules, and project-reference behavior.

Type checking JavaScript Jump to heading

Deno runs JavaScript and TypeScript side by side, but only type-checks TypeScript files by default. There are three ways to opt a JavaScript file in: add a // @ts-check pragma at the top of the file, enable compilerOptions.checkJs for the whole project, or pass --check-js on the command line (deno check --check-js main.js) for a one-off check without touching any configuration.

With the file in scope, the checker flags the same errors it would in TypeScript:

main.js
// @ts-check

let x = "hello";
x = 42; // Type 'number' is not assignable to type 'string'.

The pragma checks a single file; checkJs turns it on project-wide:

deno.json
{
  "compilerOptions": {
    "checkJs": true
  }
}

JavaScript files can't contain TypeScript syntax like type annotations, but you can provide the same information in JSDoc comments, which the type checker reads:

main.js
// @ts-check

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function add(a, b) {
  return a + b;
}

Providing declaration files Jump to heading

When importing an untyped JavaScript module from TypeScript, the checker assumes everything it exports is any. To fix that, supply a .d.ts declaration file (unless the JavaScript is already annotated with JSDoc).

One important difference from tsc: tsc automatically picks up a .d.ts file sitting next to a .js file with the same basename. Deno does not. You must say where the declaration file is, either in the JavaScript source or at the import site.

Providing types in the source Jump to heading

Prefer declaring types in the .js file itself with @ts-self-types, so every importer gets them for free:

add.js
// @ts-self-types="./add.d.ts"

export function add(a, b) {
  return a + b;
}
add.d.ts
export function add(a: number, b: number): number;

Providing types in the importer Jump to heading

If you can't modify the JavaScript source, annotate the import with @ts-types:

main.ts
// @ts-types="./add.d.ts"
import { add } from "./add.js";

This also works for npm packages that ship without type information, pointing at the corresponding @types package:

main.ts
// @ts-types="npm:@types/lodash"
import * as _ from "npm:lodash";

Providing types for HTTP modules Jump to heading

Servers hosting JavaScript modules can advertise a declaration file in an X-TypeScript-Types response header, which Deno resolves relative to the module URL (like a Location header) and uses during type checking. CDNs such as esm.sh set this header for you.

Targeting browsers and web workers Jump to heading

By default, Deno type-checks code against the Deno runtime's global scope (the deno.window library): Deno.readFile exists, document does not. Code destined for other environments can swap the type libraries via compilerOptions.lib. The common case is code shared between Deno and the browser:

deno.json
{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
  }
}

This adds browser globals like document while keeping the Deno namespace available through deno.ns. Use lib: ["deno.worker"] (or a /// <reference lib="deno.worker" /> directive) for code that runs in a new Worker. The lib property reference describes the available libraries, conflicts to expect, and worker-specific setup.

Augmenting global types Jump to heading

When polyfilling a global API, you can teach the type checker about it with declare global. Note that Deno 2 has no window object; globals live on globalThis, so declare them with var:

declare global {
  var polyfilledAPI: () => string;
}

With this declaration imported, globalThis.polyfilledAPI type-checks.

Alternatively, put the augmentation in a .d.ts file and load it via a triple-slash directive (/// <reference types="./global.d.ts" />) or globally in deno.json:

deno.json
{
  "compilerOptions": {
    "types": ["./global.d.ts"]
  }
}

Avoid global augmentation when an ordinary import will do: globals can cause naming conflicts, make code harder to reason about, and are not supported when publishing to JSR.

Next steps Jump to heading

Last updated on

Did you find what you needed?

Edit this page
Privacy policy