What md2gd Syncs, and What It Refuses To (on Purpose)
TL;DR
A doc freezes only when it holds a construct md2gd can't round-trip losslessly, like an aligned table, an image, or a task list. The most reliable workaround is to keep those out of the file in the first place: if an AI agent drafts your docs, add a rules file that tells it to write only the Markdown md2gd syncs. md2gd generates one for Claude Code, Cursor, GitHub Copilot, Codex (AGENTS.md), and Windsurf. Get the rules file for your agent and it stops producing the constructs that freeze a doc. The rest of this post covers what syncs, what doesn't, and why.
If a linkage ever shows a red "unsupported" status, nothing is broken and nothing was changed. md2gd found a construct it cannot move between Markdown and a Google Doc without risking your content, so it stopped instead of guessing. This post lists exactly what md2gd syncs today, exactly what it refuses, with an example of each, and the reason a refusal is safer than a best-effort conversion.
The one rule
md2gd keeps one Markdown file and one Google Doc in sync by reducing both sides to a canonical form, splitting each into blocks, and hashing every block. A three-way compare of those hashes is how it decides what changed on each side and merges the two.
That only works if every supported construct round-trips losslessly: Markdown to Doc and back must produce the exact same bytes every time. A feature earns a place in the supported set only when that mapping is exact and reversible. Anything else is refused, because a lossy conversion would either corrupt your document or make the hashes drift on every sync and trigger conflicts on a file nobody touched.
Sync has three states, like a traffic light:
- Green (
synced): both sides match. - Yellow: a conflict was handled automatically and sync keeps flowing.
- Red (
unsupported): one doc is frozen until you remove the construct. Every other linkage keeps syncing, the file stays fully editable, and neither side is modified or locked.
What syncs, and what doesn't
At a glance:
Syncs today
- Headings, H1 through H6
- Bold, italic, and inline code
- Inline links
- Bullet and numbered lists, one level of nesting
- Fenced code blocks
- Single-level blockquotes
- Simple tables, as a monospace pipe-grid
Freezes the doc
- Column-aligned or malformed tables
TABLE_ALIGNTABLE - Images and inserted drawings
IMAGE - Task lists with checkboxes
TASK_LIST - List nesting past one level
NESTED_LIST - Mixed ordered and unordered lists
MIXED_LIST - Strikethrough
STRIKETHROUGH - Reference-style links and footnotes
REF_LINKFOOTNOTE - Horizontal rules
HRULE - Headings past H6 and nested quotes
HEADING_TOO_DEEPNESTED_BLOCKQUOTE - Highlights, page breaks, equations, TOC
HIGHLIGHTUNKNOWN_ELEMENTTABLE_OF_CONTENTS - Unterminated code fence
UNTERMINATED_CODE_FENCE
Every supported construct has an exact Markdown to Google Docs mapping. Headings map to the Docs HEADING_1 through HEADING_6 styles, inline code becomes a Roboto Mono run, and a simple table is rendered as an aligned monospace pipe-grid rather than a Docs table object, so it fits the block model and round-trips cleanly.
A few alternate spellings are accepted on input and normalized rather than refused: __bold__ becomes **bold**, and a bare autolink like <https://example.com> becomes an inline link. So writing underscores for bold does not freeze a doc.
For how this compares to Google's own built-in Markdown paste, see Google Docs Markdown: what works and what breaks.
What md2gd refuses, with an example of each
Every refused construct has a stable code. When a doc freezes, its status reads like 241 unsupported: HRULE, TASK_LIST, TABLE_ALIGN (local) — those codes are exactly the headings below, so you can match a status to its cause. md2gd resolve prints the same codes, each with a line number for the file or a short text anchor for the Doc.
Codes raised by the Markdown file
TABLE_ALIGN — column-aligned tables
A plain table syncs. Add colon alignment to the delimiter row and it is refused, because the alignment carries formatting the monospace grid does not represent.
| Feature | Status |
| :------ | -----: |
| Sync | On |
The same table without the colons syncs fine:
| Feature | Status |
| ------- | ------ |
| Sync | On |
TABLE — a table row with no valid delimiter
A row of pipes that is not backed by a valid delimiter row is not a table md2gd can build, so it is refused rather than pushed into the Doc as literal pipes. A real table inserted in the Google Doc raises this same code from the other side.
| Name | Role |
| Ann | Lead |
TASK_LIST — task lists
The checkbox markers have no place in the supported list model.
- [ ] draft the spec
- [x] review it
NESTED_LIST — list nesting deeper than one level
One level of nesting is exactly two spaces and round-trips unambiguously. A second level does not, so it is refused. A Doc bullet at nesting level two or deeper raises the same code.
- Backend
- API
- Auth endpoint
MIXED_LIST — mixed ordered and unordered nesting
A numbered list with bulleted children, or the reverse, has no single canonical form to reduce to.
1. First step
- a note under the number
2. Second step
STRIKETHROUGH — strikethrough
Also raised when a Doc text run has strikethrough styling applied.
~~the deprecated approach~~
REF_LINK — reference-style links
Only inline links are supported.
See the [spec][1] for details.
[1]: https://example.com/spec
FOOTNOTE — footnotes
This claim needs a source.[^1]
[^1]: The source goes here.
HRULE — horizontal rules
Also raised for a horizontal rule inserted in the Google Doc.
Section one.
---
Section two.
IMAGE — images
Also raised for an image or drawing inserted in the Google Doc.

