Convert Spaces
The space convert commands export an entire Confluence space — all its pages — to a directory of Markdown files. The page hierarchy is automatically preserved as nested subdirectories.
This is the main operational workflow for acs2md and the core reason most teams license the product.
If you are planning a real customer run, see Workflows & Recipes for continuity, archive, migration, governance, and AI-corpus patterns built around space convert.
Convert by key
acs2md space convert by-key SPACE_KEY [flags]The space key is the short identifier visible in the Confluence URL: .../wiki/spaces/MYSPACE/. It is also shown in the output of acs2md space list.
Examples
# Convert to the default output directory
acs2md space convert by-key MYSPACE
# Convert to a specific directory
acs2md space convert by-key MYSPACE --output-dir ./docs
# Build a RAG knowledge base — plain text, no images
acs2md space convert by-key MYSPACE \
--exclude-marks=true \
--embed-images=false \
--output-dir ./rag-knowledge-base
# Include only currently published pages
acs2md space convert by-key MYSPACE --status current --output-dir ./docs
# Disable internal link rewriting
acs2md space convert by-key MYSPACE --rewrite-links=false --output-dir ./site
# Incremental sync — only re-convert changed pages, keep deleted files
acs2md space convert by-key MYSPACE --incremental --output-dir ./docs
# Full sync — re-convert changed pages, delete files for removed pages
acs2md space convert by-key MYSPACE --sync --output-dir ./docsFlags
| Flag | Short | Default | Description |
|---|---|---|---|
--output-dir | -o | ./output/<SPACE_KEY>/ | Directory to write Markdown files into |
--status | -s | current | Filter pages by status: current, archived, trashed, deleted, draft |
--conflict-resolution | overwrite | How to handle filename conflicts: overwrite, suffix, skip, versioned | |
--rewrite-links | true | Rewrite internal Confluence URLs to local relative paths | |
--sync | false | Track .convert-sync-state.json and only re-convert changed pages. Deletes local files for pages removed from Confluence. Mutually exclusive with --incremental. | |
--incremental | true | Like --sync, but keeps local files for removed pages. Mutually exclusive with --sync. | |
--concurrency | 4 | Pages converted at once. 1 restores strictly sequential conversion — see Throughput. |
Convert by ID
acs2md space convert by-id SPACE_ID [flags]Converts a space identified by its numeric ID instead of its key. Same flags as by-key.
acs2md space convert by-id 65539 --output-dir ./docsConversion flags
These flags are available on both by-key and by-id and control how each page is rendered.
Content & metadata
| Flag | Short | Default | Description |
|---|---|---|---|
--include-metadata | -m | true | Add a YAML front matter block to each file (title, author, created/modified dates) |
--exclude-marks | -x | true | Strip all inline formatting: bold, italic, underline, colours. Set --exclude-marks=false if you want to preserve rich inline formatting. |
--include-labels | -l | true | Fetch page labels for the front matter tags field. Costs one API call per page. |
--resolve-users | true | Resolve front matter author IDs to display names. Costs extra API calls; false leaves the raw account IDs. | |
--link-mentions | true | Link @mentions to their Confluence people profile | |
--space-key | — | Space key for the --ext-render-* macros that need one. by-key already knows it; set it explicitly with by-id. | |
--validate-strict | false | Fail the conversion on strict ADF validation errors instead of converting on a best-effort basis |
Images & media
| Flag | Short | Default | Description |
|---|---|---|---|
--embed-images | -e | true | Embed images as base64 data URIs |
--ext-embed-drawio | -D | true | Embed draw.io diagrams as base64 images |
--ext-embed-roadmap | true | Embed Roadmap Planner diagrams as base64 images | |
--embed-media-images | true | Embed Confluence media files (images, avatars) as base64 data URIs | |
--image-sidecars | false | Write images to attachments/<page-id>/ and link them relatively, instead of embedding base64 | |
--media-dimensions | false | Render sized media as an HTML <img> so the width survives |
Pass --embed-images=false to keep image references as external URLs. This
produces smaller output files but requires network access to view them.
--image-sidecars is the middle ground: the bytes stay on disk, so the export
is still self-contained, but no page carries a 100 KB single-line data URI.
Obsidian & output style
| Flag | Default | Description |
|---|---|---|
--obsidian | false | Preset: Obsidian callouts, heading anchors Obsidian resolves, and image sidecars |
--panel-style | github | How Confluence panels render: github (alerts), obsidian (callouts), emoji (emoji blockquotes) |
--wikilinks | false | Render internal inline cards as [[Page Title]]. Needs --ext-resolve-inline-card-titles. |
--inline-tags | false | Append page labels as #tag on a trailing line, on top of the front matter tags field |
--obsidian is a preset, not a lock — an explicit flag always wins. See
Obsidian Vaults for what it sets, a per-feature
compatibility matrix, and a symptom-to-fix table.
Extension flags
| Flag | Default | Description |
|---|---|---|
--ext-render-toc | true | Generate a Markdown table of contents from document headings |
--ext-render-recently-updated | true | Render the recently-updated macro as a Markdown list |
--ext-render-listlabels | true | Render label badges |
--ext-render-pagetree | true | Render the pagetree macro as a nested Markdown list |
--ext-render-children | true | Render the children macro as a flat Markdown list |
--ext-render-contributors | true | Render the contributors macro as a Markdown list |
--ext-render-page-signatures | true | Render Document Control signature tables |
--ext-render-qc-properties | true | Render QC property and revision macros with their actual values |
--ext-render-task-report | true | Render tasks-report-macro as a Markdown table |
--ext-render-content-report | true | Render content-report-table macro as a Markdown table |
--ext-render-cql-query | true | Run the CQL Query macro and render its results as a Markdown list |
--ext-resolve-inline-card-titles | true | Resolve inline card links to their actual page titles |
Every --ext-render-* flag above costs extra API calls, because the macro has
to be resolved against Confluence rather than read from the page body.
--ext-render-listlabels and --ext-render-recently-updated additionally need
a space key — by-key already has it, by-id needs --space-key.
Throughput
space convert converts 4 pages at once by default. Conversion is network-bound — several API calls per page — so overlapping a few pages hides most of the per-page latency.
# Raise it for a large space on a tolerant tenant
acs2md space convert by-key DEVOPS --output-dir ./docs --concurrency 8
# Restore strictly sequential conversion
acs2md space convert by-key DEVOPS --output-dir ./docs --concurrency 1The useful ceiling is your Confluence tenant’s rate limit, not the number of CPU cores, which is why the default is deliberately modest. Raising it is safe because the HTTP client honours the Retry-After header: when Confluence answers a throttled request with 429 or 503 and names a delay, the client waits exactly that long rather than guessing. The server sets the pace instead of the client discovering it by failing.
Output is identical at every setting. Pages are collected in input order
regardless of which worker finishes first, so the directory layout,
--rewrite-links and the summary counts do not change when you raise
--concurrency.
Each command also reports how long it took:
✅ Sync complete!
✅ Converted: 835
❌ Failed: 0
🔗 Links rewritten: 835 files
📂 Output: ~/tmp/docs-sec-obsidian/SEC
⏱️ Took: 2m 14sThe figure covers the whole command, API calls and image downloads included.
A second Ctrl+C force-quits. The first one cancels cleanly and lets the
pages already in flight finish unwinding, which is not instant when several
are converting at once.
Progress reporting
Long-running conversions display a real-time progress bar on stderr showing the current page title, completion percentage, and estimated time remaining. This does not interfere with stdout output that may be piped to a file.
That separation matters for automation because it keeps progress visibility for humans without corrupting redirected output.
If you need a clean transcript for CI logs, demos, or docs capture, use the global --no-progress flag.
Observed conversion run (sanitized)
The following output was captured from a live macOS run against a small current space, with the space name and key anonymized:
📚 Converting space: Team Documentation (TEAMDOCS)
📂 Output directory: /tmp/acs2md-doc-sample
🔄 Mode: incremental — state file: /tmp/acs2md-doc-sample/.convert-sync-state.json
🔍 Fetching page metadata for sync...
📥 Fetched metadata for 6 pages ✅
📋 Found 6 page(s) to convert (0 up-to-date)
✅ Sync complete!
✅ Converted: 6
❌ Failed: 0
✅ Up-to-date (skipped): 0
🗑️ Removed from tracking: 0
🔗 Links rewritten: 1 files
📂 Output: /tmp/acs2md-doc-sampleRunning the same command again against the existing output directory produced:
📚 Converting space: Team Documentation (TEAMDOCS)
📂 Output directory: /tmp/acs2md-doc-sample
🔄 Mode: incremental — state file: /tmp/acs2md-doc-sample/.convert-sync-state.json
🔍 Fetching page metadata for sync...
📥 Fetched metadata for 6 pages ✅
✅ All pages are up-to-date.
✅ Sync complete!
✅ Up-to-date (skipped): 6
🗑️ Removed from tracking: 0
📂 Output: /tmp/acs2md-doc-sampleSpace plus page workflows
acs2md also includes page-level commands so teams can combine bulk export with targeted inspection from the same binary.
acs2md page convert by-id,by-title,by-urlacs2md page get by-id,by-title,by-urlacs2md page count nodes,page count marks, andpage properties
Output structure
acs2md creates subdirectories that mirror the Confluence page tree. Each page becomes a Markdown file, and child pages are placed inside a folder named after their parent.
./output/TEAMDOCS/
├── .convert-sync-state.json
├── Team_Documentation.md
└── Team_Documentation/
├── Architecture_decisions.md
├── Change_log.md
├── Meeting_notes.md
├── Product_requirements.md
└── Retrospectives.mdFile names are derived from page titles, with conflicts resolved using the --conflict-resolution strategy.
The root Markdown file and the child folder sharing the same page title are not theoretical; that layout came directly from a live export.
Output conflict resolution
When two pages in the space produce the same file name, --conflict-resolution controls the behaviour:
| Value | Behaviour |
|---|---|
overwrite | Overwrite any existing file (default) |
suffix | Append the page ID to disambiguate duplicate file names |
skip | Leave the existing file and skip the duplicate |
versioned | Create a timestamped output directory for each run |
Sync and incremental mode
On the current live release tested for this documentation, acs2md space convert by-key defaults to incremental mode when neither --incremental nor --sync is passed. The tool creates a .convert-sync-state.json file inside the output directory and only re-converts pages that have changed since the last run.
| Flag | Behaviour |
|---|---|
--sync | Re-convert changed pages. Deletes local files for pages that were removed from Confluence. |
--incremental | Re-convert changed pages. Keeps local files for pages that were removed from Confluence. This is the observed default behaviour on v1.0.0. |
The two flags are mutually exclusive.
Sync and incremental modes are ideal for CI/CD pipelines and scheduled exports. On subsequent runs, only pages with new versions are fetched and converted, significantly reducing API calls and processing time.
Sample state file
The state file created by the live export looked like this after anonymization:
{
"last_sync_at": "2026-03-29T10:55:33.744109+02:00",
"pages": {
"100101": {
"converted_at": "2026-03-29T10:55:31.313290+02:00",
"version_created_at": "2024-01-15T09:00:00.000Z",
"page_id": "100101",
"title": "Team Documentation",
"file_path": "/tmp/acs2md-doc-sample/Team_Documentation.md",
"version_number": 3
}
},
"space_id": "100001",
"version": 1
}Recommended usage patterns
- Use
--syncwhen the output directory should remain an exact live mirror of the source space. - Use
--incrementalwhen you want a continuity or archive-style copy that keeps deleted content locally. - Use
--rewrite-links=falseonly when local portability is not required. - Run
space pages --treebefore the first large export if you need to verify scope with stakeholders.
Practical examples
Full space migration for a static site
acs2md space convert by-key DOCS \
--include-metadata \
--output-dir ./site/docsAI knowledge base (plain text, no images)
acs2md space convert by-key KB \
--exclude-marks \
--embed-images=false \
--ext-render-toc=false \
--output-dir ./ragNightly backup via CI/CD
#!/bin/bash
export ACS2MD_CONFLUENCE_DOMAIN=mycompany.atlassian.net
export ACS2MD_CONFLUENCE_USERNAME=$CONFLUENCE_USER
export ACS2MD_CONFLUENCE_API_TOKEN=$CONFLUENCE_TOKEN
acs2md space convert by-key DOCS \
--output-dir "./backup/$(date +%Y-%m-%d)" \
--log-file acs2md.logIncremental sync in CI/CD
# Only re-convert changed pages, keep files for removed pages
acs2md space convert by-key DOCS \
--incremental \
--output-dir ./docs
# Only re-convert changed pages, delete files for removed pages
acs2md space convert by-key DOCS \
--sync \
--output-dir ./docsArchive-oriented export that keeps removed pages
acs2md space convert by-key DOCS \
--incremental \
--output-dir ./archive/docsExport without link rewriting
# Keep original Confluence URLs instead of rewriting to local paths
acs2md space convert by-key MYSPACE \
--rewrite-links=false \
--output-dir ./exportFor scenario-driven guidance instead of single commands, continue with Workflows & Recipes.