Skip to content

TypeScript & Content Types

Astro ships TypeScript support out of the box — you can write .ts files, type your component scripts, and get editor type-checking with zero setup. A new project extends one of Astro’s presets in tsconfig.json:

{
"extends": "astro/tsconfigs/strict"
}

The presets are base, strict, and strictest — pick strict (or strictest) for real projects; it turns on the checks that catch actual bugs.

You type a component’s props by declaring a Props interface in the frontmatter. Astro uses it to type-check every place the component is used:

---
interface Props {
title: string;
count?: number; // optional prop
}
const { title, count = 0 } = Astro.props;
---
<h1>{title} ({count})</h1>

Pass a wrong type or forget a required prop, and the editor (and astro check) flags it at the call site.

This is where Astro’s typing shines. When you define a content collection with a Zod schema (from the content module), Astro generates TypeScript types from that schema. getCollection and getEntry then return fully-typed entries — your editor knows every frontmatter field and its type:

import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
// posts[0].data.title -> string (from the schema)
// posts[0].data.pubDate -> Date (z.coerce.date())
// posts[0].data.nope -> type error: property does not exist
flowchart LR
  schema["Zod schema in src/content.config.ts"] --> gen["Astro generates types"]
  gen --> query["getCollection / getEntry return typed data"]
  query --> safe["type errors on wrong field access"]
Your schema becomes your types

These types live in a generated .astro/ folder that Astro maintains for you (refreshed by the dev server and by astro sync). You never edit them; you just get autocomplete and safety for free from the schema you already wrote.

When you use middleware to attach request-scoped data (like the current user) to Astro.locals, you type it by declaring the App.Locals interface, usually in src/env.d.ts:

src/env.d.ts
declare namespace App {
interface Locals {
user: { id: string; name: string } | null;
}
}

Now Astro.locals.user is typed everywhere — in middleware, pages, and endpoints.

Editors type-check as you go, but .astro files aren’t checked by tsc. The command that does it is astro check — it validates types across your .astro, .ts, and .tsx files. Wire it into your build so type errors block a broken deploy:

{
"scripts": {
"build": "astro check && astro build"
}
}
How do you type a component’s props in Astro?
Where do the types for `getCollection('blog')` come from?
How do you type data attached to `Astro.locals` (e.g. by middleware)?
What does `astro check` do?