MIGRATE FROM
TINACMS
Remove the config folder, drop the local dev server, and edit content from a browser tab instead.
TRY MD0 FREEHOW TINACMS WORKS, AND WHY SOME TEAMS MOVE ON
TinaCMS was built by the Forestry team after Forestry shut down in April 2023. The goal was to replace Forestry's hosted editing experience with something open-source that teams could run themselves. The result works, but it comes with meaningful setup overhead.
At its core, TinaCMS wraps your markdown files with a generated GraphQL schema. Your content still lives in .md or .mdx files in your repository, but TinaCMS generates a typed GraphQL layer on top so that your SSG can query content through client.queries.xxx() calls instead of reading files directly from the filesystem.
To use TinaCMS you add a tina/ directory to your repo. Inside it sits a config.ts file where you define your collections, fields, and media configuration. This config is checked into version control and stays in your repository permanently. It is not a one-time scaffold that disappears after setup.
For local editing, you run npx tinacms dev alongside your normal SSG dev server. That means two terminal processes running at the same time: one for your site, one for the TinaCMS layer. In production, TinaCMS Cloud handles the editing backend. The free tier has user and content limits; larger teams move to a paid plan or self-host the Tina backend.
Teams with simple blogs or documentation sites sometimes find this stack heavier than they need. If you are not using TinaCMS's GraphQL queries in your build pipeline, you are carrying the overhead without getting much of the benefit.
HOW MD0 IS DIFFERENT
TINACMS
- →
tina/config.tslives in your repo - →GraphQL schema generated at build time
- →Run
npx tinacms devalongside your site - →Tina Cloud or self-hosted backend for production
- →Inline visual editing (edits overlay your actual site)
MD0
- →Zero files added to your repository
- →No GraphQL layer; reads markdown directly
- →No local dev server to run
- →Pure SaaS at cms.md0.io
- →Separate editor panel (not inline on your site)
md0 stores zero configuration in your repository. Collection definitions, field schemas, and media settings all live inside md0's web UI at cms.md0.io. There is no tina/ folder, no generated GraphQL client, and no additional npm packages to install.
Authentication runs through GitHub OAuth. When you connect a repository, md0 reads your markdown files directly from the GitHub API. Edits are committed back to your repo as standard Git commits. Your build pipeline stays unchanged because the files on disk are the same files it always read.
The editing experience in md0 is a standalone web app, not an overlay on your site. You open cms.md0.io, pick your collection, edit the content, and save. md0 pushes a commit to your chosen branch. Your CI/CD picks it up and deploys as normal.
TRADE-OFFS TO UNDERSTAND
TinaCMS has real strengths that md0 does not replicate. Before switching, check whether any of these matter for your project.
Inline visual editing. TinaCMS renders your actual site in an iframe and overlays editing controls directly on the page. You see the live design while you type. md0 uses a separate editor panel. What you see is a structured form and a markdown preview, not your site's rendered output. If your content team relies on seeing the site design during editing, that is a meaningful difference.
Type-safe content queries. TinaCMS generates a TypeScript client from your collection schema. Your Next.js or Nuxt data-fetching code gets typed return values fromclient.queries.post() without any extra work. md0 does not generate a type layer. You read files from the filesystem using gray-matter or your framework's content APIs, and you type those results yourself.
Content references and relationships. TinaCMS supports reference fields that link one document to another with GraphQL joins. md0 is better suited for flat markdown content: collection of posts, a collection of docs pages, a collection of team members. If your schema has complex cross-collection references, md0 may not cover the full use case.
If inline editing and GraphQL type safety are central to your workflow, factor the migration work carefully. If you have a straightforward markdown-based site and mainly want a clean editing UI without config overhead, md0 is a solid fit.
WHAT CHANGES WHEN YOU SWITCH
The migration touches your build code and your repository structure, but it does not touch your actual content. Here is what changes and what stays put.
WHAT GETS REMOVED
- ✕
tina/directory andconfig.ts - ✕
@tinacms/cli,tinacms, and related packages - ✕GraphQL queries in your data-fetching code
- ✕The
tinacms devscript frompackage.json
WHAT STAYS THE SAME
- Your markdown and MDX files, untouched
- Your frontmatter keys and values
- Your GitHub repo and full Git history
- Your deployment pipeline and hosting
- Media files already in your repo
- Your URL structure and site output
MIGRATION STEPS
Document your TinaCMS collections
Open tina/config.ts and note each collection name, the folder path it points to, and every field definition. This becomes your reference when recreating collections in md0.
Map TinaCMS field types to md0 field types
The mapping is mostly one-to-one: string becomes text, datetime becomes date/time, image becomes image, reference becomes text (since md0 does not model cross-collection references natively). Boolean, number, and rich-text fields map directly.
Connect your GitHub repo to md0
Sign in at cms.md0.io with GitHub OAuth. Grant md0 access to the repository that contains your content. No install, no CLI, no webhook configuration.
Create collections in md0 to mirror your TinaCMS collections
For each collection in tina/config.ts, create a matching collection in md0 with the same folder path and field definitions. md0 will read and write files from the same directories TinaCMS used.
Update your SSG data-fetching code
Replace client.queries.xxx() GraphQL calls with direct filesystem reads. For Next.js use fs.readFileSync + gray-matter. For Astro use getCollection() or import.meta.glob. For Hugo, files were already read from disk so nothing changes there.
Test the build end to end
Run your site build and confirm content renders correctly without the Tina data layer. Check that all collection paths resolve and that frontmatter fields are parsed as expected.
Remove TinaCMS packages from package.json
Uninstall @tinacms/cli, @tinacms/react, tinacms, and any other tina-prefixed packages. Run your package manager install to clean the lockfile.
Delete the tina/ directory and commit
Remove the entire tina/ folder from your repository. Commit the deletion. At this point your repo is free of TinaCMS configuration.
Remove the tinacms dev script
Delete the tinacms dev entry from the scripts section of package.json. Your dev workflow now runs a single process again.
REPLACING TINACMS GRAPHQL QUERIES
The biggest code change is swapping TinaCMS GraphQL queries for direct file reads. Here is a typical before-and-after for a Next.js page that fetches a single post.
import { client } from '@/tina/__generated__/client'
export async function getPostData(slug: string) {
const res = await client.queries.post({
relativePath: `${slug}.mdx`,
})
const post = res.data.post
return post
}import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
export async function getPostData(slug: string) {
const filePath = path.join(process.cwd(), 'content/posts', `${slug}.mdx`)
const file = fs.readFileSync(filePath, 'utf8')
const { data, content } = matter(file)
return { frontmatter: data, content }
}The file path in the after example matches whatever folder TinaCMS was pointing at in its collection config. If TinaCMS had path: 'content/posts' in your config.ts, that same path works directly with fs.readFileSync.
For listing pages that fetch all posts, replace TinaCMS's client.queries.postConnection() with fs.readdirSync() filtered to .md and .mdx files, then parse each with gray-matter. The pattern is the same across collection types.
Related
Still comparing? md0 CMS vs TinaCMS | All migration guides
READY TO REMOVE THE CONFIG LAYER?
Connect your repo at cms.md0.io and start editing without a tina/ folder or a second dev server.
START FREE