Open-Source Wikis

/

Astro

/

How to contribute

/

Patterns and conventions

withastro/astro

Patterns and conventions

The Astro repo has been around since March 2021 and has accumulated a small number of strong conventions that are worth knowing before you write code.

Source of truth: AGENTS.md and CONTRIBUTING.md

AGENTS.md (root) is the most up-to-date pointer for AI agents and humans alike, including monorepo layout, test commands, the bgproc/agent-browser helpers, and the optimize-deps deep dive. CONTRIBUTING.md (root) covers local setup, the changeset workflow, the priority labels (p1p5), the issue triage flowchart, and snapshot/prerelease publishing. Read both before opening a PR.

Style is enforced, not documented

There is no manual style guide for TypeScript code. From STYLE_GUIDE.md:

Anything enforced by linting and formatting is considered a style rule. It is strictly required that you follow all style rules.

Concretely:

  • Biome is the canonical linter and formatter. Config: biome.jsonc. Run pnpm run lint:fix to auto-fix.
  • Prettier handles non-code files (Markdown, JSON, etc.). Config: prettier.config.mjs. Run pnpm run format.
  • ESLint (eslint.config.js) supplies a small set of project-specific rules on top of Biome (mostly around regex safety and TypeScript-eslint).
  • knip (knip.js) detects unused exports during pnpm run lint.
  • The .git-blame-ignore-revs file lists formatting commits to ignore in git blame.

Runtime / non-runtime split

The use of Node.js APIs (e.g. node:) is limited and should be done only in specific parts of the code. Code that is runtime-agnostic should be placed inside folders or files called runtime (runtime/ or runtime.ts). — CONTRIBUTING.md

This applies to packages/astro/src/. node: imports are allowed in:

  • packages/astro/src/core/ (Node-only)
  • packages/astro/src/cli/
  • Vite plugin implementations (the plugin file itself, not the virtual modules it returns)

They are forbidden in:

  • packages/astro/src/runtime/server/ and packages/astro/src/runtime/client/
  • Files literally named runtime.ts or directories called runtime/
  • Virtual modules emitted by Vite plugins

If you import a Node-only helper from runtime code, the build either fails on Cloudflare Workers or Vercel Edge at runtime.

Public vs. internal exports

packages/astro/package.json declares two export maps:

  • "exports" — the monorepo view, with ./_internal/* subpaths used by sibling packages.
  • "publishConfig.exports" — the npm view, which pnpm rewrites to "exports" at publish time. Internal subpaths never ship.

When you add an export:

Visibility "exports" "publishConfig.exports"
Public Yes Yes
Internal Yes (under ./_internal/) No

packages/astro/test/exports.test.ts enforces that the two stay in sync.

When importing from another workspace package, prefer the named subpath over a deep relative path:

// Good
import { loadFixture } from 'astro/_internal/test/test-utils';
// Bad
import { loadFixture } from '../../../astro/test/test-utils.js';

Decoupling business logic from infrastructure

CONTRIBUTING.md documents this pattern at length, with a worked createKey example. The shape:

  • Infrastructure: external systems (filesystem, network, crypto, time, terminal).
  • Business logic: pure logic.

Make external dependencies explicit by passing them as parameters or via abstract interfaces. The cli/ module is the most consistent example — every concrete dependency has an Infra*Provider class and a matching interface in cli/definitions.ts. See for example packages/astro/src/cli/info/infra/cli-debug-info-provider.ts paired with packages/astro/src/cli/info/core/info.ts. Tests then pass SpyLogger, FakeKeyGenerator, etc.

This is the convention "to follow when writing new code". Older code does not always follow it; do not refactor unprompted.

TypeScript boundaries

  • TypeScript 5.9.x, module: NodeNext, moduleResolution: NodeNext. See tsconfig.base.json.
  • Type-only imports use import type { … }. Biome's useImportType rule enforces this.
  • Centralized types live in packages/astro/src/types/, split into internal/ and public/. Public types must be re-exported from astro to be considered API.
  • Zod is used for runtime validation of user config and content schemas. Astro re-exports its bundled Zod via astro/zod for parity with user-facing APIs (zod/v4 is what the schemas are written against).

Errors

Astro errors are defined declaratively in packages/astro/src/core/errors/errors-data.ts (the second-largest single file in the repo at 2,261 lines). Each error has a code, a title, a message function, and a hint. Throwing should use AstroError from core/errors/errors.ts. There is also a separate userError.ts exposed via astro/errors for integrations.

The dev overlay error display lives in packages/astro/src/core/errors/dev/ and the Vite plugin in packages/astro/src/vite-plugin-overlay/.

Logging

packages/astro/src/core/logger/ defines the abstract logger. Concrete implementations (node, console, JSON) live in core/logger/impls/. The CLI exposes its own pretty implementations via cli/infra/. Public logger entrypoints are exported as astro/logger, astro/logger/node, etc.

SKIP_FORMAT is a sentinel label that bypasses the logger's prefix formatting. Use it sparingly.

Telemetry

Astro telemetry is opt-out and runs through packages/telemetry/. Events are defined in packages/astro/src/events/index.ts. The CLI calls notify() and update() to surface and toggle telemetry, gated by astro telemetry enable|disable|reset. Never log PII.

Changesets

Be sure to add a changeset when something has changed with Astro. Non-packages (examples/*) do not need changesets. — CONTRIBUTING.md

Use pnpm exec changeset and pick patch, minor, or major. The release PR aggregates changesets — see lore for how releases work.

Naming & file conventions

  • Filenames are kebab-case.ts (with notable exceptions for class-only files such as RenderContext lowercased to render-context.ts).
  • Class names are PascalCase.
  • Test files end in .test.js (mocha) or .test.ts (newer node:test packages, see packages/astro/scripts and pnpm-workspace.yaml).
  • Vite plugin functions are named vitePluginX(...) and live in vite-plugin-x.ts or vite-plugin-x/index.ts.
  • The _internal prefix is reserved for un-published export subpaths.
  • astro: is reserved for virtual module names.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Astro wiki | Factory