Zed extension that runs the TypeScript 7 native language server.
It attaches to Zed's existing JavaScript, JSX, TypeScript, and TSX languages.
TypeScript 7's language server is a native executable that ships inside the platform-specific
@typescript/typescript-<platform>-<arch> npm packages, launched in LSP mode:
tsc --lsp --stdioThe extension executes that native binary directly when it can locate it next to the resolved
typescript package (npm/bun hoisted layouts, pnpm on Linux/macOS). Otherwise it falls back to the
package's bin/tsc Node launcher — Microsoft's own resolution logic — via the worktree's node
(volta/fnm shims included) or Zed's bundled Node. No Node process stays in the middle on the native
path.
The extension resolves the TypeScript 7+ package to run, preferring the project's own copy:
tsdk.path(explicit, version checked). Accepts the package root, itslibdir (VS Codetypescript.tsdkconvention), abin/tscpath, or a platform package containing the native binary.- Any dep in the worktree
package.json(dependencies,devDependencies,peerDependencies, oroptionalDependencies) whose effective package name istypescript: a direct dependency or annpm:alias under any key (such as"@typescript/native": "npm:typescript@^7"). Verifies that the installed package is >=7 and has a usable launcher. (Skips@typescript/typescript6compat aliases.) Within each section,typescriptis tried first, followed by aliases in alphabetical order. Sections are checked in the order listed above. - Otherwise: a managed installation in the extension's own directory. Stable releases are downloaded directly from GitHub; npm handles tags, ranges, and prerelease versions.
Project and host discovery run a short-lived helper using Node from the worktree's PATH, falling
back to Zed's Node runtime when no Node is found there. The helper uses the extension's
process:exec capability. This allows it to inspect installed packages outside the extension's WASM
filesystem. It follows ancestor node_modules directories and pnpm symlinks, checks the installed
package name and version, and returns the native binary or Node launcher path. catalog:, named
catalogs, and workspace:/link:/file: dependencies are identified from their installed
metadata, including aliases. No catalog parsing or package-manager command is needed. Missing,
outdated, or unusable project installations fall back to the managed install; an invalid explicit
tsdk.path reports an error.
Discovery starts from the opened worktree's package.json. It does not choose among child
workspaces with different TypeScript versions, and Yarn PnP layouts without node_modules are not
supported by automatic discovery. Use tsdk.path to select a specific installation.
For managed installs, version wins over updateChannel:
| Setting | Install behavior |
|---|---|
version: "7.0.2" |
Download the platform archive from GitHub release v7.0.2. |
version: "latest" |
Download the latest stable GitHub release. |
Other version specs |
Use npm for tags, ranges and prerelease pins, such as next or ^7. |
updateChannel: "latest" |
Download the latest stable GitHub release (default). |
updateChannel: "prerelease" |
Download the latest GitHub release with assets, including prereleases. |
updateChannel: "next" |
Install typescript@next through npm. |
GitHub installations include the native executable and its standard libraries. Each version is cached separately and reused after a complete download. Concurrent downloads use independent installation directories and publish only after validation; existing launch paths remain intact. Stable and prerelease channels keep separate cache selections, so switching back to stable also stays stable offline. When a release lookup is unavailable, that channel's previous cached release can still start. An existing stable managed npm installation can also be reused if the default GitHub installation fails. For an exact version pin, this fallback requires the same installed version. Exact versions never fall back to a different version.
An explicit Node runtime in binary.path keeps using npm for managed installations because it needs
the package's bin/tsc launcher. With the prerelease channel, npm installs the exact version
selected from GitHub and records that selection after installation. If GitHub is unavailable on a
later start, it reuses that exact installed version when its metadata and Node launcher are still
present. Project and host discovery use the short-lived Node helper; downloading and running a
managed GitHub release does not require npm or a Node launcher.
For a FreeBSD server host, install Node and make node available on the worktree's PATH. Zed's
managed Node downloader does not support FreeBSD. The helper detects the server host's OS and
architecture, so FreeBSD x64 and arm64 select typescript-freebsd-x64.tgz and
typescript-freebsd-arm64.tgz, respectively. Other hosts use the matching asset when the selected
release provides one; a missing asset produces an error. Discovery does not use Zed's restricted
platform enums. The native language server runs directly after discovery. npm-based channels
additionally need an npm runtime usable by Zed on the server host.
TypeScript 6 and older are rejected — for those, use Zed's built-in TypeScript support instead.
All configuration lives under lsp.typescript in Zed settings. The extension owns a small set of
launch/install keys; everything else in settings is forwarded verbatim to the language server,
which pulls it via workspace/configuration.
| Setting | Meaning |
|---|---|
version |
npm version spec to install (wins over updateChannel). |
updateChannel |
"latest", "prerelease" (GitHub including prereleases), or "next" (npm nightly). |
tsdk.path |
Explicit TypeScript package location (see Server resolution). |
server.pprofDir |
Passes --pprofDir so the server writes pprof CPU/memory profiles there. |
server.goMemLimit |
Sets GOMEMLIMIT for the server. Integer bytes with optional B/KiB/MiB/GiB/TiB suffix, or off. |
server.args |
Extra CLI args appended to --lsp --stdio — forwards future server flags without an extension update. |
server.env |
Extra environment variables for the server process (e.g. GOGC, debug vars). |
Dotted ("server.pprofDir": …) and nested ("server": {"pprofDir": …}) forms are both accepted;
the nested form wins when both are set.
The current server flag surface in --lsp mode is --stdio, --pipe <name>, --socket <addr>,
and --pprofDir <dir>; anything new lands via server.args.
lsp.typescript.binary overrides how the server is launched entirely:
{
"lsp": {
"typescript": {
"binary": {
// a path ending in tsc/tsc.js/tsc.exe is launched as the server itself;
// any other path is treated as a custom node to run the resolved tsc with
"path": "/path/to/tsc",
"arguments": ["--lsp", "--stdio"], // optional: full args override
"env": { "GOMEMLIMIT": "4GiB" } // optional: highest-precedence env
}
}
}
}Environment precedence, lowest to highest: shell environment, server.env, server.goMemLimit,
binary.env.
The server requests the configuration sections js/ts, typescript, javascript, and editor,
and merges them with ascending precedence editor → javascript → typescript → js/ts. Put
those sections directly in settings — they are forwarded to the server:
{
"lsp": {
"typescript": {
"settings": {
"typescript": {
"preferences": {
"quoteStyle": "single",
"importModuleSpecifier": "non-relative",
"preferTypeOnlyAutoImports": true
}
}
}
}
}
}Matching Zed's built-in TypeScript support, the extension enables all inlay hint kinds and both code
lens kinds server-side by default (Zed's own inlay_hints setting still controls whether hints are
displayed). Your settings deep-merge over these defaults and win at leaf level, so e.g.
"typescript": {"inlayHints": {"parameterNames": {"enabled": "none"}}} turns one hint kind off
while the rest keep their defaults.
VS Code-style dotted keys are also accepted and expanded, so configuration copied from TypeScript docs works as-is (nested form wins on conflict):
{
"lsp": {
"typescript": {
"settings": {
"typescript.inlayHints.parameterNames.enabled": "all",
"js/ts.implicitProjectConfig.checkJs": true
}
}
}
}Option groups the server reads (same names as the VS Code TypeScript settings, minus the
typescript./javascript. prefix): inlayHints.*, preferences.* (quote style, auto-imports,
module specifiers, organize imports), suggest.*, format.*, referencesCodeLens.* /
implementationsCodeLens.*, validate.*, workspaceSymbols.*, autoClosingTags, and
implicitProjectConfig.* under js/ts. The authoritative list is UserPreferences in the server
source.
initialization_options are passed to the server verbatim at initialize.
Known upstream options (InitializationOptions, read in server.go):
| Option | Upstream meaning |
|---|---|
disablePushDiagnostics |
Disable automatic diagnostic pushes. |
codeLensShowLocationsCommandName |
Client command used by resolved references/implementations CodeLens. |
userPreferences |
Initial user preferences (same shape as the sections above). |
enableTelemetry |
Enable server telemetry events. |
logVerbosity |
Initial server log verbosity. |
{
"lsp": {
"typescript": {
"initialization_options": {
"enableTelemetry": false,
"logVerbosity": 2
}
}
}
}Preferences set via the configuration sections update live on workspace/didChangeConfiguration;
initialization_options only apply at server startup.
- TypeScript 7 has no stable programmatic API yet, so embedded-language workflows (Vue, MDX, Astro, Svelte, Angular templates) are not supported by the upstream server.
codeLensShowLocationsCommandNamerequires client-side command support that Zed extensions cannot register; code lenses resolve, but the "show locations" command is editor-dependent.
Prerequisite: Rust installed with rustup.
In Zed, run zed: install dev extension and select this directory.
Run cargo test for the Rust tests and node --test tests/resolve_typescript.test.cjs for the host
resolver tests. After changing the extension, use Rebuild on Zed's Extensions page.
After edits, rebuild from the Extensions page.
For logs, run Zed with zed --foreground or use zed: open log.
{ "lsp": { "typescript": { "settings": { "updateChannel": "next", "server": { "pprofDir": "./.typescript-pprof", "goMemLimit": "2048MiB", "env": { "GOGC": "50" } } } } } }