Self-hosted service
Spenhouet/confluence-markdown-exporter avatar
Spenhouet/confluence-markdown-exporter

confluence-markdown-exporter: pulling a Confluence space into Markdown

Export Atlassian Confluence pages as markdown files.

591 stars134 forksPythonMIT

At a glance

What is it?
confluence-markdown-exporter reads pages, folders, spaces or whole organisations out of the Atlassian API and writes clean Markdown for Obsidian, ADO wikis, Foam or Gollum, skipping unchanged pages so a repeat run only rewrites what moved.
Who is it for?
Adopt confluence-markdown-exporter when you are leaving Confluence for a Markdown-based system and need the page tree, macros and attachments carried across, since the incremental export and the target-system presets are what make a repeated run practical. Do not adopt it if your content lives on Confluence Server rather than Cloud, because the folder and organisation commands are documented for Cloud.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is a migration, not a conversion

The tool describes itself as exporting Confluence pages in Markdown format, and the stated purpose is migrating content from Confluence to platforms that support Markdown, naming Obsidian, Gollum, Azure DevOps wikis, Foam, Dendron and more.

That framing sets the expectations correctly. This is not a converter that takes one page and gives you one file. It is an export tool for leaving a system, and the differences between those two tasks show up in the design.

The scope of a single export is large. You can export individual pages, pages with descendants, folders, or entire Confluence spaces via the Atlassian API into clean Markdown. The word folder matters, because Confluence Cloud has a folder structure that is not the same thing as a space, and the tool has a separate command for it. There is also a command for every space of an organisation, which is the scale-at-the-top end of the same idea.

The second thing that makes it a migration tool rather than a converter is the incremental behaviour. The tool skips unchanged pages by default, re-exporting only what has changed since the last run. For a space with thousands of pages, that is the difference between a migration you can run repeatedly while people are still editing, and a one-shot job that has to be perfect the first time.

The supported targets are called out separately from the feature list, and there is a dedicated presets page in the documentation for them, which tells you the output layout is configurable per destination rather than one fixed tree.

Installing with one curl, and the local cme CLI

The quickstart is three steps: install, authenticate, export.

On macOS and Linux the install is a single piped script.

bash
curl -LsSf uvx.sh/confluence-markdown-exporter/install.sh | sh

On Windows there is a PowerShell equivalent.

powershell
powershell -ExecutionPolicy ByPass -c "irm https://uvx.sh/confluence-markdown-exporter/install.ps1 | iex"

And a specific version can be requested by putting the version in the path.

bash
curl -LsSf uvx.sh/confluence-markdown-exporter/5.5.0/install.sh | sh

The install method also offers PyPI via pip or uv, and a prebuilt Docker image, both of which are covered in the installation docs and the Docker page.

The project installs two console scripts, confluence-markdown-exporter and cme, and both point at the same Typer application, so cme is a short alias for the same CLI.

The Docker route has a significant difference that the README calls out directly. Steps two and three of the quickstart use the local cme CLI, and inside the Docker image there is no interactive cme config menu. You supply a pre-defined config, either a mounted JSON file or CME_ environment variables, and run a single export command per container invocation. That is a different operating model, and the Dockerfile shows it in the environment variables it sets: CME_CONFIG_PATH points at the app data JSON and CME_EXPORT__OUTPUT_PATH at the output directory, both under /data.

So the local install is for a person at a terminal, and the Docker image is for CI.

The export commands, and where the files land

Authentication is set interactively, with the URL, username and API token or PAT.

sh
cme config edit auth.confluence

Then the export verbs, one per scope of content.

sh
# A single page
cme pages <page-url>

# A page and all its descendants
cme pages-with-descendants <page-url>

# All pages in a folder (Confluence Cloud)
cme folders <folder-url>

# An entire space
cme spaces <space-url>

# Every space of an organisation
cme orgs <base-url>

The naming is the documentation, because each verb takes the same kind of argument, a URL, and the scope widens as you go down the list. The one thing to notice is the comment on the folders command: Confluence Cloud, which tells you the folder concept is a Cloud feature rather than something present everywhere.

Output goes to the configured export.output_path, which is the current directory by default. That default is convenient for a first run and a trap for a scripted one, so setting it explicitly in the config is the sensible habit for anything you plan to repeat.

The CLI is built on Typer, with rich and questionary in the dependency list, and the interactive configuration menu in the cme config edit command is where questionary earns its place.

The dependency pin that explains the pace of the project

The dependency list in pyproject.toml contains a comment that is more informative than the versions themselves, and it is worth reading as a design constraint rather than a note.

The project depends on atlassian-python-api with the constraint greater than or equal to 4 and less than 5, and the comment explains why. The 5.x line of that library is a breaking rewrite of the Confluence client: the Cloud class drops get_page_by_id, get_attachments_from_content and get_page_comments, the client.url no longer carries the /wiki context, and the v2 cursor pagination builds an invalid next-page URL. The comment ends with a cap until we migrate.

That is a project that is deliberately blocked from upgrading a dependency, and the note about the v2 pagination building an invalid next-page URL is the kind of detail that would cost a day to rediscover. It also tells you something about the upstream library: a client for the Confluence API that changes its shape under you is a recurring source of breakage for anything built on it.

