/ astro-integrations / How to Use TinaCMS with Astro: Complete Guide
astro-integrations 5 min read

How to Use TinaCMS with Astro: Complete Guide

Step-by-step guide to integrating TinaCMS with your Astro website.

TinaCMS is a Git-backed headless CMS that gives you visual editing on your actual site while storing all content as Markdown or MDX files in your repository. The key differentiator here is that your content lives in Git, not in a third-party database. Edits made through the visual editor create real commits in your repo. For developers who want version-controlled content with a friendly editing interface, Tina is a compelling option.

With Astro, TinaCMS provides a GraphQL API layer over your local Markdown/MDX files. You define your content schema in code, and Tina generates a type-safe API plus an admin UI that editors can use to modify content without touching files directly.

Prerequisites

  • Node.js 18+
  • An Astro project (npm create astro@latest)
  • A GitHub repository for your project (Tina commits directly to your repo)
  • A Tina Cloud account for the hosted editing experience (free tier available, paid from $29/mo)

Installation

Initialize TinaCMS in your existing Astro project:

npx @tinacms/cli@latest init

This command scaffolds the configuration and adds the necessary dependencies. It creates a tina/ directory with your schema configuration.

If you prefer manual setup:

npm install tinacms @tinacms/cli

Configuration

The init command creates tina/config.ts. Define your content schema here:

// tina/config.ts
import { defineConfig } from "tinacms";

export default defineConfig({
  branch: process.env.TINA_BRANCH || "main",
  clientId: process.env.TINA_CLIENT_ID || "",
  token: process.env.TINA_TOKEN || "",
  build: {
    outputFolder: "admin",
    publicFolder: "public",
  },
  media: {
    tina: {
      mediaRoot: "blog-images",
      publicFolder: "public",
    },
  },
  schema: {
    collections: [
      {
        name: "post",
        label: "Blog Posts",
        path: "src/content/posts",
        format: "mdx",
        fields: [
          {
            type: "string",
            name: "title",
            label: "Title",
            isTitle: true,
            required: true,
          },
          {
            type: "string",
            name: "description",
            label: "Description",
          },
          {
            type: "datetime",
            name: "publishDate",
            label: "Publish Date",
          },
          {
            type: "image",
            name: "heroImage",
            label: "Hero Image",
          },
          {
            type: "string",
            name: "tags",
            label: "Tags",
            list: true,
          },
          {
            type: "rich-text",
            name: "body",
            label: "Body",
            isBody: true,
          },
        ],
      },
    ],
  },
});

Add your Tina Cloud credentials to .env:

TINA_CLIENT_ID=your-client-id
TINA_TOKEN=your-read-only-token
TINA_BRANCH=main

Get these from the Tina Cloud dashboard after connecting your GitHub repository.

Basic Usage

TinaCMS provides a GraphQL client that reads your content. Use it to fetch posts in your Astro pages:

---
// src/pages/blog/index.astro
import { client } from "../../tina/__generated__/client";
import BaseLayout from "../../layouts/BaseLayout.astro";

const postsResponse = await client.queries.postConnection({
  sort: "publishDate",
  last: 50,
});

const posts = postsResponse.data.postConnection.edges || [];
---

<BaseLayout title="Blog">
  <h1>Blog</h1>
  {posts.map(({ node: post }) => (
    <article>
      <a href={`/blog/${post._sys.filename}`}>
        <h2>{post.title}</h2>
        <p>{post.description}</p>
      </a>
    </article>
  ))}
</BaseLayout>

For individual post pages:

---
// src/pages/blog/[slug].astro
import { client } from "../../tina/__generated__/client";
import { TinaMarkdown } from "tinacms/dist/rich-text";
import BaseLayout from "../../layouts/BaseLayout.astro";

export async function getStaticPaths() {
  const postsResponse = await client.queries.postConnection();
  const posts = postsResponse.data.postConnection.edges || [];

  return posts.map(({ node }) => ({
    params: { slug: node._sys.filename },
  }));
}

const { slug } = Astro.params;
const postResponse = await client.queries.post({
  relativePath: `${slug}.mdx`,
});

const post = postResponse.data.post;
---

<BaseLayout title={post.title}>
  <article>
    <h1>{post.title}</h1>
    {post.heroImage && (
      <img src={post.heroImage} alt={post.title} />
    )}
    <div set:html={post.body} />
  </article>
</BaseLayout>

Run the dev server with Tina's admin panel:

npx tinacms dev -c "astro dev"

This starts both Astro's dev server and the Tina admin at /admin. Editors can now create and modify content visually.

Production Tips

  1. Use Tina's local mode during development. Run npx tinacms dev for local development, which reads directly from your filesystem without needing Tina Cloud. This is faster and works offline.

  2. Set up branch-based editing. Tina supports editing on different Git branches. Point your staging environment at a draft branch so editors can preview changes without affecting production content.

  3. Define custom components for rich text. If your MDX uses custom components, register them in Tina's rich-text field configuration. This lets editors insert components like callouts, code blocks, or embeds through the visual editor.

  4. Keep your generated client up to date. Run npx tinacms build after schema changes to regenerate the GraphQL client. The generated types ensure your queries stay in sync with your content model.

  5. Use Tina's media manager. Configure the media field to upload images to your repo's public folder. This keeps all assets version-controlled alongside your content.

Alternatives to Consider

  • Keystatic if you want a similar Git-based CMS with an even simpler setup and an official Astro integration.
  • Prismic if you need a hosted CMS with more advanced slice-based content modeling and scheduled publishing.
  • Decap CMS (formerly Netlify CMS) if you want a free, open-source Git-based CMS with a simpler feature set.

Wrapping Up

TinaCMS gives you the best of both worlds: a visual editing experience for content creators and Git-backed content for developers. The schema-as-code approach means your content model is versioned and reviewable in pull requests. For Astro sites where you want to keep content in your repository while still providing a proper CMS interface, Tina is one of the strongest options available. The free tier handles small teams, and the editing experience is smooth enough that non-technical collaborators can contribute content without learning Markdown.