This repo is the source of docs.bitrise.io. It's a Docusaurus 3 site sourced from Markdown / MDX in docs/ plus reusable fragments in src/partials/. Anyone editing here — human or AI — must keep the build green and all 444 live URLs intact. The rules below let you do that deterministically.
If a question isn't answered here, the canonical sources are:
- Bitrise Style Guide on Confluence — voice, tone, terminology, formatting (longer-form)
migration/glossary.json— every Bitrise-specific term we recognizemigration/partials_index.json— every reusable content chunk
See README.md — it has the full step-by-step (Node install → npm install → npm start).
| Path | What it's for | Touch when |
|---|---|---|
docs/<section>/<page>.mdx |
A documentation page — always .mdx, never .md. Subfolder structure becomes the sidebar tree. |
Editing or adding pages. |
docs/<section>/<sub>/_category_.json |
Sidebar category metadata (label, position, optional link). link: null means "non-clickable toggle". |
Renaming/reordering sidebar entries; never delete by hand. |
src/partials/<slug>.mdx |
Reusable content fragment imported by <Partial_X />. Edit here once, every consumer updates. |
Editing shared content; adding new reusable chunks. |
static/img/<topic>/<file>.png |
Static images served at /img/<topic>/<file>.png. UUID-prefixed files in _paligo/ are migration-managed — don't rename. |
Adding new screenshots; replacing existing ones. |
static/llms.txt |
Hand-curated index of the docs for AI agents (#114). docusaurus-plugin-llms generates llms-full.txt and per-page .md mirrors, but not the root llms.txt — that file is maintained by hand. bitrise.io/llms.txt, kept in bitrise-io/llms-txt, points its documentation section here rather than repeating it, so this file is the developer surface an agent reaches from the marketing site. |
Adding a product area or major section; renaming or moving a page listed in it. |
src/pages/index.tsx |
The portal landing page (/). |
Changing the homepage cards/links. |
src/components/GlossTerm/ |
Tooltip glossary component. | Almost never. |
migration/ |
Paligo→Markdown converter + supporting JSON (URL map, partial index, glossary, nav labels). | Re-running the full migration only. |
docusaurus.config.ts |
Site config + markdown.preprocessor (escapes JSX-looking text, expands list-context partials). |
Changing site-wide config, navbar, footer, integrations. |
redirects.json |
Cloudflare redirect rules. The 444 live URLs are the source of truth. | Anytime a page slug changes. |
src/css/custom.css |
Brand colors and Docusaurus IFM overrides. | Visual changes. |
The full guide is on Confluence. The actionable rules:
We talk to people, not at them. American English. Three properties:
- Clear — a quick scan tells the reader what the page is about.
- Direct — get to the point. Lead with what they need.
- Helpful — the goal is to help users get things done, not to impress.
| ✗ Don't | ✓ Do |
|---|---|
| Configure your Bitrise user account to receive email notifications on the subject of the builds triggered either automatically or manually. | Set up email notifications for your builds on your account. |
| Bitrise is a platform with many different capabilities. We have a lot of features that are very simple to use for your Continuous Integration needs. | Bitrise is a CI/CD service that is easy to use, with a wide range of features. |
| The setting enables user-created tests with valid targets only. | Set it to run your tests but make sure they have valid targets. |
| The bitrise.yml will determine the configuration of your build. | The bitrise.yml determines the configuration of your build. |
| An Organization is set up from the Account settings page of a user's account. | Set up a new Organization from the Account settings page of your account. |
- Tense: present. Things you describe happen now, not later.
- Voice: active. ("Bitrise downloads the source" — not "the source is downloaded by Bitrise".)
- Mood: indicative for descriptions, imperative for procedures. Don't switch within a sentence.
- Acronyms: spell out on first use, then use the acronym (
Unique Device Identifier (UDID), thenUDID). Skip if your audience certainly knows it (SSH,URL,API).
- Sentence case for all titles, headings, and UI labels. Not title case.
- ✓
Registering a test device - ✗
Registering a Test Device
- ✓
- Capitalize Bitrise-specific proper nouns:
Bitrise,Step,Workflow,Workflow Editor,Environment Variable(Env Var),Organization(Org),Org Elite,Org Standard,Dashboard,Identity Provider(IdP). - Lowercase general-purpose nouns even though they describe Bitrise features:
app,project,build,repository,stack,certificate,provisioning profile,pull request,virtual machine(VMis OK uppercase),log in,sign up. - Match UI capitalization when you reference a UI element verbatim —
**APPS & ARTIFACTS**if that's how the screen reads.
- Spell out 0–9, use numerals for 10+.
- Be consistent within a category: if any number in the category needs numerals, all of them do (
between 5 and 50, notbetween five and 50). - Inside tables and UI text, numerals are always fine.
- Use 2–7 items. Never a list with one item.
- Ordered for procedures (numbered steps).
- Unordered for grouped options or requirements.
- Never nest lists.
- Capitalize the first word of each item.
- End each item with a period only if any item is a full sentence (or completes one with the introduction). Otherwise no period.
- Introduce lists with a sentence or fragment ending in a colon.
- Procedures — gerund:
Running Xcode tests on Bitrise. - Concepts — short noun phrase, replaceable with "it":
Selective builds. - Avoid generic page titles (
Overview,Introduction) — they're fine as section headings inside a page.
- No end punctuation in titles, headings, UI labels, or short list items (≤ 3 words).
- Periods end sentences. Avoid exclamation marks except in critical warnings.
- Question marks sparingly — we answer questions, we don't ask them.
- No quotation marks for emphasis. Say it directly.
- Bold for permanent UI labels:
Click **Save changes**.,Open the **Workflows** tab. - Backticks for user-renameable identifiers: workflow names (
`release-build`), branch names (`main`), Step IDs.
migration/glossary.json is the canonical list. Most-used entries:
| Term | Use | Don't use |
|---|---|---|
| Bitrise | The product. | bitrise (lowercase, except in bitrise.yml) |
bitrise.yml |
Always lowercase, in backticks. | Bitrise YAML, Bitrise YML |
| Step (capital S) | A single integration in a Workflow. | step, integration |
| Workflow (capital W) | A sequence of Steps. | workflow (except in code) |
| Workflow Editor | The UI for editing Workflows. | workflow editor |
Environment Variable / Env Var |
Capitalized. Use abbreviation in titles. | environment variable |
| Organization / Org | Capitalized. Use Org in titles. |
organization |
| project | Lowercase. Within an app. | Project, app (synonyms — they're not) |
| app | Lowercase. The Bitrise unit. | application, project |
| build | Lowercase. The output of a Workflow run. | Build |
| pull request | Lowercase. Spelled out. | PR, pull-request |
| repository | Lowercase. Spelled out in DevCenter. | repo |
| URL | Always uppercase. | url, Url |
| CI/CD | The shorter form. Use continuous integration and delivery only on landing pages. |
CI / CD with spaces |
| log in (verb) | Two words for the action. | sign in, login |
| sign up (verb) | Two words for the action. | signup, register |
| stack | Lowercase generic. Capitalize when quoting a specific stack name (Android & Docker, on Ubuntu 16.04 - LTS Stack). |
Stack |
| guide / article | Synonyms for a DevCenter document. | post (that's blog content) |
| deploy key | Two words. GitHub's term — keep it. | deployment key |
If you introduce a new Bitrise-specific term, add it to migration/glossary.json so <GlossTerm> works. The format is one entry per term:
"workflow": {
"term": "Workflow",
"definition": "A collection of Steps, environment variables, and other configurations."
}Every page starts with YAML frontmatter:
---
title: "Running Xcode tests on Bitrise"
description: "Configure the Xcode Test Step to run unit and UI tests on Bitrise."
sidebar_position: 3
slug: /bitrise-ci/testing/running-xcode-tests-on-bitrise
sidebar_label: "Run Xcode tests" # optional, only if it differs from title
---titleandslugare required. Always adddescriptiontoo, on every new page — it's not just SEO copy: pages without their ownimagefrontmatter get a social preview card auto-generated fromtitle+description(seefunctions/og.js), so a missing description means that page's link previews fall back to the generic site tagline instead of describing the actual page.- Slugs always start with
/. Otherwise Docusaurus prefixes them with the file's natural path and you get duplicated segments. - Don't change an existing slug without adding a redirect in
redirects.json. Live URLs are part of our SEO contract. sidebar_labellets the navigation entry differ from the page's H1.
Every page under docs/ is .mdx. Create new ones that way too, even when
the page has no JSX in it today. There are no .md pages left in docs/; if
you see one, rename it.
The reason is translation. A protected term is marked in the source with a
<NT> do-not-translate wrapper (src/components/NT), and that's JSX — a .md
page physically cannot carry one. The tagging pass that adds those wrappers
only globs **/*.mdx, so a stray .md page is skipped silently: no error,
just a page whose product names, UI labels, and Step names go unprotected and
get machine-translated.
Two things follow from the extension, both of which turned up as build failures
during the bulk rename on pages that had been fine as .md:
- The file is parsed as MDX, not CommonMark. Bare
<Word>placeholders and{kebab-case}become JSX and fail the build (see Common pitfalls below), and void HTML tags must self-close —<br/>, not<br>. docusaurus.config.ts'smarkdown.preprocessoris gated on.mdx, so the page now also goes throughexpandListPartialsand the JSX tag escaper.
Importing a partial, <Tabs>, <GlossTerm>, or <NT> needs the matching
import line at the top:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import GlossTerm from '@site/src/components/GlossTerm';
import NT from '@site/src/components/NT';Renaming .md → .mdx needs no redirect: every page carries an explicit
slug:, and Docusaurus strips either extension when building the route.
Scripts that write pages must emit .mdx too — scripts/sync_mcp_docs.py
pins the extension rather than reusing the source repo's filename, because a
.md twin of an existing .mdx page is a duplicate-route error (both carry
the same slug:).
- New images go to
static/img/<topic>/<filename>.<ext>(e.g.static/img/code-signing/keychain-export.png). Reference as/img/code-signing/keychain-export.png. - Always include alt text:
. - Capture at 1728 × 875 (per Style Guide). Resize the browser window to that exact size before screenshotting; it gives consistent dialog framing.
- Don't rename the UUID-prefixed files in
static/img/_paligo/— they're migration-managed and referenced by UUID across the corpus.
- Inline: backticks for filenames, command names, env var names.
Open `bitrise.yml`. - Blocks: triple backticks with a language hint (one of
yaml,bash,json,swift,kotlin,groovy,ruby,dart,diff— those are the languages our Prism config loads). - Don't put code blocks inside admonitions. Render the admonition first, then the code block as a sibling.
Showing a change to an existing file
Readers meet two situations, and they need two different blocks.
Use a diff block when the reader edits a file they already have and the change is a few lines. Mark added lines with +, removed lines with -, and keep enough surrounding context that the reader can find the spot:
dependencies {
+ implementation("com.microsoft.codepush.react:react-native-code-push:+")
}Use a normal language-tagged block when the file is new, or when you show it whole.
One page uses one convention. Don't mix a diff block with a // ...existing placeholder or a bare ... on the same page.
A diff block is for reading, not for pasting. The copy button hands the reader the + and - characters, so if a snippet is meant to be copied whole, it isn't a diff block — show the full file instead.
:::note[Workflows in YAML]
This guide is about creating a Workflow in the Workflow Editor.
:::Available types: note, tip, info, warning, important, caution. Title in square brackets is optional.
When to use which (per Style Guide):
note— heads-up ("Please note that…").important— non-skippable info on prerequisites or constraints.warning— something that can break or can't be undone.info— extra context, tips & tricks.tip— best-practice suggestion.caution— sharper thannote, less thanwarning.
Use for parallel instructions (e.g. Workflow Editor vs Configuration YAML):
<Tabs>
<TabItem value="workflow-editor" label="Workflow Editor" default>
1. Open the Workflow Editor.
1. Click **Save changes**.
</TabItem>
<TabItem value="configuration-yaml" label="Configuration YAML">
```yaml
workflows:
primary:
steps: []valueis the URL hash slug (kebab-case).labelis what the user sees.- Mark the first tab
default.
- Internal links use bare absolute paths — no locale prefix. Each locale
(
en,ja, ...) has its ownbaseUrl(seedocusaurus.config.ts'si18n.localeConfigs), so a hardcoded/en/...prefix breaks under any other locale (/ja/en/..., which doesn't exist). Docusaurus resolves a bare path against whichever locale is currently rendering. - The link target is the page's slug, not its file path:
See [Adding a new project](/bitrise-ci/getting-started/adding-a-new-project).
- Don't add the
.htmlsuffix; Docusaurus handles it. - Keep the leading slash. These are absolute paths;
bitrise-ci/foowithout it resolves relative to the current page, which looks correct on a hub landing page and is wrong everywhere else. - In JSX (components,
docs/*/index.mdxprops), link with<Link to="/path">and resolve assets withuseBaseUrl('/img/...'). A raw<a href>or<img src>skips baseUrl resolution entirely, so it requests the path at the domain root — where nothing is served (see the root-files pitfall below). - For glossary terms, prefer
<GlossTerm baseform="Workflow">Workflow</GlossTerm>on first mention so readers get the inline tooltip.
The biggest authoring lever in this repo. Edit one file, every page that uses it updates.
Anything reused across pages: a setup-prerequisites paragraph, a "how to open the Workflow Editor" set of steps, a shared admonition. Each lives at src/partials/<readable-slug>.mdx (e.g. opening-the-workflow-editor.mdx).
- Find the right one in
migration/partials_index.json(key = component name, value = file slug). - In your page (
.mdx), add the import at the top:import Partial_OpeningTheWorkflowEditor from '@site/src/partials/opening-the-workflow-editor.mdx';
- Reference it where the content goes:
- Block context (between paragraphs, in a section): on its own line —
<Partial_OpeningTheWorkflowEditor /> - List context (inside a numbered procedure): as a list item —
1. <Partial_OpeningTheWorkflowEditor />. The build's preprocessor splices the partial's actual list items in, so step numbering stays continuous.
- Block context (between paragraphs, in a section): on its own line —
- Create
src/partials/<readable-slug>.mdxwith the standard import header (Tabs,TabItem,GlossTerm) and your content. - The slug is kebab-case derived from the topic title (
opening-the-workflow-editor, notopening_the_workflow_editororOpeningTheWorkflowEditor). - The component name is
Partial_+ PascalCased title (Partial_OpeningTheWorkflowEditor). - Update
migration/partials_index.jsonto register the mapping (component → slug). - Reference it from any page that needs it (see above).
- Don't paste a partial's content directly into a page. The whole point is one source of truth.
- Don't put a section heading at the top of a partial that's used in list context — the preprocessor only extracts the first list block, and a heading would be left dangling.
Every Bitrise-specific term that appears in body text should use the <GlossTerm> component on its first mention per page. The reader gets a hover tooltip with the definition, and clicking jumps to the glossary page.
A <GlossTerm baseform="Workflow">Workflow</GlossTerm> is a collection of Steps.baseformis the canonical form (matches a key inmigration/glossary.json).- The element's text is what's displayed (so it can be inflected —
Workflows,workflow). - If
migration/glossary.jsondoesn't have the term yet, add it:{ "workflow": { "term": "Workflow", "definition": "A sequence of Steps that Bitrise runs to build, test, or deploy your app." } }
These caused build failures during the migration. Watch for them.
✗ Replace <username> with your handle.
✓ Replace `<username>` with your handle.MDX parses bare <Word> as a JSX tag and the build fails. Wrap placeholder tokens in backticks (or HTML entities <username>).
✗ | `GET /apps/{app-slug}/builds` | List builds. |
✓ | `GET /apps/\{app-slug\}/builds` | List builds. |MDX reads {...} as a JSX expression. Hyphens make it invalid (kebab-case isn't a valid JS identifier), so MDX errors. Escape both braces.
If you rename a page or change its slug, add a rule in redirects.json so the old URL still resolves. Cloudflare serves the redirects; the 444 live URLs are part of our SEO contract. If the page is listed in static/llms.txt (the hand-curated AI-agent index), update its link there too — the weekly check-llms-txt.yml job fails on dead llms.txt links.
Some sidebar categories are intentionally non-clickable — they only toggle expand/collapse. Their _category_.json has "link": null. Adding an index.mdx makes Docusaurus auto-link the category and breaks that behavior.
All images go under static/img/. References in pages start with /img/... (Docusaurus serves static/ from root). Don't reference static/img/... directly — that path doesn't exist at runtime.
Each locale has its own baseUrl, so Docusaurus writes the whole build — all of
static/, plus the generated sitemap.xml and the docusaurus-plugin-llms
output — into build/en/ and build/ja/. Nothing lands at build/ itself.
Anything that a client can only ever request at the root (/robots.txt,
/sitemap.xml, /llms.txt, /favicon.ico, /changelog.xml, 404.html,
_redirects, _headers, the <page>.md mirrors) is copied back up by
scripts/promote-root-static-files.js,
which runs as part of npm run build. If you add a file that needs a root URL,
add it there too — the build stays green either way, so nothing else will catch
it. Large directories (/img, /fonts) are 301'd to /en/... in
static/_redirects instead of being copied a third time.
Cloudflare Pages Functions are routed by their path under functions/, so a
function reached from a page also needs a locale-prefixed route (see
functions/[locale]/og.js) — page-emitted URLs go through baseUrl.
Code blocks, admonitions, and plain text that belong to a numbered list item must be indented to the item's content column (3 spaces for 1. , 4 for 10. , etc.). Unindented content terminates the list, causing every subsequent item to restart numbering from 1.
✗ 1. Do the thing
```bash
code
```
2. Do the next thing ← renders as step 1
✓ 1. Do the thing
```bash
code
```
2. Do the next thing ← renders as step 2The same rule applies to admonitions and continuation paragraphs mid-procedure.
Sentence case only. ✓ Adding a new project ✗ Adding a New Project.
When asked to sync, pull, or update the Bitrise MCP docs, run:
python3 scripts/sync_mcp_docs.pyThis fetches .md files from bitrise-io/bitrise-mcp/docs/ on GitHub and writes them as .mdx (with injected frontmatter, link rewriting, and MDX/list rendering fixes) to docs/bitrise-platform/ai/bitrise-mcp/. Set GITHUB_TOKEN in the environment for authenticated requests (5,000 req/hr vs 60 req/hr unauthenticated).
The script is idempotent. After running, review the diff and commit if the changes look correct. Never manually edit the synced files — edits belong in the source repo.
The Bitrise CI API reference (docs/bitrise-api/api-reference/) and the RDE API reference (docs/bitrise-rde-api/api-reference/) are generated from their OpenAPI specs using docusaurus-plugin-openapi-docs (specs configured in docusaurus.config.ts). Never edit the generated files by hand — they are overwritten on every run.
npm run gen-api-docsThis runs two things in sequence:
docusaurus gen-api-docs all— generates the.api.mdxfiles andsidebar.tsfor every configured spec (the CI API and the RDE API).node scripts/patch-api-info.js— applies fixes the plugin doesn't handle, to each generated*.info.mdx:- Adds
displayed_sidebarso the page renders inside the correct sidebar (bitriseAPISidebarfor the CI API,rdeSidebarfor the RDE API). - Fills in the empty License section with the name from the spec — CI API only (
MIT).
- Adds
Always use the npm script, not the bare docusaurus command, so the patch is always applied.
docusaurus.config.ts has a markdown.preprocessor that escapes JSX-looking text across all .mdx files. The generated API files contain multi-line JSX components (<StatusCodes>, <RequestSchema>, etc.) that the line-by-line escaper would mangle. They are skipped entirely via an early return that matches every generated reference (bitrise-api, bitrise-rde-api, …):
if (filePath.includes('/api-reference/')) return fileContent;Do not remove this rule — it prevents the preprocessor from breaking the generated files.
If docusaurus.config.ts changes (including merge commits that touch it), the webpack cache in .docusaurus/ can become partially stale, producing confusing MDX parse errors like Expected a closing tag for <StatusCodes> even though the generated files are correct. Fix: clear the cache before starting the dev server.
npm run clear # or: rm -rf .docusaurus
npm startIf you discover a new convention or pitfall while editing, add it here so the next contributor (human or AI) doesn't relearn it. Keep the file < 400 lines; if a section grows long, link to a deeper page on Confluence instead.
Before proposing any docs edit (new page, moved page, changed slug, or reworded heading), run the source-level checker on the files you touched and fix anything it flags. This catches broken internal links and missing #anchor targets at authoring time — before the PR — rather than relying on the build-time onBrokenLinks warning or the post-build link_analyzer.js.
Run node scripts/check-links-source.js docs/path/to/edited-page.mdx to check specific files, or node scripts/check-links-source.js with no arguments to scan the whole docs/ tree. Exit code is non-zero if any internal link points to a missing page or any #anchor points to a heading that doesn't exist on the target page. It follows @site/src/partials/*.mdx imports and understands OpenAPI-generated .api.mdx / .info.mdx routes, so those aren't false positives. Don't hand over a page with unresolved cross-references.
Whenever you edit or create a page (.mdx under docs/), check its frontmatter for a description field. If it's missing, warn the user explicitly before opening a PR — don't silently add a placeholder and don't skip the check. Say which file(s) lack one and ask whether to write one now or proceed without it. This matters beyond SEO: the social preview card (functions/og.js) is generated from title + description, so a page without one falls back to the generic site tagline in link previews instead of describing that page.