HEADING_TOO_DEEP — headings past H6
Seven or more # characters is deeper than the Docs heading styles go.
####### Too deep
NESTED_BLOCKQUOTE — nested blockquotes
A single level of quoting is supported; a quote inside a quote is not.
> outer quote
> > nested quote
UNTERMINATED_CODE_FENCE — an unterminated code fence
An opening code fence with no matching closing fence leaves the parser unable to tell where the block ends, so the scan stops there and reports this code.
Codes raised by the Google Doc
These have no Markdown to type. They are inserted directly in the Doc and have no exact Markdown equivalent:
TABLE— a real inserted table (Insert, then Table), as opposed to md2gd's monospace pipe-gridIMAGE— an inserted image or drawingHIGHLIGHT— highlighted or colored text: a text background color or a non-default font colorTABLE_OF_CONTENTS— an inserted table of contents (Insert, then Table of contents)UNKNOWN_ELEMENT— the catch-all for any Docs element outside the supported set, including page breaks, block equations (Insert, then Equation), and embeds or drawings
Why refusing beats converting
Every other approach to Markdown and Google Docs either drops these constructs on the floor or turns them into raw characters. That is fine for a one-time paste. It is not fine for a two-way sync that runs continuously, because a lossy conversion compounds:
- If md2gd converted an aligned table by quietly dropping the alignment, the next pull would produce Markdown that no longer matches what you wrote, the block hash would change, and md2gd would report an edit you never made.
- If it guessed at a deeply nested list, the reconstructed indentation would drift between syncs, so the same untouched list would conflict again and again.
Refusing avoids both. When md2gd cannot represent something exactly, it freezes that one doc and tells you why, rather than corrupting the document or filling your history with phantom conflicts. This is the same reason a plain table syncs but an aligned one does not: one has an exact reversible mapping and the other does not.
What to do when a doc is frozen
Every reconcile scans the whole file and the whole Doc and reports the complete set of offenders at once, so you fix everything in one pass instead of discovering them one at a time. Run:
md2gd resolve <file>
It lists each unsupported construct with its exact location: a line number for the file, and a short surrounding-text anchor for the Doc. Remove or rewrite the flagged items, for example delete a horizontal rule or drop the colons from an aligned table, and the doc returns to green on the next sync. Nothing was ever modified for you, so there is nothing to undo.
Related reading
- To stop an AI agent from writing constructs that freeze a doc, add the rules file for your agent.
- Keep Markdown and Google Docs in sync
- Google Docs Markdown: what works and what breaks
- Why AI Markdown breaks in Google Docs
- To build a clean table before you sync, use the Markdown table generator.