CCFM — Confluence Cloud Flavoured Markdown¶
A CLI tool that converts Markdown to Atlassian Document Format (ADF) and deploys pages to Confluence Cloud. Write documentation as Markdown, deploy it as native Confluence pages — no legacy conversions, no storage format hacks, full editor compatibility.
Features¶
- Native ADF output — Pages open in the Confluence editor without any legacy conversion
- Automatic page hierarchy — Directory structure maps directly to Confluence page hierarchy
- CCFM extensions — Status badges, panels, expands, dates, smart page links, emoji, image width control
- Idempotent — Safe to run multiple times; creates or updates pages automatically
- Remote state — Deployment state stored in Confluence itself, no local files to commit
- Concurrent deploy protection — Terraform-style locking prevents conflicting deploys
- CI/CD ready — Deploy documentation on every commit to your main branch
Full syntax reference: CCFM Syntax Reference
Design Philosophy¶
CCFM follows a single-repo, single-space model. Each ccfm.yaml manages exactly one
Confluence space. All files under docs_root are deployed to that space, and the directory
structure maps directly to the Confluence page hierarchy.
This is an intentional design choice — not a limitation. A single source of truth per space means reliable state tracking, predictable orphan detection, and no conflicts between teams.
If you need to manage multiple Confluence spaces, use separate repositories (or separate config files within one repo) — one per space. See Deployment Patterns for examples.
One space, one repo
Deploying to the same Confluence space from multiple repositories will cause state conflicts.
CCFM's orphan detection treats pages not in the current docs_root as deleted — a second
repo deploying to the same space will destroy the first repo's pages.
Why one repo per space?¶
CCFM's deployment model tracks state — which pages exist, their content hashes, and when they
were last deployed. Orphan detection compares this state against the files in your docs_root
to determine what should be created, updated, or destroyed. This only works when a single source
of truth owns the state.
This is the same constraint that Terraform and other state-based tools enforce: each state file should be managed by exactly one configuration. When two configurations share state, each one sees the other's resources as orphans and plans to destroy them.
Unlike infrastructure tools where a bad destroy can be permanent, CCFM docs live in version control. If pages are accidentally destroyed, recovery is a re-deploy from the correct repository — no data is lost, just temporarily unavailable.
The plan command always shows pending destroy actions before any changes are applied, and
apply requires explicit confirmation (or --auto-approve for CI). These guardrails give
you visibility before any destructive action is taken.
Quick Start¶
1. Get an API token¶
Go to Atlassian API Tokens, create a token, and note your Atlassian email address.
2. Install¶
Or use Docker:
3. Initialise your space¶
Before deploying for the first time, initialise CCFM in your Confluence space. This creates
a _ccfm management page that stores deployment state and lock information.
ccfm \
--domain your-domain.atlassian.net \
--email your.email@example.com \
--token YOUR_API_TOKEN \
--space YOUR_SPACE_KEY \
init
This is idempotent — safe to run multiple times.
4. Write a page¶
---
page_meta:
title: My First Page
labels:
- docs
deploy_config:
ci_banner: false
---
# My First Page
This is **bold** text, this is *italic*.
> [!info]
> This is an info panel.
::In Progress::blue:: ::Stable::green::
5. Preview and apply¶
# See what would change without touching Confluence
ccfm plan
# Apply changes (interactive confirmation)
ccfm apply
# Skip confirmation prompt (for CI)
ccfm apply --auto-approve
6. Inspect ADF output¶
Use --debug-file to convert a single markdown file to ADF JSON and print it to stdout
without making any API calls:
ccfm plan --debug-file path/to/my-page.md
ccfm plan --debug-file path/to/my-page.md | jq '.content[0]'
Page Hierarchy¶
Directories map directly to Confluence pages. A file at docs/Team/Engineering/api.md creates:
By default, container pages (Team, Engineering) are created as placeholders.
To control a container page's title and content, add a .page_content.md file inside the directory:
docs/
└── Team/
├── .page_content.md ← controls the "Team" Confluence page
└── Engineering/
├── .page_content.md
└── api.md
.page_content.md files support full CCFM syntax and frontmatter, including labels and
custom titles.
Container pages and page links¶
Container pages (created from .page_content.md) are deployed before their child pages.
If a .page_content.md contains page links to its own children (e.g., [Overview](<Team Overview>)),
those links cannot resolve on the first deploy because the child pages don't exist yet.
Workaround: Run ccfm apply --force --auto-approve after the initial deploy to re-push
all pages — the child pages now exist and the links will resolve correctly.
Recommended alternative: Instead of manually linking to child pages from a container page, use Confluence's built-in Children Display macro after the initial deploy. This macro automatically lists all child pages and stays up to date as pages are added or removed — no manual link maintenance required. Since the macro is added via the Confluence editor (not the markdown source), it won't be overwritten by subsequent CCFM deploys.
FAQ¶
Multiple teams need to publish documentation — what's the best approach?¶
Option A: Centralised docs repo with team-owned subdirectories. One repository, one Confluence space. Each team owns a directory:
This is the simplest model — one deploy pipeline, one state, clear hierarchy.
Option B: Separate Confluence spaces per team. Each team has their own repository and their own Confluence space. Use a GitLab/GitHub group or org to keep docs repos discoverable. Consider a repo template so new teams can bootstrap quickly with CI pipelines, linting, and CCFM config pre-configured.
Can I deploy from multiple repositories to the same Confluence space?¶
No. CCFM's state tracking and orphan detection assume a single source of truth per space. Deploying from a second repository will destroy pages created by the first. This is by design — it keeps the model simple and predictable.
Can I use CCFM without a config file?¶
For plan --debug-file (ADF inspection), yes — no config or credentials needed. For plan
and apply, you need a ccfm.yaml with at least docs_root configured. Credentials can come
from the config file, CLI flags, or environment variables.
What's Next?¶
- Syntax Reference — Full CCFM syntax with ADF mapping
- CLI Reference — All subcommands, options, and examples
- Configuration — ccfm.yaml, frontmatter, state management, locking
- Deployment Patterns — Single-env, multi-env, multi-source
- Docker & CI/CD — Docker, GitHub Action, pipeline examples