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.
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 runa 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(ordeno 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:
{
"unstable": ["tsgo"]
}
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:
{
"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:
{
"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:
// @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:
{
"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:
// @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:
// @ts-self-types="./add.d.ts"
export function add(a, b) {
return a + b;
}
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:
// @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:
// @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:
{
"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:
{
"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
- Set up your editor with the Deno language server for inline type checking. See Setup your environment.
- Read the
TypeScript configuration reference
for the full compiler options table and
tsconfig.jsonsemantics. - Type-check the examples in your docs with documentation tests.
- Moving an existing project? Start with Migrating from Node.js.