The rest of the dependency set is a normal modern Python stack: lxml at 6.0.2 or newer for the HTML parsing, markdownify for the HTML to Markdown conversion, pydantic-settings for the configuration, pyyaml, typer for the CLI, rich and tabulate for output, jmespath for querying, python-dateutil, and questionary for the interactive prompts.

The docs toolchain is worth a note because it is unusual: the site is authored as Material for MkDocs and built with Zensical, described in the comment as the Material team's successor to MkDocs. The project requires Python 3.10 or newer, and the build backend is hatchling.

The Docker image, and how CI is expected to run it

The Dockerfile is a two-stage build on python:3.12-slim, and its structure is careful in ways that matter for an image you run in CI.

The builder stage copies the uv and uvx binaries from a pinned ghcr.io/astral-sh/uv:0.8 image, sets UV_LINK_MODE=copy, UV_COMPILE_BYTECODE=1 and UV_PYTHON_DOWNLOADS=never, and then runs uv sync with the lockfile. The dependency install uses a cache mount keyed on the target architecture, and the metadata is bind-mounted rather than copied so that edits to unrelated files do not invalidate the layer. The project itself is then installed in a second sync with --no-editable, which the comment explains makes the install self-contained so the source is not needed at runtime.

The runtime stage copies only the virtual environment, not the source. It creates a system user and group with a fixed uid and gid of 1000, chowns /data to it, and sets HOME and XDG_CONFIG_HOME to /data/config so the configuration and the exported files are both reachable through a mounted volume.

That is the design of a container meant to be run once per export: mount a config, mount an output directory, run a single command, and the container exits. Combined with the non-interactive documentation page for CI use, it tells you the intended pipeline is a scheduled job rather than a long-lived service.

The overrides/ directory in the repository root, the scripts/ directory and the tests/ directory complete the picture of a project that keeps its configuration surface and its test suite close to the code.

What this exporter will not do for you

There are limits here that are worth stating before a migration is planned around them.

The first is Confluence Server. The documentation lists supported Confluence content, macros and add-ons on a dedicated feature page, and the folder and organisation commands are marked as Confluence Cloud. The tool reaches the Atlassian API through the Cloud client, and a Server instance exposes a different API surface, so a Server estate is not the same problem.

The second is that the output is Markdown, and Markdown is a lossy target for a wiki. Confluence content includes macros, and the feature list is specifically about which macros are supported. A macro that has no Markdown equivalent either degrades to its rendered output or does not come across, and a migration that silently loses the interactive parts of a page is the failure mode to watch for. The documentation's feature list is therefore the thing to read against your own content, page type by page type, before you commit.

The third is that a Markdown export is a copy, not a link. Once the pages are files, anything still living in Confluence and the exported copy drift apart, which is exactly why the incremental mode exists and why you would want to run the export on a schedule until the cutover rather than once.

The fourth is operational. The tool is a Python application with a large dependency tree, and the pinned Atlassian client version means that a future upgrade is a real piece of work, not a version bump. The releases show a steady cadence, 5.3.0 in July 2026, 5.4.0 in August and 5.5.0 at the end of September, and the last push was on 2026-10-09.

There is also no alternative to weigh here that does the same job. Other Confluence exporters exist, but the ones that do a faithful page-tree export with macros, attachments, incremental behaviour and per-target output presets are rare, and the practical comparison is usually between this and writing a script against the API yourself, which is where the pinned dependency and the macro handling would become your problem rather than the maintainer's.

Editorial conclusion

Adopt confluence-markdown-exporter when you are leaving Confluence for a Markdown-based system and need the page tree, macros and attachments carried across, since the incremental export and the target-system presets are what make a repeated run practical. Do not adopt it if your content lives on Confluence Server rather than Cloud, because the folder and organisation commands are documented for Cloud. Before you run a full space export, test on a single page and note the last push was on 2026-10-09 with release 5.5.0 on 2026-09-28.

Frequently asked questions

How do I install confluence-markdown-exporter?

On macOS and Linux, curl -LsSf uvx.sh/confluence-markdown-exporter/install.sh | sh. On Windows, the PowerShell equivalent is documented, and a specific version can be requested by putting the version in the path. PyPI via pip or uv and a prebuilt Docker image are also available.

Which Confluence scopes can the exporter handle?

Individual pages, pages with descendants, folders on Confluence Cloud, entire spaces, and every space of an organisation. Each is a separate cme subcommand taking a URL, and output goes to the configured export.output_path, which defaults to the current directory.

Does the exporter re-export unchanged pages?

No, unchanged pages are skipped by default, and only what has changed since the last run is re-exported. That is what makes a repeated export practical on a large space.

How do I use the Docker image in a non-interactive pipeline?

There is no interactive cme config menu inside the image, so you supply a pre-defined config as a mounted JSON file or as CME_ environment variables and run a single export command per container invocation. The Docker page documents the flow for CI use.

Which Atlassian client library does the exporter use?

atlassian-python-api is pinned to greater than or equal to 4 and less than 5. The comment in the project metadata explains that the 5.x line is a breaking rewrite that drops several client methods and breaks v2 cursor pagination, and that the cap stays until the project migrates.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. Spenhouet/confluence-markdown-exporter on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/spenhouet-confluence-markdown-exporter.svg)](https://hysenlabs.com/projects/spenhouet-confluence-markdown-exporter)

Was this useful?