Keystatic

Keystatic is a Git-based CMS that stores content as files in your repo. Markdown, JSON, or YAML. No external database, no API keys. Editors use a local or hosted admin UI, and content lives right next to your code.

Last updated: 2026-03-29

Setup

Install Keystatic and configure it alongside your Astro project. Content is stored as files in your repo. No external services needed.

Terminal
# Install Keystatic
npm install @keystatic/core @keystatic/astro

# astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import keystatic from '@keystatic/astro';

export default defineConfig({
  integrations: [react(), keystatic()],
  output: 'hybrid',
});

Configure Content Schema

Define your content collections in a keystatic.config.ts file. Keystatic generates the admin UI from your schema. Editors get forms, image upload, and rich text editing.

Terminal
// keystatic.config.ts
import { config, fields, collection } from '@keystatic/core';

export default config({
  storage: { kind: 'local' },
  collections: {
    posts: collection({
      label: 'Blog Posts',
      slugField: 'title',
      path: 'src/content/posts/*',
      format: { contentField: 'content' },
      schema: {
        title: fields.slug({ name: { label: 'Title' } }),
        excerpt: fields.text({ label: 'Excerpt', multiline: true }),
        coverImage: fields.image({
          label: 'Cover Image',
          directory: 'public/images/posts',
          publicPath: '/images/posts/',
        }),
        publishedDate: fields.date({ label: 'Published Date' }),
        content: fields.markdoc({ label: 'Content' }),
      },
    }),
  },
});

Read Content in Astro

Use Keystatic's reader API to fetch content at build time. It reads directly from your file system. Fast, typed, and zero network overhead.

Terminal
---
// src/pages/blog/index.astro
import { createReader } from '@keystatic/core/reader';
import config from '../../keystatic.config';

const reader = createReader(process.cwd(), config);
const posts = await reader.collections.posts.all();
---

{posts.map((post) => (
  <a href={`/blog/${post.slug}`}>
    <h2>{post.entry.title}</h2>
    <p>{post.entry.excerpt}</p>
  </a>
))}

Best Practices

Keystatic is ideal for developer-friendly teams who want content versioned in Git.