Adding Keystatic to an Astro project


This guide assumes you have an existing Astro project with SSR and output: 'hybrid' configured.

If you don't have an existing Astro project, you can create a new one with the following command:

npm create astro@latest

Installing dependencies

Add Astro's integrations for Markdoc and React using the astro add command:

npx astro add react markdoc

You will also need two Keystatic packages:

npm install @keystatic/core @keystatic/astro

Creating a Keystatic config file

A Keystatic config file is required to define your content schema. This file will also allow you to connect a project to a specific GitHub repository (if you decide to do so).

Create a file called keystatic.config.ts in the root of the project and add the following code to define both your storage type (local) and a single content collection (posts):

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

export default config({
  storage: {
    kind: 'local',
  collections: {
    posts: collection({
      label: 'Posts',
      slugField: 'title',
      path: 'src/content/posts/*',
      format: { contentField: 'content' },
      schema: {
        title: fields.slug({ name: { label: 'Title' } }),
        content: fields.document({
          label: 'Content',
          formatting: true,
          dividers: true,
          links: true,
          images: true,

Keystatic is now configured to manage your content based on your schema.

Setting up the Keystatic Admin UI

First, create a file in the project root, alongside your Keystatic config file:

  import { makePage } from '@keystatic/astro/ui'
  import keystaticConfig from './keystatic.config'

  export const Keystatic = makePage(keystaticConfig)

Next, create a new page called src/pages/keystatic/[...params].astro that mounts the component on the client side only:

// src/pages/keystatic/[...params].astro
import { Keystatic } from '../../../'
export const prerender = false

<Keystatic client:only />

Create a new file called src/pages/api/keystatic/[...params].ts that will create API routes for Keystatic’s Admin UI:

// src/pages/api/keystatic/[...params].ts
import { makeHandler } from '@keystatic/astro/api'
import keystaticConfig from '../../../../keystatic.config'

export const all = makeHandler({
  config: keystaticConfig,

export const prerender = false

Go to your package.json and add --host to your "dev" script:

"dev": "astro dev --host"

Finally, go to astro.config.mjs in the project root and add output: 'hybrid' to defineConfig options. Your astro.config.mjs should look similar to this:

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import markdoc from '@astrojs/markdoc';

export default defineConfig({
  integrations: [react(), markdoc()],
  output: 'hybrid',

You can now launch the Keystatic Admin UI. Start the Astro dev server:

npm run dev

Visit to see the Keystatic Admin UI running.

Creating a new post


In our Keystatic config file, we've set the path property for our posts collection to src/content/posts/*.

As a result, creating a new post from the Keystatic Admin UI should create a new content directory in the src directory, with the new post .mdoc file inside!

Go ahead — create a new post from the Admin UI, and hit save.

You will find your new post inside the src/content/posts directory:

└── content
    └── posts
        └── my-first-post.mdoc

Navigate to that file in your code editor and verify that you can see the Markdown content you entered. For example:

title: My First Post

This is my very first post. I am **super** excited.

Rendering Keystatic content


Keystatic provides its own Reader API to pull data from the file system into your frontend. This guide leverages Astro's built-in content collections instead.

Displaying a collection list

The following example displays a list of each post title, with a link to an individual post page:

import { getCollection } from 'astro:content'

const posts = await getCollection('posts')
  { => (
      <a href={`/posts/${post.slug}`}>{}</a>

Displaying a single collection entry

To display content from an individual post, you can import and use Astro's <Content /> component to render your content to HTML:

import { getEntry } from 'astro:content'

const post = await getEntry('posts', 'my-first-post')
const { Content } = await post.render()

  <Content />

Deploying Keystatic + Astro

Because Keystatic needs to run serverside code and use Node.js APIs, you will need to add an Astro adapter to deploy your project.

You will also probably want to connect Keystatic to GitHub so you can manage content on the deployed instance of the project.