md2gdocs
DownloadDocsDemoToolsBlog
  1. Home›
  2. Blog›
  3. What md2gd Syncs, and What It Refuses To (on Purpose)

What md2gd Syncs, and What It Refuses To (on Purpose)

2026-08-30·8 min read

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 · syncedYellow · conflict handledRed · unsupported
  • 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_ALIGN TABLE
  • 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_LINK FOOTNOTE
  • Horizontal rules HRULE
  • Headings past H6 and nested quotes HEADING_TOO_DEEP NESTED_BLOCKQUOTE
  • Highlights, page breaks, equations, TOC HIGHLIGHT UNKNOWN_ELEMENT TABLE_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.

![architecture diagram](./diagram.png)

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-grid
  • IMAGE — an inserted image or drawing
  • HIGHLIGHT — highlighted or colored text: a text background color or a non-default font color
  • TABLE_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.
md2gdocs

Local Markdown and the real Google Doc, kept in two-way sync. Block by block.

Product

  • How it works
  • Features
  • Pricing
  • Download

Resources

  • Client docs
  • Free tools
  • Blog

Legal

  • Privacy
  • Terms
© md2gdDocument content flows straight from your machine to Google. We never store your documents.