Thanks for your interest in contributing. This is a pnpm workspace driven by Nx that publishes fifteen packages across six products - the plugins, their renderers and the shared cores - so most commands below are run from the repository root.
Which package a change belongs to is usually obvious from the package table. If it is not, open an issue first; the boundary between a plugin, a core and a renderer is deliberate, and putting something on the wrong side of it is the one kind of change that is expensive to undo.
The quickest way to a working environment is Docker, which brings up the whole stack at once:
git clone https://github.com/qkix/strapi-plugins.git
cd strapi-plugin-better-blocks
docker compose up --buildThat builds the plugins and the renderers and starts a Strapi v5 app (SQLite) at
http://localhost:1337/admin, seeded with the showcase articles and an admin
account (admin@example.com / admin12#). The renderer examples come up
alongside it, all reading that same content:
| Example | URL |
|---|---|
| React | http://localhost:5173 |
| Astro | http://localhost:4321 |
| Nuxt | http://localhost:3000 |
To wipe the seeded database and uploaded media and start again:
docker compose down -v && docker compose up --buildSee examples/README.md for what gets seeded and how to run a single example.
Node 20 or 22 - the Strapi SDK refuses 23 and newer.
pnpm install
pnpm build # every publishable package
pnpm --filter @qkix/example-strapi-app develop
pnpm --filter @qkix/example-nuxt-app dev # or -react-app / -astro-app-
Create a branch from
main. -
Make your change in the package it belongs to.
-
Verify it. For plugin work that means the Strapi admin; for renderer work, the matching example app - and
docker compose up --build, because the plugins and renderers are compiled into the image, so a plaindocker compose restartdoes not pick up source changes. An example app's own source hot-reloads with no rebuild. -
Run the checks CI runs:
pnpm lint pnpm typecheck pnpm test
To work on one package, filter:
pnpm --filter @qkix/strapi-plugin-better-blocks test
pnpm exec nx run @qkix/better-blocks-vue-renderer:buildNx only reruns what a change actually affects, so a second run is mostly cache.
The shared cores. The Better Blocks document types and the
framework-independent logic live in packages/better-blocks-core; chart specs,
validation and SVG rendering live in packages/chartkit-core. If you add a block
attribute or a chart option, add it there first - every renderer reads it from
that one place, and the boundary lint rule keeps the dependency direction
honest.
Adding a block type to the Better Blocks editor is a public API, not an edit to the editor package. See Registering a block type: one definition object teaches the editor, the validator, the migrator and every renderer about the block. Chartkit's chart block is the worked example.
Renderers stay in step. A feature that changes rendered markup should land in the React, Astro and Vue renderers together, or it becomes a difference nobody remembers to close. Each has its own test suite and they share a characterization suite over the core helpers.
- Keep PRs focused - one feature or fix per PR.
- Fill out the PR template.
- Make sure CI passes (lint, typecheck, test, build) on both Node 20 and 22.
- Add screenshots or GIFs for UI changes.
- Documentation ships with the code: a change to a package's behavior updates that package's README in the same PR.
Package labels (pkg: …) are applied automatically from the paths a PR touches,
so a new package needs an entry in .github/labeler.yml
and in the issue forms.
Conventional commits, and the type is not cosmetic - nx release reads it to
decide who gets a new version.
Files that belong to no package - pnpm-lock.yaml, nx.json, the root
package.json - count as touching every package. A type that never bumps
(chore, docs, ci, build, refactor, test) is harmless there, but
fix or feat gives all fourteen packages a release whose changelog entry
says nothing about them.
So type by what ships, not by what the work felt like:
| The change | Type |
|---|---|
| Behaviour a package's users get | feat / fix |
| Examples, tooling, CI, lockfile bumps | chore / ci / build |
| READMEs, screenshots, roadmaps | docs |
Adding a dependency to an example app is a chore, however much it fixed
something locally - nothing shipped. Check with
nx show projects --affected --files=<path> when unsure.
The scope is the package's directory name. feat(strapi-plugin-rewind),
not feat(rewind). nx resolves a scope against project names and directory
names, and a scope it cannot resolve is not an error - the commit is treated as
an indirect change to every package it touched and quietly downgraded to a
patch. So feat(rewind) ships a feature as a patch and says nothing about it.
Check a scope with nx show projects --projects=<scope> before relying on it.
And the scope that counts is the one in the pull request title. main only
takes squash merges, so however many commits a branch has, one commit lands -
with the PR title as its subject. Splitting a branch into one commit per package
looks tidy and changes nothing: the split is discarded on merge and every
package but the one named in the title drops to an indirect patch.
For a change that is a feature for more than one package, name them all. nx splits a scope on commas:
feat(chartkit-core,chartkit-editor,strapi-plugin-chartkit): per-chart colors
Packages that merely depend on one of those get their patch automatically and do not belong in the list.
Releases are cut with nx release: versions, tags and changelogs are per
package, so a change to one does not bump the others. The release workflow is
manual and defaults to a dry run. A feat is a minor and a fix is a patch,
at 0.x the same as anywhere else - but a breaking change goes to 1.0.0, so
pass an explicit version to the workflow to keep a pre-1.0 package in 0.x.
Use the bug report template and include which package you are using, its version, your Strapi version, and steps to reproduce.
Use the feature request template. A description of the problem is worth more than a description of the solution - it leaves room for a better one.