Publish Markdown content to Framer CMS.
This tool allows Git to become the source of truth for Framer CMS content. Content can be stored as Markdown, reviewed through pull requests, generated or updated by AI workflows, and published automatically to Framer.
Markdown → Git → Review → Framer CMS
Framer is excellent for designing and publishing websites. However, as content becomes increasingly AI-generated and AI-maintained, the CMS UI can become a bottleneck.
Markdown is the native format for many AI workflows.
Compared to HTML or CMS-specific formats, Markdown is:
- Easier for LLMs to generate and modify
- More token-efficient
- Human-readable
- Easy to review and edit manually
- Supported by virtually every AI and developer tool
As a result, content generation pipelines, AI agents, and human authors can all work against the same format.
When content is generated and updated by AI, reviewing changes becomes more important than editing content directly.
Git provides capabilities that traditional CMS interfaces struggle to match:
- Meaningful diffs and change reviews
- Pull-request based approval workflows
- Full audit trail and history
- Easy rollback of problematic AI-generated changes
- Native integration with tools such as Claude Code, Cursor, and GitHub Actions
- Direct filesystem access for scripts and automation
This makes Git a great source of truth for AI-managed content.
Content is authored and maintained as Markdown files in Git, while Framer remains responsible for design, layout, and publishing.
The primary purpose of this project is to sync Markdown content into Framer CMS automatically. Exporting Framer content to Markdown exists mainly to bootstrap an existing Framer collection into a Git-based workflow.
npm install
npm run buildOr run directly via npx:
npx framer-to-markdown --token <FRAMER_TOKEN_> --config config/blog.yml
npx markdown-to-framer --token <FRAMER_TOKEN> --config config/blog.ymlYou can also provide the token via an environment variable:
export FRAMER_TOKEN=<token>Each sync target is defined in a YAML file.
Example:
site: https://framer.com/projects/MySite-37823594648842342348-12345
collection: Blog
content: content/blog
markdown: sections
metadata:
- Title
- Team| Option | Required | Description |
|---|---|---|
site |
Yes | Framer project URL or project ID. |
collection |
Yes | Framer CMS collection name or ID. |
content |
No | Directory containing Markdown files. Defaults to content/<collection>. |
markdown |
Yes | Defines where page content is stored. Either a Framer field name (e.g. Content) or the special value sections. |
metadata |
No | List of Framer fields that should be mapped to YAML frontmatter. These fields are copied verbatim between Framer and frontmatter. |
When markdown: sections is used:
Section NN HTMLfields are combined into a single Markdown document during export- Markdown headings are split back into sections during import
Section NN Titlefields are populated from the first heading of each section
This is useful for content where sections should remain individually addressable in Framer. Each section can be rendered with its own anchor links and table of contents entries, improving navigation for both users, search engines (SEO), and AI chatbots that consume web content. Structured pages with stable section anchors are generally easier to reference, cite, and retrieve than a single large content block.
CMS items are matched by their slug.
- Existing items are updated when the slug already exists
- New items are created when the slug does not exist
- If no slug is present, one is generated from the filename
Example:
---
slug: customer-feedback-analysis
Title: Customer Feedback Analysis
Team: Product
---
# Overview
Content goes here.
## FAQ
### Why do I see this error?
This is due to...Typically used once to bootstrap an existing Framer collection into Git:
npx framer-to-markdown \
--token="$FRAMER_TOKEN" \
--config=config/blog.ymlThe exporter:
- Reads all items from the collection
- Converts Framer HTML content to Markdown
- Copies configured metadata fields into frontmatter
- Removes Framer HTML comments
- Formats output using Prettier
- Writes one Markdown file per CMS item
npx markdown-to-framer \
--token="$FRAMER_TOKEN" \
--config=config/blog.ymlThe importer:
- Reads all Markdown files
- Parses YAML frontmatter
- Maps metadata fields back to Framer
- Converts Markdown to Framer content
- Creates or updates items by slug
For FAQ-style content, JSON-LD FAQ schema is generated automatically and written to a JSON-LD field when present.
Import is additive:
- Existing items are updated by slug
- Missing items are created
- Items that exist in Framer but not in Markdown are left unchanged
One way to use this tool is to make Git the source of truth for all CMS content.
Content is stored as Markdown files in a Git repository. Humans, AI agents, and automation workflows interact exclusively with those files. Whenever content changes, a GitHub Action publishes the updates to Framer CMS.
In this setup, Framer becomes primarily a presentation layer. Content is authored, reviewed, versioned, and generated in Git, while Framer remains responsible for design, layout, and publishing.
This effectively creates a headless content workflow on top of Framer CMS.
Example Github Action:
name: Publish to Framer
on:
push:
branches:
- main
jobs:
markdown-to-framer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx markdown-to-framer --token="${{ secrets.FRAMER_TOKEN }}" --config=blog/framer-sync.ymlFlow:
Markdown
↓
Pull Request
↓
Merge
↓
GitHub Action
↓
Framer CMS