confluence.js covers both Confluence Cloud REST APIs from one package:
- Cloud REST API v2 — 30 namespaces, 218 methods
- Cloud REST API v1 — 28 namespaces, 130 methods
Both are first-class. Atlassian has not deprecated v1, and the two cover different ground: v1 still owns what v2 has no equivalent for, and v2 owns everything Atlassian has moved forward.
Every response is validated at runtime against a Zod 4 schema
derived from Atlassian's OpenAPI spec, so API drift surfaces as a ZodError
rather than as undefined three call frames later.
- ESM-only, Node ≥ 22, no polyfills — the transport is the built-in
fetch - Tree-shakable: only the endpoints you import reach your bundle
- One runtime dependency:
zod
- Getting Started
- Authentication
- Two ways to use it
- Choosing between v1 and v2
- Error handling
- Retries
- Migrating from 2.x
- Other Products
- License
npm install confluence.jsimport { createV2Client } from 'confluence.js';
const confluence = createV2Client({
host: 'https://your-domain.atlassian.net',
auth: {
type: 'basic',
email: 'YOUR@EMAIL.ORG',
apiToken: 'YOUR_API_TOKEN',
},
});
const page = await confluence.page.createPage({
spaceId: '123456',
title: 'Project Overview',
body: {
representation: 'storage',
value: '<p>Welcome to our project documentation</p>',
},
});
console.log(`Page created: ${page.title}`);host is your bare site URL. The API path (/wiki/api/v2, /wiki/rest/api)
belongs to the request, not to host — so one host serves both versions.
Create an API token in Atlassian account settings:
const confluence = createV2Client({
host: 'https://your-domain.atlassian.net',
auth: { type: 'basic', email: 'YOUR@EMAIL.ORG', apiToken: 'YOUR_API_TOKEN' },
});Hand over your app credentials and a refresh token. The client refreshes before expiry, retries once on a 401, and resolves the cloud id itself:
const confluence = createV2Client({
auth: {
type: 'oauth2',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
refreshToken: 'YOUR_REFRESH_TOKEN',
// Atlassian rotates the refresh token on every refresh — persist the new one.
onTokenRefresh: ({ refreshToken }) => tokenStore.save(refreshToken),
},
});No host: 3LO tokens only work through api.atlassian.com, never on your site's
own domain, so the client builds that URL from the cloud id. Pass cloudId or
siteUrl if the token can reach more than one site.
confluence.js also exports the flow itself — generateAuthorizationUrl,
parseCallbackUrl, exchangeAuthorizationCode, refreshOAuth2Token and
getAccessibleResources. See the OAuth 2.0 guide
— scopes differ between v1 and v2, and refresh tokens rotate.
For a bearer token from somewhere other than 3LO, hand over a resolver — called per request — and a hook to re-derive auth after a 401:
const confluence = createV2Client({
host: 'https://your-domain.atlassian.net',
auth: { type: 'bearer', getToken: () => tokenStore.current() },
getAuthOn401: async () => ({ type: 'bearer', token: await refresh() }),
});JWT (Atlassian Connect) is not supported in 3.x. See MIGRATION.md.
A client object — the whole API behind namespaces:
import { createV2Client } from 'confluence.js';
const confluence = createV2Client({ host, auth });
await confluence.page.getPages({ spaceId: ['123'] });
await confluence.space.getSpaces({ limit: 25 });Flat functions — the same endpoints as standalone functions taking a client. Nothing you do not import reaches your bundle:
import { createClient } from 'confluence.js/core';
import { getPages } from 'confluence.js/v2';
const client = createClient({ host, auth });
await getPages(client, { spaceId: ['123'] });One client drives both versions:
import { createClient } from 'confluence.js/core';
import { getPages } from 'confluence.js/v2';
import { getGroups } from 'confluence.js/v1';
const client = createClient({ host, auth });
await getPages(client, {}); // → /wiki/api/v2/pages
await getGroups(client, {}); // → /wiki/rest/api/group| Import | What it gives you |
|---|---|
confluence.js |
createV1Client, createV2Client, ApiError, config types |
confluence.js/v2 |
v2 flat functions, parameter and response types |
confluence.js/v1 |
v1 flat functions, parameter and response types |
confluence.js/core |
createClient, transport types, OAuth helpers |
confluence.js/v1 and confluence.js/v2 are not re-exported from the root: the
two versions collide on a few names (createSpace, getTasks).
Reach for v2 by default — it is where Atlassian is investing. It covers pages, blog posts, custom content, whiteboards, databases, folders, comments, attachments, labels, tasks, versions, spaces, space permissions and space roles.
Reach for v1 only for what v2 has no equivalent for: attachment upload (v2 reads attachments but cannot create them), content restrictions, watches, permission checks, content states, groups, relations, CQL search, templates, themes, settings, user properties, audit records and long-running tasks.
v1 is a complement to v2, not the old API kept alive. This package tracks
Atlassian's v1 spec as published, and that spec is shrinking: 37 operations that
2.x exposed — getContent, createContent, getSpace among them — are already
gone from it, and 33 of those have a v2 equivalent
(the map).
Expect it to narrow further, and treat a v1 call as something to move to v2 once
v2 covers it.
Every failure is one of this package's own error types — nothing leaks from
fetch. Non-2xx responses throw an ApiError subclass carrying the status and
the parsed body; transport faults throw a NetworkError; the OAuth flow throws
an OAuthError.
import { isNotFoundError, isRateLimitError } from 'confluence.js';
try {
await confluence.page.getPageById({ id: 42 });
} catch (error) {
if (isNotFoundError(error)) return null;
if (isRateLimitError(error)) await sleep(error.retryAfterMs ?? 60_000);
throw error;
}AuthError (401), ForbiddenError (403), NotFoundError (404),
RateLimitError (429) and ServerError (5xx) all extend ApiError, so
instanceof works — but prefer the isXxx predicates: they survive two copies
of the package in one node_modules, where instanceof silently returns false.
A response that does not match its schema throws a ZodError instead. That
means Atlassian's API drifted from its own spec, and it is worth
an issue.
Off by default. When enabled, retries cover transport-layer failures only — network errors and 502/503/504:
const confluence = createV2Client({
host,
auth,
retry: { maxAttempts: 3, initialDelayMs: 500, backoffFactor: 2 },
});4xx is never retried, 429 included: rate limiting is a signal to slow down, not a transient fault to paper over.
3.0 reworks the public surface — class clients became factories, authentication
became auth, callbacks and middlewares are gone, and the v1 surface now follows
Atlassian's current spec. See MIGRATION.md for the full map;
a codemod handles the mechanical parts:
npx jscodeshift -t node_modules/confluence.js/tools/codemod/v2-to-v3.ts src/MIT License © MrRefactoring See LICENSE for details.