Editor Setup and TypeScript
Purpose and Scope
Astro projects can be written with JavaScript or TypeScript, but the editor experience is TypeScript-powered either way. The official guidance is that Astro works in many editors, with VS Code recommended because the maintained Astro extension provides syntax highlighting, TypeScript information for .astro files, IntelliSense, completions, hints, and diagnostics. The repository code behind that experience lives in the language-server TypeScript plugins: it adapts Volar and TypeScript services so embedded scripts inside .astro files behave like normal authoring surfaces instead of raw generated TSX.
This page explains the editor-facing TypeScript layer rather than general project creation. A reader setting up a new project should still include a tsconfig.json, because Astro’s documentation describes it as important even for JavaScript projects: tools such as Astro and VS Code use it to understand imports, generated types, and project boundaries. The Astro dev server is not the type checker; editor diagnostics and separate command-line checking complement development. The source files here show how the language server enriches completions, code actions, file rename edits, and diagnostics for Astro-specific syntax and virtual code.
Sources: packages/language-tools/language-server/src/plugins/typescript/index.ts, packages/language-tools/language-server/src/plugins/typescript-addons/index.ts, packages/language-tools/language-server/src/plugins/typescript/diagnostics.ts
Relevant Source Files
packages/language-tools/language-server/src/plugins/typescript-addons/index.ts- Registers an additional completion provider that only contributes Astro snippets inside frontmatter of anAstroVirtualCodedocument.packages/language-tools/language-server/src/plugins/typescript/index.ts- Wraps Volar TypeScript service plugins and enhances the semantic TypeScript plugin with Astro-aware completions, code actions, diagnostics, and rename behavior.packages/language-tools/language-server/src/plugins/typescript-addons/snippets.ts- Defines the frontmatter snippets exposed to authors, includinginterface Props,getStaticPaths, andprerender.packages/language-tools/language-server/src/plugins/typescript/codeActions.ts- Maps TypeScript code action edits from embedded virtual documents back into the original Astro source document.packages/language-tools/language-server/src/plugins/typescript/completions.ts- Filters, sorts, rewrites, and resolves TypeScript completion items so Astro component imports andastro:module imports feel native.packages/language-tools/language-server/src/plugins/typescript/diagnostics.ts- Filters noisy TypeScript diagnostics and rewrites selected messages into more helpful Astro-oriented guidance.
Core Primitives
The central primitive is AstroVirtualCode, which represents an Astro file after the language tooling has split it into embedded documents such as TSX. The TypeScript plugin uses context.decodeEmbeddedDocumentUri() to relate a document URI back to the original source script and its generated root. That relationship lets the plugin decide whether a request is really operating on an Astro file, whether it targets a TSX embedded document, and how far diagnostics should be trusted inside the generated output.
The second primitive is the Volar language-service plugin contract. packages/language-tools/language-server/src/plugins/typescript/index.ts calls createTypeScriptServices() from volar-service-typescript, then maps the returned plugins. Most plugins are passed through unchanged, but the typescript-semantic plugin is wrapped. The wrapper delegates to TypeScript first, then post-processes the result with Astro-specific helpers. This is important architecturally: Astro does not replace TypeScript completions or diagnostics. It layers mapping, filtering, sorting, and clearer messages on top of TypeScript’s own analysis.
A third primitive is frontmatter awareness. Astro components have a frontmatter script section, and authoring features should not appear everywhere in the template. The add-on completion plugin checks cancellation, avoids trigger-kind completion requests that should not produce snippets, decodes the embedded document, verifies the root is AstroVirtualCode, and then calls isInsideFrontmatter() before returning snippet completions. This keeps Astro-specific authoring shortcuts in the script area where declarations such as props, static paths, and prerender exports belong.
Sources: packages/language-tools/language-server/src/plugins/typescript-addons/index.ts, packages/language-tools/language-server/src/plugins/typescript/index.ts
Editor Execution Flow
When an editor asks for completions in a .astro file, the wrapped TypeScript semantic service first gathers normal TypeScript suggestions. Astro then calls enhancedProvideCompletionItems() with the TypeScript module, the original completion list, and the document text. That helper detects already imported Astro component sources so duplicate auto-import suggestions can be filtered out. It also gives astro: imports a higher sort priority and treats .astro, .svelte, and .vue component sources as file-like completions, because those are often the suggestions an Astro author expects when writing markup or importing islands.
Resolution is a second pass. TypeScript may add additionalTextEdits for auto-imports, but those edits are produced against embedded documents. enhancedResolveCompletionItem() decodes the embedded URI, locates the generated virtual code, and maps edits back through mapEdit(). Astro component imports are also rewritten so labels, filter text, insert text, text edits, and import text do not expose internal component suffixes. The result is that a user sees and inserts natural component names while the editor still benefits from TypeScript’s import machinery.
Code actions follow the same delegation-and-mapping pattern. enhancedProvideCodeActions() maps each returned code action, and enhancedResolveCodeAction() preserves the same mapping step for actions that arrive later in resolved form. The implementation only rewrites document changes when the change is a TextDocumentEdit, its URI can be decoded, the embedded virtual code is found, and the generated root is an AstroVirtualCode. That defensive sequence prevents Astro mapping logic from touching unrelated TypeScript documents while still making fixes, refactors, and fix-all operations land in the author’s .astro source.
Sources: packages/language-tools/language-server/src/plugins/typescript/completions.ts, packages/language-tools/language-server/src/plugins/typescript/codeActions.ts
TypeScript Diagnostics and Helpfulness
Diagnostics are intentionally adjusted because generated TSX can produce messages that are technically true but confusing for Astro authors. The TypeScript plugin wrapper checks whether the request is for the TSX embedded document of an AstroVirtualCode file. If the Astro compiler has compilation errors, the plugin suppresses TypeScript diagnostics for that generated TSX, because invalid generated code would otherwise produce noisy secondary errors. If the generated TSX is valid, Astro records the mapped body line limit and filters diagnostics outside the relevant range.
The diagnostic enhancer removes selected TypeScript errors that do not map well to Astro authoring, including the return-outside-function case, isolated module errors, and duplicated JSX attribute diagnostics. It also rewrites or extends messages where Astro has better user guidance. For example, a missing astro:content module message gains advice to run astro dev, astro build, or astro sync to generate types and to restart the language server if needed. This connects the editor symptom directly to the project workflow that refreshes generated content types.
Framework-component diagnostics receive similar treatment. If an imported .svelte or .vue snapshot looks empty to TypeScript and triggers an is-not-a-module error, the message suggests installing the corresponding integration with astro add svelte or astro add vue, then restarting the language server if necessary. JSX terminology is also translated for Astro readers: a JSX no-closing-tag message becomes an HTML-oriented message, and an invalid JSX element type is reframed as an invalid component, with extra context for Svelte or Vue syntax errors.
Sources: packages/language-tools/language-server/src/plugins/typescript/diagnostics.ts, packages/language-tools/language-server/src/plugins/typescript/index.ts
Snippets and Astro Type Surfaces
The frontmatter snippet provider is small but reveals the public TypeScript surfaces Astro wants editors to make discoverable. interface Props inserts an interface named Props, matching Astro’s documented convention for typing component props. getStaticPaths inserts an exported constant with a callback that returns an empty array and uses satisfies GetStaticPaths; the snippet also adds an import for GetStaticPaths from astro at the frontmatter start. prerender inserts export const prerender = true or false as a choice, matching the route-level control used by server-rendered projects.
These snippets are only returned when frontmatter exists. getSnippetCompletions() returns an empty array when the frontmatter status is doesnt-exist, computes the frontmatter start position from parsed metadata, and builds completion items with snippet insert text, markdown documentation, labels, descriptions, and filter text. The behavior matters for editor setup because it shows that Astro’s TypeScript support is not only diagnostics: it also teaches the editor about framework conventions that ordinary TypeScript would not infer from the generated code alone.
Sources: packages/language-tools/language-server/src/plugins/typescript-addons/snippets.ts, packages/language-tools/language-server/src/plugins/typescript-addons/index.ts
Compact Reference
| Area | Source entry point | User-visible behavior |
|---|---|---|
| TypeScript service wrapping | create(ts, options) in packages/language-tools/language-server/src/plugins/typescript/index.ts | Creates Volar TypeScript service plugins, then enhances the typescript-semantic plugin. |
| Rename imports | provideFileRenameEdits(oldUri, newUri, token) | Delegates to TypeScript unless astro.updateImportsOnFileMove.enabled is explicitly false. |
| Completion enhancement | enhancedProvideCompletionItems(ts, completions, documentText) | Filters duplicate component imports, prioritizes astro: modules, and marks Astro/framework components as file completions. |
| Completion resolution | enhancedResolveCompletionItem(resolvedCompletion, context) | Preserves component details, rewrites Astro component names, and maps additional text edits back to source. |
| Code actions | enhancedProvideCodeActions() and enhancedResolveCodeAction() | Maps edits in TypeScript actions from embedded documents into Astro source ranges. |
| Diagnostics | enhancedProvideSemanticDiagnostics(originalDiagnostics, tsxLineCount?) | Filters noisy diagnostics and adds Astro-specific help for generated types and framework integrations. |
| Frontmatter snippets | getSnippetCompletions(frontmatter) | Provides interface Props, getStaticPaths, and prerender snippets only when frontmatter exists. |
For day-to-day setup, install an Astro-aware editor extension or language-server integration, keep tsconfig.json in the project, and run the documented Astro sync, dev, build, or check workflows when generated types change. If completions or diagnostics appear stale after adding integrations or content collections, restart the language server after running the relevant Astro command. To understand adjacent workflows, read the CLI reference for astro sync and checking commands, and the API reference for the public types surfaced by snippets.