A GitHub Action that publishes Markdown files from your repository to Confluence Cloud pages. Run it on every push to main and Confluence stays in step with the docs in your repo.
It sends each file to the REST API of Markdown Importer for Confluence, which turns the Markdown into a native Confluence page. The full walkthrough of this setup is in our guide How to sync docs from GitHub to Confluence automatically.
- Finds Markdown files in a folder (
docsby default). - Strips YAML front matter and picks a page title from the front matter
title:, the first#heading, or the filename. - Creates or updates one Confluence page per file, all under one parent page.
- Retries rate limits (429) and server errors (5xx) with backoff.
- Stops with a clear message on an invalid or expired token, an expired license, or a wrong space or parent.
- Writes a table of files, titles and results to the job summary.
- Markdown Importer for Confluence installed on your Confluence Cloud site, with an active license. The REST API is part of the paid app.
- A Confluence admin to create an API token in the app.
- A Linux or macOS runner (
ubuntu-latestis fine). The action uses the runner's Python 3 and has no other dependencies.
In Confluence, go to Confluence Settings > Apps > Markdown Importer for Confluence and open the API tab.
- Click Create New Token, give it a label such as
github-docs-sync, and choose an expiry. - Copy the token. It is shown only once.
- Copy the endpoint URL from the API Documentation section on the same page.
The token acts with the Confluence permissions of the person who created it, so consider creating it from an account that can only write to the docs space.
In your repository, open Settings > Secrets and variables > Actions and add:
| Secret | Value |
|---|---|
CONFLUENCE_API_ENDPOINT |
The endpoint URL from the API tab |
CONFLUENCE_API_TOKEN |
The API token |
Both must be numeric IDs. The space key (for example DOCS) does not work.
Open Markdown Importer for Confluence, pick the space in the Space selector and the parent page in the Page selector. Both show the ID you need. See Finding space and page IDs.
You can also get the space ID from https://<your-site>.atlassian.net/wiki/api/v2/spaces?keys=DOCS while logged in (the id field), and the parent page ID from the page URL: .../pages/123456789/....
Create .github/workflows/publish-docs.yml:
name: Publish docs to Confluence
on:
push:
branches: [main]
paths: ["docs/**"]
workflow_dispatch:
concurrency:
group: confluence-docs
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Yamuno-Software/confluence-markdown-sync@v1
with:
endpoint: ${{ secrets.CONFLUENCE_API_ENDPOINT }}
token: ${{ secrets.CONFLUENCE_API_TOKEN }}
space-id: "98765432"
parent-id: "123456789"
path: docsTo check titles before anything is published, run it once with dry-run: true.
| Input | Required | Default | Description |
|---|---|---|---|
endpoint |
yes | Your site's API endpoint URL. Pass it from a secret. | |
token |
yes | Markdown Importer API token. Pass it from a secret. | |
space-id |
yes | Numeric Confluence space ID. The space key does not work. | |
parent-id |
yes | ID of the page every published page goes under. | |
path |
no | docs |
Folder to read files from. |
glob |
no | **/*.md |
Which files inside path to publish. |
overwrite |
no | true |
Update an existing page with the same title in the space. With false, existing pages fail with 400. |
title-from |
no | frontmatter |
frontmatter, h1 or filename. If the chosen source is missing, it falls back down that list. |
dry-run |
no | false |
Print files and titles without calling the API. endpoint and token can be empty. |
created, updated, skipped and failed: the number of files in each state.
Page titles are unique within a Confluence space, and the title decides which page a file updates. If a page with that title already exists anywhere in the space, it is updated and placed under parent-id. Use titles that will not clash with pages you edit by hand.
- With
title-from: frontmatter, a file withtitle: Getting startedin its front matter becomes the page "Getting started". Without it, the first#heading is used (headings inside code blocks are ignored), then the filename with-and_turned into spaces. - If two files end up with the same title, the action stops before sending anything and lists them.
- Changing a title creates a new page. The old one stays until you delete it.
- Flat publish. Every file becomes a child of
parent-id. Folders are not turned into a page tree, because the API does not return the ID of the page it creates. To split docs across several parents, use the action more than once with differentpathandparent-idvalues. - No attachments. Local images such as
./img/diagram.pngare not uploaded. Use absolute image URLs, or upload attachments with the Confluence REST API. - Pages only. The API does not create blog posts.
- No deletes. Removing a file from the repo does not remove its page.
- Edits in Confluence are overwritten on the next publish when
overwriteistrue. - About 1 MB per page, which is Confluence's page size limit.
- Every matching file is sent on every run. With
overwrite: truethat is safe, it just updates unchanged pages again.
API tokens last at most 30 days. When the token expires, the action fails with a 403 and the message "The token has expired". Create a new token in the app and update the CONFLUENCE_API_TOKEN secret. A recurring calendar reminder a few days before the expiry date saves a failed run.
| Status | Meaning | What to do |
|---|---|---|
| 400 | Bad request: the page exists and overwrite is false, or Confluence rejected the page (for example parent-id does not exist in the space) |
Set overwrite: true, and check parent-id is a page in the space the token's creator can edit. |
| 401 | Missing or invalid token | Check the CONFLUENCE_API_TOKEN secret has no extra spaces or line breaks. |
| 402 | License expired | The app needs an active license when the token is created. Renew it and create a new token. |
| 403 | Token expired | Create a new token and update the secret. |
| 404 | Space or parent page not found | Check space-id and parent-id, and that the token's creator can access them. |
| 429, 5xx | Rate limit or server error | Retried automatically up to 3 times. If it keeps failing, re-run the job later. |
More in the REST API troubleshooting guide.
- Guide: How to sync docs from GitHub to Confluence automatically
- Markdown Importer REST API docs
- Markdown Importer for Confluence on the Atlassian Marketplace
- Support: Yamuno support portal
python3 -m unittest discover -s tests -vMIT. Made by the Yamuno team.