All articles

Why ClickUp Docs Goes Stale (And How to Sync Markdown from GitHub Automatically)

ClickUp's markdown support is broken by design, and every standard integration tool hits the same wall. Here's why — and the one pattern that actually works for syncing specs from GitHub to ClickUp Docs.

M
mdspec team April 26, 2026 · 8 min read

If you’ve tried to keep a ClickUp Doc in sync with a markdown file in your GitHub repo, you’ve probably noticed that every path you try leads to the same dead end. Zapier can’t do it. Make can’t do it. Pipedream gets partway there. ClickUp’s own native GitHub integration doesn’t touch Docs at all.

This isn’t a configuration problem. It’s structural — and understanding why it’s structural is the fastest path to a working solution.


The Real State of ClickUp’s Markdown Support

Let’s start with what ClickUp’s feedback board actually says, because the frustration there is instructive.

Years of unresolved requests from developers and AI-tool users cluster around the same complaints:

  • Pasting markdown into a ClickUp Doc produces garbled output. Headers become plain text. Code blocks lose formatting. Tables paste as tab-separated text.
  • ClickUp’s internal format is not markdown. It uses a proprietary rich-text representation that doesn’t map cleanly to standard markdown, meaning round-trip conversion (markdown in, ClickUp format stored, markdown out) is lossy.
  • AI coding tools — Cursor, Claude Code, Copilot Workspace — can’t read ClickUp Docs. They need plain markdown files or URLs to raw text. “ClickUp doesn’t speak the language of nearly all AI tools” is a direct quote from one of the more upvoted feedback threads.
  • The feedback has been open for years. ClickUp has iterated on its editor but hasn’t solved the underlying format mismatch.

This isn’t a complaint about ClickUp as a product — it’s excellent for task management. The mismatch is architectural: ClickUp’s document format was designed for rich web editing, not for interoperability with the plain-text tooling that developers use.


Why Standard Integration Tools Can’t Bridge the Gap

The typical instinct when you want to sync two tools is to reach for Zapier, Make, or Pipedream. Here’s why each fails specifically for ClickUp Docs.

Zapier: ClickUp’s Zapier integration operates on tasks, not Docs. You can create tasks, update task descriptions, and attach files. Docs are a separate product surface that Zapier’s ClickUp connector doesn’t reach. The markdown_description field on tasks supports some markdown, but a task description is not a Doc.

Make: Same limitation. Make’s ClickUp module covers tasks, lists, folders, and spaces. The Docs API is a separate code path that Make’s connector doesn’t implement.

Pipedream: Closer — Pipedream lets you write custom Node.js code against any API, so you can call ClickUp’s Docs API directly. But you’re back to writing and maintaining a custom integration, and ClickUp’s Docs API has its own quirks (more on this below).

ClickUp’s native GitHub integration: This exists and connects your ClickUp workspace to GitHub. What it does: links tasks to PRs and commits, displays PR status in tasks, shows related commits on task cards. What it doesn’t do: touch Docs in any way. It’s entirely on the task-management side of ClickUp.

The common thread: every standard integration layer treats ClickUp as a task management tool, because that’s what ClickUp primarily is and what its most-used APIs cover. ClickUp Docs is a newer, separate product surface with a different API path, and the integration ecosystem hasn’t caught up.


The ClickUp Docs API: What It Can Do

ClickUp does have a Docs API. Here’s what it supports:

  • Create a new Doc in a space or folder
  • Update a Doc’s content
  • Get a Doc’s pages
  • Create or update pages within a Doc

The content model is ClickUp’s proprietary rich-text format (not markdown). To publish markdown content to a ClickUp Doc via the API, you need to either:

  1. Convert your markdown to ClickUp’s rich-text format before writing, or
  2. Use the markdown content type parameter that ClickUp’s API accepts in some endpoints

Option 2 is the practical path. ClickUp’s Docs API can accept a type: "text/markdown" content type in page creation/update requests, which means you can POST raw markdown and ClickUp will handle the conversion server-side. The conversion isn’t perfect — the same format limitations that affect pasting markdown in the UI apply here — but for structured technical specs (headings, paragraphs, code blocks, lists), the output is usable.


The Working Pattern: Git → .mdspecmap → CI → ClickUp Docs

Here’s the approach that works reliably.

The setup:

Keep your specs as plain markdown files in a /specs/ folder in your git repository. Connect ClickUp in the mdspec dashboard and declare the destination in a .mdspecmap placed in each folder you want to sync.

specs/.mdspecmap:

version: 1
mappings:
  - integration: clickup
    parent: alias:specs-doc

docs/runbooks/.mdspecmap:

version: 1
mappings:
  - integration: clickup
    parent: alias:ops-runbooks

Each alias: is a ClickUp Doc alias configured in the mdspec dashboard, pointing at the Doc where pages should appear.

The GitHub Actions step:

name: Publish Specs

on:
  push:
    branches:
      - main
    paths:
      - 'specs/**'
      - 'docs/runbooks/**'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - run: npx mdspeci publish --project ${{ vars.MDSPEC_PROJECT_ID }}
        env:
          MDSPEC_TOKEN: ${{ secrets.MDSPEC_TOKEN }}
          GITHUB_EVENT_BEFORE: ${{ github.event.before }}

