Mark — a tool for syncing your markdown documentation with Atlassian Confluence pages.
This is very useful if you store documentation to your software in a Git repository and don't want to do an extra job of updating Confluence page using a tinymce wysiwyg enterprise core editor which always breaks everything.
Mark does the same but in a different way. Mark reads your markdown file, creates a Confluence page if it's not found by its name, uploads attachments, translates Markdown into HTML and updates the contents of the page via REST API. It's like you don't even need to create sections/pages in your Confluence anymore, just use them in your Markdown documentation.
Mark uses an extended file format, which, still being valid markdown, contains several HTML-ish metadata headers, which can be used to locate page inside Confluence instance and update it accordingly.
File metadata can be written as YAML front matter when the frontmatter
feature is enabled. Because specifying --features replaces the defaults,
include the default features explicitly if needed:
mark --features=mermaid --features=mention --features=frontmatterYAML front matter uses the following format:
---
space: <space key>
parents:
- <parent 1>
- <parent 2>
folders:
- <folder 1>
- <folder 2>
title: <title>
attachments:
- <local path>
labels:
- <label 1>
- <label 2>
image-align: <left|center|right>
---
<page contents>The legacy HTML header format is also supported:
When both formats are present, HTML headers override matching scalar front matter values. Repeatable parents, folders, attachments, and labels are appended to the values from front matter.
<!-- Space: <space key> -->
<!-- Parent: <parent 1> -->
<!-- Parent: <parent 2> -->
<!-- Folder: <folder 1> -->
<!-- Folder: <folder 2> -->
<!-- Title: <title> -->
<!-- Attachment: <local path> -->
<!-- Label: <label 1> -->
<!-- Label: <label 2> -->
<!-- Property: <key>=<value> -->
<!-- Synchronized: <true|false> -->
<!-- Image-Align: <left|center|right> -->
<page contents>There can be any number of Parent headers, if Mark can't find specified
parent by title, Mark creates it.
Changing a Parent header on a page that has already been published moves that
page to the new parent. Mark treats your repository as the source of truth for
where a page lives, as it already does for the page's title and content, so a
page somebody has moved by hand in Confluence is moved back on the next run. A
page nested below its declared parent is left where it is: every parent the
headers name is still in its ancestry, so nothing is contradicted.
The Folder header allows organizing pages within Confluence folders. Like Parent headers,
if Mark can't find the specified folder by title, it creates it. Folders and parents can be
mixed in the same document to create complex hierarchies.
Note
Folder support is currently only available on Confluence Cloud and is not supported in Confluence Server / Data Center.
Also, optional following headers are supported:
<!-- Layout: (article|plain) -->- (default) article: content will be put in narrow column for ease of reading;
- plain: content will fill all page;
<!-- Type: (page|blogpost) -->- (default) page: normal Confluence page - defaults to this if omitted
- blogpost: Blog post in
Space. Cannot haveParent(s)
<!-- Content-Appearance: (full-width|fixed|default) -->- (default) full-width: content will fill the full page width
- fixed: content will be rendered in a fixed narrow view
- default: sets the Confluence property value to
"default", which is the narrow layout as set by the Confluence UI. Note:fixedmaps to a different Confluence property value and can cause misaligned page title and body content — usedefaultinstead for the narrow layout.
<!-- ac:ignore -->
content that stays out of Confluence
<!-- ac:ignore end -->Everything between the two markers is left out of the published page, along with the markers themselves. It is for content that reads well in one place and badly in the other -- a table of contents that Confluence builds for itself, or a plain-text stand-in for a macro:
<!-- Include: ac:profile
Name: Doe, John -->
<!-- ac:ignore -->
John Doe's profile
<!-- ac:ignore end -->Read on GitHub the file shows the name; published to Confluence it shows the profile macro. Included files may mark regions of their own, which is stripped as each file is read. Attachments referenced only inside an ignored region are not uploaded, since nothing on the page would point at them. A marker without its pair is an error rather than a guess -- quietly publishing half a page is worse than refusing.
<!-- Order: <number> -->Positions the page among its siblings, smaller numbers first. Pages that do not declare an order are left exactly where Confluence has them, so annotating one page does not disturb the rest.
Only pages published in the same run are arranged, and only relative to each
other -- a run narrowed with --files knows nothing about the other children of
those parents and will not rearrange them. Nothing is moved that is already in
the right relative order, so a run that changes nothing performs no moves at
all.
Note that giving Confluence explicit positions takes that branch of the tree out of its default alphabetical ordering, which is inherent to asking for a particular order.
<!-- Sidebar: <h2>Test</h2> -->Setting the sidebar creates a column on the right side. You're able to add any valid HTML content. Adding this property sets the layout to article.
<!-- Emoji: 🚀 -->You can set a page emoji icon by specifying the icon in the headers.
<!-- Image-Align: center -->You can set the alignment for all images in the page. Common values are left, center, and right. Can also be set globally via the --image-align CLI option (per-page header takes precedence).
Note: Images with width >= 760px automatically use center instead of the configured alignment, as Confluence requires this for wide images.
Mark supports Go templates, which can be included into article by using path to the template relative to current working dir, e.g.:
<!-- Include: <path> -->If the template cannot be found relative to the current directory, a fallback directory can be defined via --include-path. This way it is possible to have global include files while local ones will still take precedence.
Optionally the delimiters can be defined:
<!-- Include: <path>
Delims: "<<", ">>"
-->Or they can be switched off to disable processing:
<!-- Include: <path>
Delims: none
-->Note: Switching delimiters off really simply changes them to ASCII characters "\x00" and "\x01" which, usually should not occure in a template.
Templates can accept configuration data in YAML format which immediately
follows the Include and Delims tag, if present:
<!-- Include: <path>
<yaml-data> -->Includes can be nested inside other included templates. Furthermore, included files can define page metadata (such as Title, Space, Parent, etc.) or macro definitions. Circular inclusion loops are automatically detected and reported as an error.
Mark also supports attachments. The standard way involves declaring an
Attachment along with the other items in the header, then have any links
with the same path:
<!-- Attachment: <path-to-image> -->
<beginning of page content>
An attached link is [here](<path-to-image>)NOTE: Be careful with Attachment! If your path string is a subset of
another longer string or referenced in text, you may get undesired behavior.
Mark also supports macro definitions, which are defined as regexps which will be replaced with specified template:
<!-- Macro: <regexp>
Template: <path>
<yaml-data> -->NOTE: Make sure to define your macros after your metadata (Title/Space), mark will stop processing metadata if it hits a Macro.
Capture groups can be defined in the macro's which can be later
referenced in the <yaml-data> using ${<number>} syntax, where <number> is
number of a capture group in regexp (${0} is used for entire regexp match),
for example:
<!-- Macro: MYJIRA-\d+
Template: ac:jira:ticket
Ticket: ${0} -->Macros can also use inline templates.
Inline templates are templates where the template content
is described in the <yaml-data>.
The Template value starts with a #, followed by the key
used in the <yaml-data>.
The key's value must be a string which defines the template's content.
<!-- Macro: <tblbox\s+(.*?)\s*>
Template: #inline
title: ${1}
inline: |
<table>
<thead><tr><th>{{ .title }}</th></tr></thead>
<tbody><tr><td>
-->
<!-- Macro: </tblbox>
Template: #also_inline
also_inline: |
</td></tr></tbody></table>
-->
<tblbox with a title>
and some
content
</tblbox>Macro templates can also output <!-- Include: ... --> directives, allowing macros to dynamically load external template files or include other documents.
If you don't want to specify the page title in the metadata of each file, mark provides two ways to set it automatically.
You can use the --title-from-h1 flag to extract the page title from the first H1 heading in the markdown file. If no H1 heading is found, the title must be set in the page metadata.
You can use the --title-from-filename flag to use the filename (without the extension) as the page title. mark will automatically convert the filename to a more readable title by:
- Replacing underscores (
_) and dashes (-) with spaces. - Applying title case to the filename.
For example, a file named my_awesome-page.md will have the title "My Awesome Page".
These two options are mutually exclusive. If both flags are provided, mark will produce an error.
If you set the Layout to plain, the page layout can be customized using HTML comments inside the markdown:
<!-- Layout: plain -->
<!-- ac:layout -->
<!-- ac:layout-section type:three_with_sidebars -->
<!-- ac:layout-cell -->
More Content
<!-- ac:layout-cell end -->
<!-- ac:layout-cell -->
More Content
<!-- ac:layout-cell end -->
<!-- ac:layout-cell -->
Even More Content
<!-- ac:layout-cell end -->
<!-- ac:layout-section end -->
<!-- ac:layout-section type:single -->
<!-- ac:layout-cell -->
Still More Content
<!-- ac:layout-cell end -->
<!-- ac:layout-section end -->
<!-- ac:layout end -->Please be aware that mark does not validate the layout, so it's your responsibility to create a valid layout.
You can use this to define placeholders:
<!-- ac:placeholder -->
Placeholder
<!-- ac:placeholder end -->```bash
...
some long bash code block
...
```
| Parameter | Default |
|---|---|
collapse |
false |
title |
none |
linenumbers |
false |
1 (any number for firstline) |
1 |
Example:
bash collapseIf you have long code blocks, you can make them collapsible.bash collapse title Some long long bash functionAnd you can also add a title.bash linenumbers collapse title Some long long bash functionAnd linenumbers.bash 1 collapse title Some long long bash functionOr directly give a number as firstline number.bash 1 collapse midnight title Some long long bash functionAnd even themes.- 1 collapse midnight title Some long long codePlease note that, if you want to have a code block without a language use-as the first character, if you want to have the other goodies.
More details at Confluence Code Block Macro doc.
You can now use GitHub-style alert syntax in your markdown, and Mark will automatically convert them to Confluence macros:
> [!NOTE]
> This creates a blue info box - perfect for helpful information!
> [!TIP]
> This creates a green tip box - great for best practices and suggestions!
> [!IMPORTANT]
> This creates a blue info box - ideal for critical information!
> [!WARNING]
> This creates a yellow warning box - use for important warnings!
> [!CAUTION]
> This creates a red warning box - perfect for dangerous situations!Block Quotes are converted to Confluence Info/Warn/Note box when the following conditions are met:
- The BlockQuote is on the root level of the document (not nested)
- The first line of the BlockQuote contains one of the following patterns
Info/Warn/Noteor GitHub MD Alerts style[!NOTE]/[!TIP]/[!IMPORTANT]/[!WARNING]/[!CAUTION]
| GitHub Alerts | Confluence | Description |
|---|---|---|
[!TIP] (green lightbulb) |
Tip (green checkmark in circle) | Helpful suggestions and best practices |
[!NOTE] (blue I in circle) |
Info (blue I in circle) | General information and notes |
[!IMPORTANT] (purple exclamation mark in speech bubble) |
Info (blue I in circle) | Critical information that needs attention |
[!WARNING] (yellow exclamation mark in triangle) |
Note (yellow exclamation mark in triangle) | Important warnings and cautions |
[!CAUTION] (red exclamation mark in hexagon) |
Warning (red exclamation mark in hexagon) | Dangerous situations requiring immediate attention |
In any other case the default behaviour will be resumed and html <blockquote> tag will be used
Mark supports GitHub Flavored Markdown task lists.
Task lists are automatically converted to Confluence ac:task-list elements.
- [x] Finished task
- [ ] Unfinished taskIf a list is "mixed" (contains both tasks and regular list items), it will fall back to a standard HTML list with textual markers like [x] or [ ] to ensure validity in Confluence storage format.
By default, mark provides several built-in templates and macros:
-
template
ac:statusto include badge-like text, which accepts following parameters:- Title: text to display in the badge
- Color: color to use as background/border for badge
- Grey
- Red
- Yellow
- Green
- Blue
- Subtle: specify to fill badge with background or not
- true
- false
-
template
ac:boxto include info, tip, note, and warning text boxes. Parameters:- Name: select box style
- info
- tip
- note
- warning
- Icon: show information/tip/exclamation mark/warning icon
- true
- false
- Title: title text of the box
- Body: text to display in the box
See: https://confluence.atlassian.com/conf59/info-tip-note-and-warning-macros-792499127.html
- Name: select box style
-
template
ac:jira:ticketto include JIRA ticket link. Parameters:- Ticket: Jira ticket number like BUGS-123.
See: https://confluence.atlassian.com/conf59/status-macro-792499207.html
-
template
ac:jira:filterto include JIRA Filters/Searches. Parameters:- JQL: The "JQL" query of the search
- Server (Optional): The Jira server to fetch the query from if its not the default of "System Jira"
-
template
ac:jiraissuesto include a list of JIRA tickets. Parameters:- URL (Required), The URL of the XML view of your selected issues. (link to the filter)
- Anonymous (Optional) If this parameter is set to 'true', your JIRA application will return only the issues which allow unrestricted viewing. That is, the issues which are visible to anonymous viewers. If this parameter is omitted or set to 'false', then the results depend on how your administrator has configured the communication between the JIRA application and Confluence. By default, Confluence will show only the issues which the user is authorised to view.
- BaseURL (Optional) If you specify a 'baseurl', then the link in the header, pointing to your JIRA application, will use this base URL instead of the value of the 'url' parameter. This is useful when Confluence connects to JIRA with a different URL from the one used by other users.
- Columns (Optional) A list of JIRA column names, separated by semi-colons (;). You can include many columns recognized by your JIRA application, including custom columns.
- Count (Optional) If this parameter is set to 'true', the issue list will show the number of issues in JIRA. The count will be linked to your JIRA site.
- Cache (Optional) The macro maintains a cache of the issues which result from the JIRA query. If the 'cache' parameter is set to 'off', the relevant part of the cache is cleared each time the macro is reloaded. (The value 'false' also works and has the same effect as 'off'.)
- Height (Optional) The height in pixels of the table displaying the issues.
- RenderMode (Optional) If the value is 'dynamic', the JIRA Issues macro offers an interactive display.
- Title (Optional) You can customise the title text at the top of the issues table with this parameter. For instance, setting the title to 'Bugs-to-fix' will replace the default 'JIRA Issues' text. This can help provide more context to the list of issues displayed.
- Width (Optional) The width of the table displaying the issues. Can be entered as a percentage (%) or in pixels (px).
See: https://confluence.atlassian.com/doc/jira-issues-macro-139380.html
-
template:
ac:emoticonto include emoticons. Parameters:- Name: select emoticon
- smile
- sad
- cheeky
- laugh
- wink
- thumbs-up
- thumbs-down
- information
- tick
- cross
- warning
- plus
- minus
- question
- light-on
- light-off
- yellow-star
- red-star
- green-star
- blue-star
See: https://confluence.atlassian.com/doc/confluence-storage-format-790796544.html
- Name: select emoticon
-
template:
ac:youtubeto include YouTube Widget. Parameters:- URL: YouTube video endpoint
- Width: Width in px. Defaults to "640px"
- Height: Height in px. Defaults to "360px"
-
template:
ac:childrento include Children Display macro- Reverse (Reverse Sort): Use with the
Sort Children Byparameter. When set, the sort order changes from ascending to descending.truefalse(Default)
- Sort (Sort Children By):
creation— to sort by content creation datetitle— to sort alphabetically on titlemodified— to sort of last modification date.- If not specified, manual sorting is used if manually ordered, otherwise alphabetical.
- Style (Heading Style): Choose the style used to display descendants.
- from
h1toh6 - If not specified, default style is applied.
- from
- Page (Parent Page):
/— to list the top-level pages of the current space, i.e. those without parents.pagename— to list the children of the specified page.spacekey:pagename— to list the children of the specified page in the specified space.- If not specified, the current page is used.
- Excerpt (Include Excerpts): Allows you to include a short excerpt under each page in the list.
none- no excerpt will be displayed. (Default)simple- displays the first line of text contained in an Excerpt macro any of the returned pages. If there is not an Excerpt macro on the page, nothing will be shown.rich content- displays the contents of an Excerpt macro, or if there is not an Excerpt macro on the page, the first part of the page content, including formatted text, images and some macros.
- First (Number of Children): Restrict the number of child pages that are displayed at the top level.
- If not specified, no limit is applied.
- Depth (Depth of Descendants): Enter a number to specify the depth of descendants to display. For example, if the value is 2, the macro will display 2 levels of child pages. This setting has no effect if
Show Descendantsis enabled.- If not specified, no limit is applied.
- All (Show Descendants): Choose whether to display all the parent page's descendants.
truefalse(Default)
See: https://confluence.atlassian.com/doc/children-display-macro-139501.html
- Reverse (Reverse Sort): Use with the
-
template:
ac:iframeto include iframe macro (cloud only)- URL: URL to the iframe.
- Frameborder: Choose whether to draw a border around content in the iframe.
show(Default)hide
- Width: Width in px. Defaults to "640px"
- Height: Height in px. Defaults to "360px"
- Scrolling: Allow or prevent scrolling in the iframe to see additional content.
yesnoauto(Default)
- Align: Align the iframe to the left or right of the page.
left(Default)right
See: https://support.atlassian.com/confluence-cloud/docs/insert-the-iframe-macro
-
template:
ac:blog-poststo include blog-posts- Content: How much content will be shown
- titles (default)
- excerpts
- entire
- Time: Specify how much back in time Confluence should look for blog posts (default: unlimited)
- Label: Restrict to blog posts with specific labels
- Author: Restrict to blog posts by specific authors
- Spaces: Restrict to blog posts in specific spaces
- Max: Maximum number of blog posts shown (default: 15)
- Sort: Sorting posts by
- title
- creation (default)
- modified
- Reverse: Reverses the Sort parameter from oldest to newest (default: false)
See: https://confluence.atlassian.com/doc/blog-posts-macro-139470.html
- Content: How much content will be shown
-
template:
ac:includeto include a page- Page: the page to be included
- Space: the space the page is in (optional, otherwise same space)
-
template:
ac:excerpt-includeto include the excerpt from another page- Page: the page the excerpt should be included from
- Name: The specific identifier for the excerpt, allowing multiple Excerpt macros on one page to be referenced individually. If not provided, the first excerpt from the page will be used (optional, cloud only)
- NoPanel: Determines whether Confluence will display a panel around the excerpted content (optional, default: false)
-
template:
ac:excerptto create an excerpt and include it in the page- Excerpt: The text you want to include
- Name: Allows you to identify this macro so that you can add multiple Excerpt macros to one page and use a specific one on another page using the Excerpt Include macro (optional, cloud only)
- OutputType: Determines whether the content of the Excerpt macro body is displayed on a new line or inline (optional, options: "BLOCK" or "INLINE", default: BLOCK)
- Hidden: Hide the excerpt content (optional, default: false)
-
template:
ac:anchorto set an anchor inside a page- Anchor: Text for the anchor
-
template:
ac:expandto display an expandable/collapsible section of text on your page- Title: Defines the text next to the expand/collapse icon.
- Body: The Text that it is expanded to.
-
template:
ac:profileto display a short summary of a given Confluence user's profile.- Name: The username of the Confluence user whose profile summary you wish to show.
-
template:
ac:contentbylabelto display a list of pages, blog posts or attachments that have particular labels- CQL: The CQL query to discover the content
-
template:
ac:detailssummaryto show summary information from one page on a another page- Headings: Column headings to show
- FirstColumn: Name of the Title Column
- CQL: The CQL query to discover the pages
- SortBy: Sort by a specific column heading
-
template:
ac:detailsto create page properties- Body: Must contain a table with two rows, the table headings are used as property key. The table content is the value.
-
template:
ac:panelto display a block of text within a customisable panel- Title: Panel title (optional)
- Body: Body text of the panel
- BGColor: Background Color
- TitleBGColor: Background color of the title bar
- TitleColor: Text color of the title
- BorderStyle: Style of the panel's border
-
template
ac:recently-updatedto display a list of most recently changed content- Spaces: List of Spaces to watch (optional, default is current Space)
- ShowProfilePic: Show profile picture of editor
- Max: Maximum number of changes
- Types: Include these content types only (comments, blogposts, pages)
- Theme: Apperance of the macro (concise, social, sidebar)
- HideHeading: Determines whether the macro hides or displays the text 'Recently Updated' as a title above the list of content
- Labels: Filter the results by label. The macro will display only the pages etc which are tagged with the label(s) you specify here.
-
template:
ac:pagetreesearchto add a search box to your Confluence page.- Root: Name of the root page whose hierarchy of pages will be searched by this macro. If this not specified, the root page is the current page.
-
template:
ac:columnTo be used with the section macro to define the columns in a page.- Width: Width of the column
- Body: The content of the column
-
template:
ac:multimediato embedd an attached video, animation or other multimedia files in a Confluence page- Name: Name of the file
- Width: Width of the video (optional)
- AutoPlay: Start playing the file on page load (default: false)
-
template
ac:view-file- Name: Name of the file
- Height: height of the view
-
macro
@{...}to mention user by name specified in the braces.
This should be in disclaimer.md.
**NOTE**: this document is generated, do not edit manually.Add this to your article.md.
<!-- Space: TEST -->
<!-- Title: My Article -->
<!-- Include: disclaimer.md -->
This is my article.<!-- Space: TEST -->
<!-- Title: TODO List -->
<!-- Macro: :done:
Template: ac:status
Title: DONE
Color: Green -->
<!-- Macro: :todo:
Template: ac:status
Title: TODO
Color: Blue -->
* :done: Write Article
* :todo: Publish Article<!-- Space: TEST -->
<!-- Title: Announcement -->
<!-- Macro: :box:([^:]+):([^:]*):(.+):
Template: ac:box
Icon: true
Name: ${1}
Title: ${2}
Body: ${3} -->
:box:info::Foobar:
:box:tip:Tip of day:Foobar:
:box:note::Foobar:
:box:warning:Alert!:Foobar:<!-- Include: ac:toc -->If default TOC looks don't find a way to your heart, try parametrizing it, for example:
<!-- Macro: :toc:
Template: ac:toc
Printable: 'false'
MinLevel: 2 -->
# This is my nice title
:toc:You can call the Macro as you like but the Template field must have the ac:toc value.
Also, note the single quotes around 'false'.
See Confluence TOC Macro for the list of parameters - keep in mind that here they start with capital letters. Every skipped field will have the default value, so feel free to include only the ones that you require.
# My First Heading
<!-- Include: ac:pagetree -->The pagetree macro works almost the same as the TOC above, but the tree behavior is more desirable for creating placeholder pages above collections of SOPs.
The default pagetree macro behavior is to insert a tree rooted @self.
The following parameters can be used to alter your default configuration with parameters described more in depth here:Confluence Pagetree Macro.
Parameters:
- Title (of tree root page)
- Sort
- Excerpt
- Reverse
- SearchBox
- ExpandCollapseAll
- StartDepth
E.G.
<!-- Macro: :pagetree:
Template: ac:pagetree
Reverse: 'true'
ExpandCollapseAll: 'true'
StartDepth: 2 -->
# My First Heading
:pagetree:To include Children Display (TOC displaying children pages) use following macro:
<!-- Macro: :children:
Template: ac:children
-->
# This is my nicer title
:children:You can use various parameters to modify Children Display:
<!-- Macro: :children:
Template: ac:children
Sort: title
Style: h3
Excerpt: simple
First: 10
Page: Space:Page title
Depth: 2
Reverse: false
All: false -->
# This is my nicest title
:children:<!-- Space: TEST -->
<!-- Title: TODO List -->
<!-- Macro: MYJIRA-\d+
Template: ac:jira:ticket
Ticket: ${0} -->
See task MYJIRA-123.This is a [link to an existing confluence page](ac:Pagetitle)
And this is how to link when the linktext is the same as the [Pagetitle](ac:)
Link to a [page title containing spaces](<ac:With Multiple Words>)A relative link to another Markdown file is replaced with a link to the Confluence page that file publishes:
See [the other page](./other.md) and [a heading in it](./other.md#setup).The target file is read to find its Space and Title, and the page is looked
up by those. Links are rewritten to Confluence tiny
links
(/x/AbCdEf), which survive page renames and moves.
A link is left exactly as written when it has a scheme (https:, mailto:),
is a bare #fragment, points at a directory or a non-text file, or names a
file that has no mark metadata and so is never published. Links are resolved on
the parsed document, so a link that appears inside a code span or a fenced or
indented code block is left alone -- a page documenting Markdown gets to show
its examples unchanged. Files pulled in with Include are resolved along with
the document that includes them.
A document can ask to be left out of a run:
<!-- Synchronized: false -->or, in YAML front matter:
---
title: Runbook
synchronized: false
---Mark skips the file before it asks Confluence anything, so a document that has opted out costs no page lookup and uploads no attachments. Saying nothing means the document is published, which is the ordinary case; opting out has to be deliberate.
A page that was published before is left exactly as it is -- Mark does not
delete it, and does not report it as having lost its source file. Setting
Synchronized: true again, or removing the header, resumes publishing to the
same page.
A page can carry arbitrary key/value data that macros, reports and scripts read
back through the API. One Property header sets one of them, as many times as
you like:
<!-- Property: owner=platform-team -->
<!-- Property: reviewed=2026-08 -->In YAML front matter the same thing is a mapping under the plural key, the way
Parent headers become a parents list:
---
title: Runbook
properties:
owner: platform-team
reviewers: 3
tags:
- runbook
- on-call
---A content property holds JSON, so front matter can give a number, a list or a
mapping as the value. A Property header can only say a string, which is the
one difference between the two forms.
--global-properties points at a YAML or JSON file of properties to set on
every page:
mark --global-properties confluence-properties.yaml --files "docs/**/*.md"A document naming a property the file also names wins for its own page. A property whose value has not changed is not written again, because Confluence versions each one and rewriting it fills its history for nothing.
A page ends up with exactly the labels its Label headers name: one added in
the Confluence UI is removed on the next publish, because the document is taken
as the whole truth about its labels.
That is often not what a team wants. Labels drive macros, searches and reports,
and are frequently applied by people who are not editing the Markdown.
--append-labels adds what a document asks for without removing anything else:
mark --append-labels --files "docs/**/*.md"The cost is that a label outlives the header that introduced it -- appending
cannot tell a Label header somebody deleted from a label somebody added in
Confluence. That is visible on the page and can be undone by hand, which the
deletion it prevents is not.
By default Mark prints the address of each page as it publishes, which is what
it has always done. --output-format offers two other shapes.
json describes the whole run as one object:
mark --output-format json --files "docs/**/*.md" | jq -r '.pages[] | select(.status=="published") | .url'{
"pages": [
{
"file": "docs/architecture.md",
"status": "published",
"space": "DOCS",
"title": "Architecture",
"pageId": "1004",
"url": "https://example.atlassian.net/wiki/display/DOCS/Architecture"
},
{
"file": "docs/draft.md",
"status": "skipped",
"reason": "the document is not synchronized"
}
],
"orphans": [{"file": "docs/old.md", "action": "delete"}]
}A page is published, unchanged (--changes-only found nothing to do),
skipped (not synchronized, or edited in Confluence under --no-overwrite) or
failed, with reason saying which in the last two cases.
github prints workflow
commands,
so that a failure appears against the file that caused it in a pull request:
::notice file=docs/architecture.md::published "Architecture" to https://...
::warning file=docs/draft.md::the document is not synchronized
::error file=docs/broken.md::unable to compile markdown: ...
- run: mark --output-format github --files "docs/**/*.md"A link is resolved by finding the page it points at, so a document linking to another that the same run is creating has nothing to find yet.
Mark notes those documents and publishes them again once everything exists, so links resolve within a single run. Only the documents that were waiting are published a second time, and only on the run that creates their targets: once the pages are there, later runs find them the first time and nothing is published twice.
Nothing is published again on --dry-run or --compile-only, where no page is
being created for a link to wait for.
By default a link that cannot be resolved is left exactly as written, which in
Confluence means a link that leads nowhere. --check-links makes that a failure
instead:
mark --check-links internal,confluence --files "docs/**/*.md"ERR unable to compile markdown: link "./architecure.md" does not resolve:
there is no such file
| Value | Checks |
|---|---|
internal |
relative links to other Markdown files in the repository |
confluence |
ac: links, which name a Confluence page by title |
external |
requests each URL with a scheme to see whether it answers |
all |
all three |
The values are a set, not a mode: repeat the flag or separate them with commas,
and pick whichever combination suits. The three cost very different things --
internal is answered from the filesystem, confluence costs a lookup per link,
and external leaves the building -- so internal,confluence in CI with no
network checking is a perfectly reasonable choice, and the one above.
An internal link fails the run when the file it names is missing, is a
directory, or is a document that never becomes a page -- one with no title, so
there is nothing for the link to point at.
A link is looked for beside the document that contains it, and then beside each file that document includes. A fragment reads as a document in its own right, so a link inside one is written from where the fragment lives rather than from wherever it is pulled into. The document's own directory is always tried first, so this cannot change what an unambiguous link already meant. Only the files a document includes directly are considered, not what those files include in turn.
One case is reported but does not fail: a link to a document that exists and has a title, but is not in the space and is not being published by this run either. A page the run is about to create is waited for rather than complained about -- see below -- so this is left for the genuinely absent.
A confluence link is checked by looking for a page of that title in the
document's own space, which is what an ac: link resolves against. The title is
read the way the renderer reads it: whatever follows the colon, or the link text
when nothing does, so [Some Page](ac:) is checked as Some Page.
These are checked once the run has finished rather than as each document compiles, because a page named this way is often published by another file in the same run. Each page is looked up once however many documents link to it.
external needs network access from wherever Mark runs, and makes publishing
dependent on every site you link to being up. Each URL is requested once per run
however many pages mention it. HEAD is tried first and a refusal is retried
with GET, since plenty of servers reject HEAD while serving the URL
perfectly well.
Bare #fragments, mailto: links and rooted paths are not links Mark resolves,
and are never checked.
Every broken link in a document is reported, not just the first, so a page with several of them takes one run to find out rather than one run each.
--check-links-warn-only reports the same links without failing the run, which
is how to see the list before the build starts failing over it:
mark --check-links all --check-links-warn-only --files "docs/**/*.md"Pages still publish exactly as they would have. It is a separate flag from
--continue-on-error, which is about files rather than links and still fails
the run at the end: use --check-links-warn-only to not fail at all, and
--continue-on-error to attempt every file before failing.
will automatically upload the inlined image as an attachment and inline the image using the ac:image template.
If the file is not found, it will inline the image using the ac:image template and link to the image.
Use the following macro:
<!-- Macro: \!\[.*\]\((.+)\)\<\!\-\- width=(.*) \-\-\>
Template: ac:image
Attachment: ${1}
Width: ${2} -->And attach any image with the following
<!-- width=300 -->The width will be the commented html after the image (in this case 300px).
Currently this is not compatible with the automated upload of inline images.
Standard HTML <img> tags (inline, block, single-line, or multi-line) can be converted into <ac:image> macros by enabling the --features html-img-tag flag. This allows you to specify sizing while keeping the document readable in standard Markdown renderers like GitHub:
<img src="../images/example.png" width="300" alt="Example" title="An Example" />Standard attributes (width, alt, and title) are supported and carried over directly. Local image files are uploaded as attachments, and remote image URLs are linked directly.
Interactive Confluence date badges (<time datetime="YYYY-MM-DD" />) can be generated by enabling the --features date flag. Both HTML <time> tags and @date(YYYY-MM-DD) macro directives are converted:
Release on @date(2026-07-27) or <time datetime="2026-12-31">December 31, 2026</time>.Confluence doesn't provide mermaid.js support natively. Mark provides a convenient way to enable the feature like GitHub does. As long as you have a code block marked as "mermaid", mark will automatically render it as a PNG image and attach it to the page as a rendered version of the code block.
graph TD;
A-->B;
Optionally you can enable D2 rendering via --features="d2".
This will transform the d2 diagram into a png that will be attached to Confluence, similar to how mermaid-go support works.
All you need is a codeblock marked as "d2".
X -> YOptionally you can enable PlantUML diagram rendering via --features="plantuml".
Unlike Mermaid and D2 which are rendered locally, code blocks marked as "plantuml" are rendered in Confluence by the PlantUML for Confluence Macro.
This requires the PlantUML for Confluence macro to be installed in your Confluence instance.
@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
@endumlOptionally you can enable mkdocs-style Admonitions via --features="mkdocsadmonitions".
When enabled, this renders note, warning, tip, info admonitions as Confluence alerts.
!!! noteOptionally you can enable auto-conversion of standard HTML <details> and <summary> tags to native Confluence expand macros via --features="details".
When enabled, this maps standard HTML details blocks directly to Confluence expand macros:
<details>
<summary>Click to expand</summary>
This is the hidden content.
</details>Optionally you can enable LaTeX / Math formula rendering via --features="math".
When enabled, mathematical formulas are rendered into HTML using server-side KaTeX rendering powered by goldmark-katex—requiring no external browser process, no CGO, and no third-party Confluence plugins.
Inline Math:
Euler's identity is $e^{i\pi} + 1 = 0$ or \(e^{i\pi} + 1 = 0\).Display / Block Math:
$$
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
$$Or using \[ ... \]:
\[
f(x) = \int_{-\infty}^\infty \hat{f}(\xi)\,e^{2\pi i \xi x}\,d\xi
\]Optionally you can render bare URLs as Confluence Cloud inline smart cards via --features="inline-link-card".
When enabled, auto-detected URLs in markdown (e.g. <https://example.com> or
a bare URL on its own line) are rendered with the data-card-appearance="inline"
attribute, which Confluence Cloud uses as a hint to display the link as an
inline card preview (page title, Jira issue summary, GitHub repo card, etc.)
instead of a plain blue hyperlink.
See <https://your-instance.atlassian.net/wiki/spaces/DOCS/pages/12345/Page+Title>
for context.
Only auto-detected URLs (bare URLs / <...> autolinks) are affected.
Markdown-explicit links ([label](https://...)) keep their author-chosen
display text and render as regular hyperlinks, unchanged.
Note: Inline Smart Cards (data-card-appearance="inline") are a Confluence Cloud feature. On Confluence Data Center / Server, the attribute is safely ignored and the link displays as a standard hyperlink.
Note: mark will read configuration from your environment variables or the configuration file.
brew tap kovetskiy/mark
brew install markgo install github.com/kovetskiy/mark/v16/cmd/mark@latestDownload a release from the Releases page
docker run --rm -i kovetskiy/mark:latest mark <params>Mostly useful when you intend to enhance mark.
# Create the binary
$ docker-compose run markbuilder
# "install" the binary
$ cp mark /usr/local/binNAME:
mark - A tool for updating Atlassian Confluence pages from markdown.
USAGE:
mark [global options]
VERSION:
v16.x.x
DESCRIPTION:
Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark
GLOBAL OPTIONS:
--files string, -f string use specified markdown file(s) for converting to html. Supports file globbing patterns (needs to be quoted). [$MARK_FILES]
--continue-on-error don't exit if an error occurs while processing a file, continue processing remaining files. [$MARK_CONTINUE_ON_ERROR]
--compile-only show resulting HTML and don't update Confluence page content. [$MARK_COMPILE_ONLY]
--dry-run resolve page and ancestry, show resulting HTML and exit. [$MARK_DRY_RUN]
--edit-lock, -k lock page editing to current user only to prevent accidental manual edits over Confluence Web UI. [$MARK_EDIT_LOCK]
--drop-h1 don't include the first H1 heading in Confluence output. [$MARK_DROP_H1]
--strip-linebreaks, -L remove linebreaks inside of tags, to accommodate non-standard Confluence behavior [$MARK_STRIP_LINEBREAKS]
--title-from-h1 extract page title from a leading H1 heading. If no H1 heading on a page exists, then title must be set in the page metadata. Mutually exclusive with --title-from-filename. [$MARK_TITLE_FROM_H1]
--title-from-filename use the filename (without extension) as the Confluence page title if no explicit page title is set in the metadata. Mutually exclusive with --title-from-h1. [$MARK_TITLE_FROM_FILENAME]
--title-append-generated-hash appends a short hash generated from the path of the page (space, parents, and title) to the title [$MARK_TITLE_APPEND_GENERATED_HASH]
--minor-edit don't send notifications while updating Confluence page. [$MARK_MINOR_EDIT]
--version-message string add a message to the page version, to explain the edit (default: "") [$MARK_VERSION_MESSAGE]
--color string display logs in color. Possible values: auto, never. (default: "auto") [$MARK_COLOR]
--log-level string set the log level. Possible values: TRACE, DEBUG, INFO, WARNING, ERROR, FATAL. (default: "info") [$MARK_LOG_LEVEL]
--username string, -u string use specified username for updating Confluence page. [$MARK_USERNAME]
--password string, -p string use specified token for updating Confluence page. Specify - as password to read password from stdin, or your Personal access token. Username is not mandatory if personal access token is provided. For more info please see: https://developer.atlassian.com/server/confluence/confluence-server-rest-api/#authentication. [$MARK_PASSWORD]
--target-url string, -l string edit specified Confluence page. If -l is not specified, file should contain metadata (see above). [$MARK_TARGET_URL]
--base-url string, -b string base URL for Confluence. Alternative option for base_url config field. [$MARK_BASE_URL]
--config string, -c string use the specified configuration file. (default: "${HOME}/.config/mark.toml") [$MARK_CONFIG]
--ci run on CI mode. It won't fail if files are not found. [$MARK_CI]
--space string use specified space key. If the space key is not specified, it must be set in the page metadata. [$MARK_SPACE]
--parents string A list containing the parents of the document separated by parents-delimiter (default: '/'). These will be prepended to the ones defined in the document itself. [$MARK_PARENTS]
--parents-delimiter string The delimiter used for the parents list (default: "/") [$MARK_PARENTS_DELIMITER]
--content-appearance string default content appearance for pages without a Content-Appearance header. Possible values: full-width, fixed, default. [$MARK_CONTENT_APPEARANCE]
--mermaid-scale float defines the scaling factor for mermaid renderings. (default: 1) [$MARK_MERMAID_SCALE]
--include-path string Path for shared includes, used as a fallback if the include doesn't exist in the current directory. [$MARK_INCLUDE_PATH]
--changes-only Avoids re-uploading pages that haven't changed since the last run. [$MARK_CHANGES_ONLY]
--output-format string how to report what the run did: "url" prints the address of each published page (the default), "json" prints one object describing the whole run, "github" prints GitHub Actions workflow commands so that failures appear against the file that caused them. [$MARK_OUTPUT_FORMAT]
--on-orphan string what to do about a page whose source file is gone: "report" says so and does nothing (the default), "archive" archives the page (Confluence Cloud only), "delete" moves it to the trash. Requires --track-pages. [$MARK_ON_ORPHAN]
--orphan-under string limit --on-orphan to pages below this page or folder, given by title or id. Without it, every tracked page the --files pattern would have published is in scope. [$MARK_ORPHAN_UNDER]
--check-links string [ --check-links string ] fail on links that do not resolve. Repeat or comma-separate any of: "internal" (relative links to other files in the repository), "confluence" (ac: links naming a page by title), "external" (requests each URL to see whether it answers), or "all". [$MARK_CHECK_LINKS]
--global-properties string path to a YAML or JSON file of Confluence content properties to set on every page. A Property header or properties front matter in a document wins over the file for that page. [$MARK_GLOBAL_PROPERTIES]
--append-labels add the labels a document asks for without removing any others, so that labels applied in Confluence survive a publish. Without it, a page ends up with exactly the labels its Label headers name. [$MARK_APPEND_LABELS]
--check-links-warn-only report links that do not resolve without failing the run. Only meaningful together with --check-links. [$MARK_CHECK_LINKS_WARN_ONLY]
--no-overwrite Leave alone any page that has been edited in Confluence since mark last published it, instead of overwriting the edit. Requires --track-pages, which is where the last published version is remembered. [$MARK_NO_OVERWRITE]
--track-pages Remember which page each file publishes to, so renaming a file or changing its title updates the existing page instead of creating a second one. Stores the mapping in Confluence (a space property on Cloud, a homepage content property on Server/Data Center); nothing is written to the repository. [$MARK_TRACK_PAGES]
--preserve-comments Fetch and preserve inline comments on existing Confluence pages. [$MARK_PRESERVE_COMMENTS]
--d2-scale float defines the scaling factor for d2 renderings. (default: 1) [$MARK_D2_SCALE]
--features string [ --features string ] Enables optional features. Current features: d2, date, details, frontmatter, html-img-tag, inline-link-card, math, mention, mermaid, mkdocsadmonitions, plantuml (default: "mermaid", "mention") [$MARK_FEATURES]
--insecure-skip-tls-verify skip TLS certificate verification (useful for self-signed certificates) [$MARK_INSECURE_SKIP_TLS_VERIFY]
--image-align string set image alignment (left, center, right). Can be overridden per-file via the Image-Align header. [$MARK_IMAGE_ALIGN]
--help, -h show help
--version, -v print the versionYou can store user credentials in the configuration file, which should be
located in a system specific directory (or specified via -c --config <path>) with the following format (TOML):
username = "your-email"
password = "password-or-api-key-for-confluence-cloud"
# If you are using Confluence Cloud add the /wiki suffix to base_url
base-url = "http://confluence.local"
title-from-h1 = true
drop-h1 = true
image-align = "center"NOTE: Labels aren't supported when using minor-edit!
NOTE: See Preserving Inline Comments for a detailed description of the --preserve-comments flag.
NOTE: The system specific locations are described in here: https://pkg.go.dev/os#UserConfigDir. Currently, these are: On Unix systems, it returns $XDG_CONFIG_HOME as specified by https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if non-empty, else $HOME/.config. On Darwin, it returns $HOME/Library/Application Support. On Windows, it returns %AppData%. On Plan 9, it returns $home/lib.
It's quite trivial to integrate Mark into a CI/CD system, here is an example with Snake CI in case of self-hosted Bitbucket Server / Data Center.
stages:
- sync
Sync documentation:
stage: sync
only:
branches:
- main
image: kovetskiy/mark
commands:
- for file in $(find -type f -name '*.md'); do
echo "> Sync $file";
mark -u $MARK_USER -p $MARK_PASS -b $MARK_URL -f $file || exit 1;
echo;
doneIn this example, I'm using the kovetskiy/mark image for creating a job container where the
repository with documentation will be cloned to. The following command finds all *.md files and runs mark against them one by one:
for file in $(find -type f -name '*.md'); do
echo "> Sync $file";
mark -u $MARK_USER -p $MARK_PASS -b $MARK_URL -f $file || exit 1;
echo;
doneThe following directive tells the CI to run this particular job only if the changes are pushed into the
main branch. It means you can safely push your changes into feature branches without being afraid
that they have automatically shown in Confluence, then go through the reviewal process and automatically
deploy them when PR got merged.
only:
branches:
- mainRather than running mark multiple times, or looping through a list of files from find, you can use file globbing (i.e. wildcard patterns) to match files in subdirectories. For example:
mark -f "helpful_cmds/*.md"You can also use ** to get all files recursively.
mark -f "**/docs/*.md"By default a heading's anchor is derived from its text. To name it yourself, use
the {#custom-id} syntax:
## Release Notes {#rel}
[link to it](#rel)<h2 id="rel">Release Notes</h2>The braces are consumed rather than rendered, so the heading reads as written.
A custom id replaces the derived one rather than adding to it, so the slug of
the heading text no longer resolves -- [link](#release-notes) above would be
left exactly as written. That is what other Markdown tools do too: a custom id
is the id, not an alias.
Mark generates Confluence-compatible heading anchors, which keep their capitals
and punctuation: ## My Heading becomes id="My-Heading". A link written the
way every other Markdown tool expects -- [jump](#my-heading) -- would not name
that anchor, and would quietly go nowhere.
Mark now points such links at the anchor it actually generated, so both spellings work:
## My Heading
[slug style](#my-heading) and [exact style](#My-Heading) both resolve.Matching is on the letters and digits only, because the two conventions disagree
about which punctuation survives: a heading API/v2 Guide becomes
API/v2-Guide, while a slug of it is apiv2-guide. If two headings differ only
in punctuation that matching drops, the link is left exactly as written rather
than guessed at.
We recommend to lint your markdown files with markdownlint-cli2 before publishing them to confluence to catch any conversion errors early.
When collaborators leave inline comments on a Confluence page, updating the page via mark will normally erase those comments because the stored body is fully replaced. The --preserve-comments flag re-attaches inline comment markers to the new page body before uploading, so existing review threads survive updates.
mark --preserve-comments -f docs/page.mdOr via environment variable:
MARK_PRESERVE_COMMENTS=true mark -f docs/page.mdHow it works:
- Before uploading,
markfetches the current page body and all inline comment markers from the Confluence API. - For each existing
<ac:inline-comment-marker>tag it records the content wrapped by that marker plus a short context window immediately before the opening tag and immediately after the closing tag in the old body (not around the raw selection text, so the context is stable even when the marker wraps additional inline markup such as<strong>). - It searches the new body for the same selected text and picks the occurrence whose surrounding context best matches the original (using Levenshtein distance), so the marker lands in the right place even if nearby text has shifted.
- The updated body—with all markers re-embedded—is then uploaded as normal.
Limitations:
- If the commented text was deleted from the document, the inline comment cannot be relocated and will be lost.
marklogs a warning in this case. - Overlapping selections (two comments anchored to the same stretch of text) are detected; the earlier overlapping match is dropped with a warning, and the later one (higher byte offset) is kept, rather than producing malformed markup.
--preserve-commentsis automatically skipped for newly created pages (there are no comments to preserve yet).- When combined with
--changes-only, the comment-preservation API calls are skipped entirely on runs where the page content has not changed, avoiding unnecessary round-trips.
Mark finds an existing page by its title, and that title comes from three
independently changeable places: the Title header, the leading H1, and the
filename when --title-from-filename is set. Edit any of them and the existing
page can no longer be found, so Mark publishes a second page beside the first
and leaves the original stranded under its old name.
--track-pages records which page each source file published to, keyed on the
file path, and consults that record at the one moment the title lookup comes up
empty -- which is where "this page is new" and "this page was renamed" are
otherwise indistinguishable:
mark --track-pages --files "docs/**/*.md"Nothing is written back to your repository: no page IDs in the Markdown, no lock file, no commit. The record lives in Confluence.
| change | how it is found |
|---|---|
Title header edited |
the path is the key, so the lookup still hits |
| leading H1 edited | the same |
| file renamed | content fingerprint, matched against paths that stopped appearing |
| parent page renamed | the title it was published under, followed to the page holding it now |
| folder renamed in Confluence | the recorded folder ID, reused rather than duplicated |
| source file deleted | a recorded path absent from a run whose pattern covered it |
| two files claiming one page | the reverse index, when the second one is recorded |
document changed Space |
the same path recorded against another space |
A retitle is written even under --changes-only. The content fingerprint would
otherwise match, the update would be skipped, and the page would keep its old
title indefinitely.
Renaming a file is the awkward case: the path is the key, so a rename is a miss,
and when the title comes from the filename the title lookup misses too. What
connects them is the file's own content. Mark knows the whole file set before it
publishes any of it, so a path that has stopped appearing and a new file
carrying its content are matched to each other -- much as git log --follow
recovers a rename after the fact rather than being told about it.
| case | why |
|---|---|
| a file renamed and rewritten in one commit | the fingerprint is exact, so any content change breaks the match. Reads as a deletion plus a new page -- Git's limit too, without an -M50% to loosen it |
| an ambiguous rename | two unpublished documents sharing content, or a target page another file already claimed this run. A duplicate is a nuisance; a wrong rebind overwrites someone's page |
| anything in the first run | nothing is recorded until a file publishes with the flag set, so the first run only adopts |
a rename across two --files patterns |
matching and reporting are both scoped to the pattern that recorded the entry, so the file is a deletion in one and a new page in the other. That scoping is what lets several Mark invocations publish different folders into one space without reaching into each other's pages |
| whether a deletion was intended | it reports; it never deletes |
a Parent: header change |
still fails the run with an ancestry error rather than moving the page |
| a moved space homepage (Server/DC) | the mapping is anchored to it, so tracking starts over |
A page renamed by hand in Confluence is found by its ID and renamed back to what the file says. That is deliberate -- the repository is the source of truth, as it already is for titles and content -- but it is worth knowing before turning this on over a space people edit directly.
Mark does not delete pages, and tracking does not change that. It reports source files it can no longer account for:
space "DOCS": 2 tracked page(s) had no matching source file in this run: docs/old.md, docs/removed.md
The report is suppressed on runs that had errors, where a file that failed to process is indistinguishable from one that is gone. Each deletion is reported once: having said it, Mark stops tracking that path, so the message does not repeat forever and the mapping does not accumulate files that no longer exist. What becomes of the page itself is your call.
A dry run resolves exactly as a real one does and says what it would have done, including which existing page a retitle or a rename would have updated. It writes nothing at all -- neither to Confluence nor to the mapping.
| storage | |
|---|---|
| Cloud | space properties mark.manifest.0 … mark.manifest.15, and mark.manifest.folders |
| Server / Data Center | content properties of the same names, on the space homepage |
Space properties exist only in the v2 API, so Server and Data Center anchor to the space homepage instead. Cloud keeps the space property rather than using the homepage for both, because content properties are a v1 endpoint and v1 is what a scoped API token cannot reach.
It is split over sixteen properties rather than held in one because Confluence bounds how large a single property value may be, and one blob would cap how many files a repository may have. Each path is assigned a shard by hash; all of them are read in a single request and only the ones that changed are written back.
By default Mark reports a tracked page whose source file has disappeared and
does nothing about it. --on-orphan chooses otherwise:
| Value | Effect |
|---|---|
report |
say so and leave the page alone (the default) |
archive |
archive the page. Confluence Cloud only |
delete |
move the page to the trash |
mark --track-pages --on-orphan delete --files "docs/**/*.md"delete means the trash, which Confluence keeps recoverable. Mark never purges
a page: that second step is left to a person.
archive exists only on Confluence Cloud, and Server and Data Center report
that they have no such thing rather than appearing to have archived anything.
Confluence accepts the request and archives afterwards, so Mark reports that it
asked rather than that it finished; a failure after acceptance is not something
Mark sees.
--orphan-under narrows it to the pages below one page or folder, named by
title or by id:
mark --track-pages --on-orphan delete --orphan-under "Team Handbook" \
--files "docs/**/*.md"Without it, every tracked page the --files pattern would have published is in
scope -- which is already narrower than the space, since a pattern is only
evidence about where it was looking.
The scope applies to everything Mark does about orphans, not only to removing them. A page outside it is not reported either, and is still remembered, so a later run with a wider scope knows about it.
A combination that would leave you believing you are protected is refused outright rather than warned about, because the belief comes from silence:
--on-orphan archiveordeletewithout--track-pages--no-overwritewithout--track-pages--check-links-warn-onlywithout--check-links
A combination that merely does nothing -- --track-pages alongside a page ID,
where the mapping cannot apply -- is a warning.
--track-pagesis required, and Mark refuses to start without it. Only the manifest knows which pages Mark published, and a guess from titles is not one worth making about deletion.- A page holding child pages is left alone and reported, because removing it would take them with it and they may be pages nobody wrote in this repository.
- A run in which any file failed removes nothing: a file that failed to process is indistinguishable from one that was deleted.
- A run that published nothing removes nothing, so a failed checkout cannot empty a space.
- A document that opted out with
Synchronized: falsestill counts as present. --dry-runreports what would go and touches nothing.
A page that is left alone stays in the manifest, so the next run finds it again rather than losing sight of it.
- Paths are keyed relative to the directory Mark runs in and with forward
slashes, so
--files "$PWD/docs/*.md"and--files "docs/*.md"share one mapping when run from the same place, as do a Windows workstation and Linux CI. A file outside that directory has no better anchor and is keyed by its absolute path. --track-pageshas no effect when publishing straight to a page ID, since the mapping is per space and per file and a page ID is neither.
By default Mark overwrites whatever a page currently holds. --no-overwrite
makes it skip any page that has been edited in Confluence since Mark last
published it:
mark --track-pages --no-overwrite --files "docs/**/*.md"WRN page "Runbook" was edited in Confluence since mark published it
(version 9, mark wrote 7); leaving it alone
It needs --track-pages, because the version Mark last wrote is remembered in
the same manifest, and Mark refuses to run without it rather than appear to
guard pages it is not guarding.
A skipped page still counts as published: it is not reported as an orphan and not pruned. Nothing else about it is touched either -- no labels, no ordering, and its attachments are not re-uploaded.
The warning repeats on every run until the difference is resolved, which is
deliberate: a page drifting from its source is a standing problem, not a one-off
event. To resolve it, either bring the edit back into the Markdown, or publish
once without --no-overwrite to let Mark reclaim the page.
Comparison is by version number, not by content. Confluence rewrites storage markup on save often enough that comparing bodies would report a difference on pages nobody had touched.
- A page Mark has no recorded version for -- first run, or one published before the version was tracked -- is published normally and recorded, rather than frozen on the suspicion that it might have changed.
- Anything that creates a version counts as an edit, including a comment added in the web UI.
I've started the project to solve my own problem and open sourced the solution so anyone who has a problem like me can solve it too. I have no profits/sponsors from these projects which means I don't really prioritize working on this project in my free time. I still check the issues and do code reviews for Pull Requests which means if you encounter a bug in the program, you should not expect me to fix it as soon as possible, but I'll be very glad to merge your own contributions into the project and release the new version.
I try to label all new issues, so it's easy to find a bug or a feature request to fix/implement, if you are willing to help with the project, you can use the following labels to find issues, just make sure to reply in the issue to let everyone know you took the issue:
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!