TryGhost/framework: a pnpm monorepo of shared @tryghost packages
A collection of handy components for building Node.js applications
At a glance
- What is it?
- TryGhost/framework is not a web framework. It is the Nx-managed monorepo that holds the @tryghost/* libraries Ghost services import, and its README is mostly a pointer to per-package documentation.
- Who is it for?
- Adopt TryGhost/framework if you are extending Ghost itself or need the same shared primitives it uses, such as @tryghost/errors, @tryghost/security or @tryghost/api-framework, and you are comfortable reading each package's own README before installing it. Do not adopt it as a general-purpose Node.js framework: the root package is private, pnpm dev is a placeholder, and the repository is a workspace of libraries rather than an application scaffold.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What TryGhost/framework actually is, and who it is for
The name invites the wrong expectation. The README describes Framework as "a monorepo of `@tryghost/*` packages used across Ghost services, apps, and tooling", with each package under `packages/*` carrying its own README. There is no application entry point, no request router, no template engine. The top-level `package.json` sets `"private": true`, which means the root is never published to npm; only the individual packages are.
The audience is narrow and specific. You are a maintainer working inside Ghost, or a contributor adding a package that Ghost services will import. The README names four packages as common entry points: `@tryghost/api-framework` for API request pipeline helpers, `@tryghost/errors` for shared Ghost error types, `@tryghost/security` for token, password and identifier helpers, and `@tryghost/express-test` for HTTP test helpers. If your project is a standalone Node.js service with no relationship to Ghost, none of these packages solves a problem you have.
The value is consistency. Error shapes, token handling and test helpers stay identical across services because they come from one workspace rather than being copied between repositories. That is a maintenance argument, not a feature argument, and it only applies if you are already inside the Ghost ecosystem.
How the workspace is wired: pnpm, Nx and per-package publishing
Two tools do the work. pnpm manages the workspace through `pnpm-workspace.yaml` and `pnpm-lock.yaml`, and the root `package.json` pins `"packageManager": "[email protected]"`, so Corepack resolves the same pnpm version for everyone. Nx sits on top and orchestrates tasks across packages; `nx.json` and the `@nx/js` devDependency are what make `pnpm test` fan out to every package instead of running once at the root.
Test execution goes through Nx directly. The `test` script is `nx run-many -t test --parallel=10 --outputStyle=dynamic-legacy`, and `test:ci` is the same target with `--outputStyle=static`. Linting and formatting are separate tools: `oxlint` reads `.oxlintrc.json` and scans `packages`, while `oxfmt` reads `.oxfmtrc.json` and handles `js/ts/json/md` files. A `lint-staged` block runs `oxfmt` on staged package files, wired through Husky via the `prepare` script.
Publishing is deliberately decoupled from the repository. Release commands run at the top level and create a version commit plus tags on `main`. CI then takes over in `.github/workflows/publish.yml`: it authenticates to npm through Trusted Publishing over OIDC rather than a stored token, compares each `packages/*` version against what npm already has, and runs `pnpm publish` through `nx release publish` only for versions that are not already there, with provenance attestations enabled. The practical consequence is that a release can bump many packages at once and CI will publish only the ones that actually changed version.
Installing the workspace and consuming a single package
There are two distinct install paths, and mixing them up wastes time. If you are working in the repository itself, the README says to use the repo-pinned package manager from the root of the checkout:
corepack pnpm installCorepack reads the `packageManager` field and fetches pnpm 12.4.1, so you should not need a global pnpm install. The README also documents `pnpm setup` from the top level, which it says installs all external dependencies and links all internal dependencies. For a fresh clone, `pnpm setup` is the more complete step because it also links the workspace packages to each other.
If you are a consumer rather than a contributor, you install one package from npm. The README gives this example:
pnpm add @tryghost/<package-name>Replace the placeholder with a real package name, for instance `@tryghost/errors`. The README's own instruction for usage is blunt: read the package README for the package you are using. There is no root-level usage guide, and the four links it lists are the intended starting points. Expect to open `packages/errors/README.md` or `packages/security/README.md` before you can write a single import, because the root document does not describe exported functions or their signatures.
Adding a package, and what slimer does
The README describes a scaffolding path for new packages. You install slimer, a separate tool from the same organisation, and run it:
slimer new <package name>The README does not document what slimer generates, which files it touches, or whether it registers the package in any workspace configuration. That gap matters more than it looks. In an Nx workspace, a new package usually needs task configuration and a publishable `package.json` before `nx run-many -t test` will pick it up, and the README does not say whether slimer produces those. Treat the command as a starting point and inspect the generated tree before assuming the package participates in the workspace's test and release targets.
The same documentation gap applies to `pnpm dev`. The root script is literally `echo "Implement me!"`, and the README confirms it is a placeholder at the workspace root, advising you to run package-specific scripts from the package directory when a package has a development workflow. Anyone who clones the repository expecting a dev server will get an echoed string instead.
Releasing, scoping and the dry-run flag
Release commands live at the top level and map to semver levels: `pnpm ship:patch`, `pnpm ship:minor`, `pnpm ship:major`, plus `pnpm ship:first-release` for the initial Nx bootstrap in long-unreleased repositories. The default behaviour, per the README, is to bump every package under `packages/*` to the same level.
That default is a real constraint. A one-line fix in one package produces version bumps across the whole workspace unless you scope it. The README documents the escape hatch: append `--projects=` with comma-separated npm package names, not directory names, for example `pnpm ship:minor --projects=@tryghost/api-framework,@tryghost/domain-events`. Appending `--dry-run` previews which packages would be bumped without committing. If you are releasing frequently, the dry run is worth making a habit, because the difference between a directory name and an npm package name in `--projects=` is an easy mistake to make and the failure mode is a release you did not intend.
Before any of this runs, the `preship` script enforces a clean tree and runs the test suite. It fails if `git diff` or `git diff --cached` reports changes, printing "Error: working tree must be clean before shipping". The ship script itself pushes tags to the remote, defaulting to `origin` unless `GHOST_UPSTREAM` is set.
Where the repository is thin, and when it is the wrong tool
The most obvious limitation is documentation depth at the root. The README is an index. It tells you that `@tryghost/api-framework` handles API request pipelines, but not what a pipeline looks like, which middleware it exposes, or how errors propagate through it. Every substantive question routes you into a package directory, and the quality of those READMEs is not something the root document vouches for.
The second limitation is that the repository is not a framework in the sense most people searching for one mean. There is no CLI to scaffold an application, no routing layer, no opinion about project structure outside the workspace itself. If you arrived expecting something comparable to a general Node.js web framework, this is the wrong repository and the README will not correct that impression until you notice the word monorepo.
A third constraint is tooling lock-in. Adopting the workspace means adopting pnpm 12.4.1, Nx 23.2.0, oxlint and oxfmt, and the Nx release flow. That is a coherent stack, but it is not negotiable if you want the root scripts to work, and it is heavier than most small libraries need. The trade-off is deliberate: Nx buys parallel test execution across many packages, and the release tooling buys selective publishing, but both add configuration surface that a single-package repository would not carry.
Alternatives and what changes if you leave
The closest structural alternative is a plain pnpm workspace without Nx. You keep `pnpm-workspace.yaml` and the linking behaviour, and you drop `nx.json`, the `@nx/js` dependency and the `nx run-many` test fan-out, replacing them with a recursive pnpm script. You lose Nx's task graph and its caching, and you lose `nx release publish` as the publishing mechanism, which means rebuilding the selective-publish logic that compares each package version against npm. For a handful of packages, that trade is often worth making.
For the individual libraries, the alternative is not another monorepo but a different library. If you need structured error types in a Node service, there are widely used error packages that do not carry a Ghost-shaped API. If you need token and password helpers, established crypto and auth libraries cover the same ground with more documentation. The reason to pick `@tryghost/security` or `@tryghost/errors` is that you are already building against Ghost and want the exact error shapes and token semantics its services expect. Outside that context the package names are the only thing recommending them.
A third option is to vendor the specific helper you need. The packages are MIT licensed, so copying a function into your own codebase is permitted. You give up upstream fixes and the shared versioning, and you take on the maintenance yourself. For one small helper that is frequently the honest answer.
Licence, upgrade cost and what to check before adopting
The repository is MIT licensed, with the README stating "Copyright (c) 2013-2026 Ghost Foundation" and the top-level `package.json` carrying `"license": "MIT"`. MIT is permissive: it allows use, modification and redistribution provided the copyright notice and licence text are retained. That applies to the packages you install from npm as well, since they are published from this workspace. This is a description of the licence text, not legal advice; if licence compatibility matters to your organisation, have someone qualified read the LICENSE file rather than this paragraph.
The upgrade cost depends on which packages you consume. Version bumps across the workspace are coordinated by the release tooling, so a minor release can move several packages at once. If you pin exact versions, you control when you take those changes. If you use ranges, a workspace-wide bump can pull in changes to packages you did not intend to update, which is the practical reason to pin and upgrade deliberately.
Before adopting anything here, verify the package README for the specific library you want, confirm the exported surface matches the version you plan to pin, and check that the package is published on npm rather than only present in the workspace. The root README points to `packages/api-framework/README.md`, `packages/errors/README.md`, `packages/security/README.md` and `packages/express-test/README.md` as the common examples, and those four files are where the actual usage documentation lives.
Editorial conclusion
Adopt TryGhost/framework if you are extending Ghost itself or need the same shared primitives it uses, such as @tryghost/errors, @tryghost/security or @tryghost/api-framework, and you are comfortable reading each package's own README before installing it. Do not adopt it as a general-purpose Node.js framework: the root package is private, pnpm dev is a placeholder, and the repository is a workspace of libraries rather than an application scaffold. Before you commit, open packages/api-framework/README.md and confirm the exported helpers match the version you intend to pin, then run pnpm lint and pnpm test from the checkout to see the workspace's own tooling pass on your machine.
Frequently asked questions
What is TryGhost/framework?
It is a monorepo of @tryghost packages used across Ghost services, apps and tooling, with each package under packages/* and its own README. The root package.json is private, so the repository itself is not published to npm.
How do I install TryGhost/framework packages?
Contributors run corepack pnpm install from the root of the checkout, or pnpm setup to install external dependencies and link internal ones. Consumers install a single package from npm with pnpm add @tryghost/<package-name>.
How do I release a new version of a @tryghost package?
Run pnpm ship:patch, pnpm ship:minor or pnpm ship:major from the top-level framework directory. By default every package under packages/* is bumped to the same level, and you can scope it with --projects= using comma-separated npm package names, or preview with --dry-run.