Connecting ClickUp in the dashboard:

  1. Go to ClickUp → Settings → Apps and copy your Personal API Token
  2. In the mdspec Dashboard: Integrations → ClickUp → Connect, enter your token and workspace URL
  3. Create aliases pointing at your target ClickUp Docs
  4. In your GitHub repo: add MDSPEC_TOKEN as a secret and MDSPEC_PROJECT_ID as a repository variable

Finding your List ID:

In ClickUp, navigate to the list where you want Docs to live. The list ID is visible in the URL: app.clickup.com/t/[workspace-id]/[space-id]/[folder-id]/[list-id]. Alternatively, use the ClickUp API (GET /api/v2/space/{space_id}/list) to enumerate list IDs programmatically.


What Happens on Each Push

When a spec file changes and the PR merges to main:

  1. GitHub Actions triggers the publish workflow
  2. mdspec reads .mdspecmap and identifies which files changed
  3. For each changed file with a ClickUp destination, mdspec calls the ClickUp Docs API with the markdown content
  4. ClickUp creates or updates the Doc page, converting markdown server-side
  5. The ClickUp Doc reflects the current state of the spec within 60–90 seconds of merge

Ops engineers and product managers who live in ClickUp see current documentation. Developers who live in git see the authoritative source. Neither group has to manually synchronize anything.


Combining ClickUp with Confluence and Notion

The structural advantage of the .mdspecmap approach is that adding ClickUp doesn’t require a separate integration — it’s another destination block in the same config.

If your engineering team reads specs in Confluence, your product team reads in Notion, and your ops team reads runbooks in ClickUp, a single config covers all three:

specs/.mdspecmap:

version: 1
mappings:
  - integration: confluence
    parent: alias:backend-services
  - integration: notion
    parent: alias:product-specs
  - integration: clickup
    parent: alias:auth-service-doc
  - integration: s3
    parent: alias:docs-bucket

docs/runbooks/.mdspecmap:

version: 1
mappings:
  - integration: clickup
    parent: alias:ops-runbooks

One CI step. One push. Four destinations updated. The spec drift problem — where Confluence is one version, Notion is two versions, and ClickUp is three versions behind the source — becomes structurally impossible.

This is the core idea behind spec-as-code: treat your git repo as the single source of truth and use CI as the synchronization mechanism. For a full explanation of the pattern and how to set it up, see Publish Markdown to Confluence with GitHub Actions.


What About Formatting Limitations?

ClickUp’s server-side markdown conversion handles the common cases well:

  • Headings (H1–H6) → ClickUp heading levels
  • Paragraphs and line breaks → standard text blocks
  • Bold and italic → ClickUp inline formatting
  • Inline code and fenced code blocks → ClickUp code blocks
  • Unordered and ordered lists → ClickUp list blocks
  • Tables → ClickUp table blocks (basic support)
  • Blockquotes → ClickUp quote blocks

What renders imperfectly or not at all:

  • Nested lists beyond two levels deep
  • HTML embedded in markdown
  • Some GitHub-Flavored Markdown extensions (footnotes, task lists)
  • Images (need to be separately hosted; ClickUp Docs don’t support embedded images from external URLs as cleanly as Confluence)

For technical specs — descriptions of system behavior, API contracts, runbook procedures — these limitations are usually not an issue. The content that matters most (headings, paragraphs, code blocks, lists) renders correctly.

If your specs use heavy markdown extensions or embedded HTML, you may want to keep Confluence as your primary destination (where conversion fidelity is higher) and use ClickUp for a summary or excerpt rather than the full spec.


Why This Matters More Now

Two things make the ClickUp sync problem more urgent in 2026 than it was two years ago.

AI coding agents. If your team is using Cursor, Claude Code, or similar tools to assist with development, those agents need current specs as context. Specs locked in ClickUp Docs are inaccessible to AI agents — ClickUp Docs don’t have a raw-markdown URL you can pass to an agent’s context. The S3 destination in the .mdspecmap config above solves this: the same CI run that publishes to ClickUp also publishes a machine-readable copy to S3, which AI agents can read directly.

On-call drift. ClickUp is widely used for ops runbooks. A runbook that describes how a system behaved six months ago is not just useless during an incident — it’s actively harmful, because it sends the on-call engineer down the wrong diagnostic path. Automated sync from git means runbooks are always current. The on-call engineer reading the ClickUp Doc is reading the same version as the codebase.


Getting Started

  1. Create a /specs/ or /docs/runbooks/ folder in your repo and move your existing specs there (or create new ones)
  2. Add a .mdspecmap at the repo root with your ClickUp list ID and doc names
  3. Add CLICKUP_TOKEN to GitHub Secrets
  4. Add the GitHub Actions workflow above
  5. Push a change and verify the ClickUp Doc updates automatically

Total setup time: 15–20 minutes if you have your ClickUp API token ready.

The first push is the moment where your ClickUp Docs and your git specs are actually in sync. Every push after that keeps them that way, automatically.


mdspec handles the ClickUp Docs API integration, the markdown conversion, and the multi-destination config described in this post. Get started at mdspec.io.

M

Ship specs. Stay in flow.

mdspec syncs your markdown specs to Notion, Confluence, ClickUp, and S3 — straight from CI. Two files, one step, zero friction.

Get started free