docs/contributing/workflow.md
This guide covers the path from a working development environment to a reviewed pull request. Use the development setup first if Ghost is not already running locally.
Issues labelled good first issue are intended to be approachable first contributions. The broader help wanted list contains other contributions the project would welcome.
Discuss new features and substantial product or architectural changes in the Ghost Forum before implementing them. A focused bug fix or agreed improvement can usually proceed directly.
mainUpdate the canonical checkout, then create a descriptive branch:
git fetch origin
git switch main
git pull --ff-only origin main
pnpm setup
git switch -c concise-change-name
Keep unrelated changes on separate branches and in separate pull requests. If a
larger effort needs a shared or release branch, agree that with the maintainers
first; ordinary pull requests target main.
Add or update automated tests when behavior changes. Run the most focused checks for the code you touched, following the README beside that workspace. Before handing off a change, use the repository's one-stop formatting, lint, and test command:
pnpm check
pnpm check runs pnpm format:check, pnpm lint, and then pnpm test. It does
not include the browser E2E suite or Ember Admin tests, so run those separately
when the affected area requires them. CI uses the Nx affected graph and path
filters to select the relevant lint, unit, integration, acceptance, build, and
browser-test jobs for a pull request.
Source formatting is owned by Oxfmt,
configured in the root .oxfmtrc.json: Oxfmt defaults plus single quotes, with
embedded-language formatting disabled so string contents are never rewritten.
CI rejects unformatted code, and the pre-commit hook formats staged files
automatically, so there is usually nothing to do. To format manually:
pnpm format path/to/file.ts
pnpm format with no arguments formats the whole repository, and
pnpm format:check reports without writing. Do not add per-package formatter
configuration; the root config is the only one.
The one-time repository reformat is listed in .git-blame-ignore-revs. GitHub's
blame view skips it automatically, and pnpm setup configures local Git to use
the file. For an existing checkout, rerun pnpm setup or run once:
git config --local blame.ignoreRevsFile .git-blame-ignore-revs
Changes that affect a publishable @tryghost/* package under koenig/ or
packages/, including changes to catalog entries consumed by that package, need
a changeset so the package receives an appropriate version and changelog entry:
pnpm change
Choose patch, minor, or major according to the package's public compatibility impact. The summary becomes the changelog entry, so describe the result for the package's consumers.
A package README.md is included when the package is published, so changing it
requires a release. Repository-only Markdown such as AGENTS.md, CLAUDE.md,
changelogs, and files under a package's docs/ directory does not.
If a changed publishable package genuinely requires no release—for example, a test-only or internal tooling change—record that explicitly:
pnpm change --bump none
Use pnpm change status to inspect pending release intent. CI rejects changes
that affect publishable packages without a covering changeset. Changes that do
not affect a publishable package do not need one.
Follow the canonical commit message guidelines.
Everyone can clone, run, and modify the canonical repository without a fork. The
publication step depends on whether you can push branches to TryGhost/Ghost.
Push the current branch directly:
git push -u origin HEAD
gh pr create --base main
Create a fork when the change is ready to publish. With the GitHub CLI, the fork can be created from the existing canonical checkout and added as a separate remote:
gh repo fork --remote --remote-name fork
git push -u fork HEAD
gh pr create --repo TryGhost/Ghost --base main
The equivalent GitHub web or desktop flow is also fine. Tooling or a coding agent may perform these steps on your behalf; check the proposed remote, branch, and pull request before authorizing a push.
Target main unless a maintainer has asked for another base branch. The pull
request should explain:
Link the issue when one exists and include screenshots for visible UI changes. Keep the branch current if requested and respond to review feedback with new commits. CI must pass before merge; skipped jobs are expected when they are not relevant to the changed paths.