Skill: publish-to-confluence
Publish a markdown doc (PRD / requirement card / spec) to Confluence, write the returned page id back into the source front-matter, and set an initial status label.
When to use
- PRD / spec / requirement card is ready for review by non-engineering stakeholders
- You want Confluence as the review surface but Git as source of truth
Pipeline position
Terminal step of the PRD pipeline. Pairs with confluence-sync.js (see Confluence Sync guide) which pulls comments and status-label changes back into Git.
Prerequisites
- Browser logged into Confluence or an HTTP client with API token
- Source file has a
doc_idfront-matter
Skill body
---
name: publish-to-confluence
description: Publish a markdown doc to Confluence; write back the page id to front-matter; set initial status-draft label
---
# Publish to Confluence
## Prerequisites
- Confluence login in the current browser session (or API token in env)
- Source file with `doc_id` front-matter and at least `title` + `status`
## Target
- Space key: `<your-space>` (replace)
- Page title format: `[<doc_id>] <title>` e.g. `[PRD-2026-0015] Self-serve onboarding`
## API calls (browser JS)
Create page:
```javascript
fetch("/wiki/rest/api/content", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({
type: "page",
title: pageTitle,
space: { key: "YOUR_SPACE" },
body: { storage: { value: htmlContent, representation: "storage" } },
}),
});
```
Update existing page:
```javascript
// first fetch current version
const current = await fetch("/wiki/rest/api/content/{pageId}?expand=version");
const version = current.version.number;
fetch("/wiki/rest/api/content/{pageId}", {
method: "PUT",
body: JSON.stringify({
type: "page",
title: pageTitle,
space: { key: "YOUR_SPACE" },
version: { number: version + 1 },
body: { storage: { value: htmlContent, representation: "storage" } },
}),
});
```
Add status label:
```javascript
fetch("/wiki/rest/api/content/{pageId}/label", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify([{ prefix: "global", name: "status-draft" }]),
});
```
## After publishing — MANDATORY write-back
1. Read the source markdown
2. Update front-matter `related.confluence_page_id` with the returned id:
```yaml
related:
confluence_page_id: "123456789"
```
3. Save back
4. Report to the user:
- File path
- Confluence URL: `https://your-org.atlassian.net/wiki/spaces/YOUR_SPACE/pages/<id>`
- Title
If `confluence_page_id` is already set in the source, use the **update** flow, not create. Don't create duplicate pages.
## Label convention (enables confluence-sync)
After publishing, add one `status-*` label. The `confluence-sync.js` cron reads these and updates front-matter `status`:
| Label | Status |
|---|---|
| `status-draft` | Draft |
| `status-in-review` | In Review |
| `status-approved` | Approved |
| `status-deprecated` | Deprecated |
New pages default to `status-draft`. Reviewers change the label in Confluence UI; the Git `status` field updates within 30 minutes.
## Next step
1. Notify reviewers via the relevant channel
2. For PRD: reviewers change label to `status-in-review` in Confluence
3. Subsequent edits: use the **update** flow (requires `version + 1`)
Companion: automatic back-sync
See the Confluence Sync guide. Once a page is published + labeled, the cron job (scripts/confluence-sync.js) will:
- Pull new comments into a
## Confluence Commentssection of the source markdown - Map the Confluence
status-*label into the source'sfront-matter.status - Auto-open a PR if anything changed
The publish step is what gets the page id recorded; the sync step is what keeps the two in step over time.