Thanks for your interest in contributing to Contentrain Studio.
Please read these documents before opening a pull request:
Security vulnerabilities must be reported privately to security@contentrain.io, not through public issues.
Community contributions are welcome for the AGPL core of the product, including:
- Bug fixes
- Tests
- Documentation
- Developer tooling improvements
- Accessibility and UX improvements
- Performance, correctness, and maintainability improvements
The proprietary ee/ directory is not open for general community contribution unless maintainers explicitly coordinate that work with you.
Contentrain Studio uses the DCO, not a CLA.
By contributing, you certify that you have the right to submit the work under the project license terms. Sign off each commit with:
git commit -sThis appends a Signed-off-by: line to your commit message.
- Node.js 22+
- pnpm 10+
- Supabase CLI
- Git
- A working
.envfile based on.env.example
git clone https://github.com/Contentrain/studio.git
cd studio
pnpm install
cp .env.example .env
pnpm db:start
pnpm db:migrate
npx contentrain generate
pnpm devUseful commands:
pnpm lint
pnpm lint:fix
pnpm typecheck
pnpm test:unit
pnpm test:integration
pnpm test:nuxt
pnpm test:rls
pnpm test:e2e
pnpm test:ci
pnpm buildThe most important project rules are architectural, not stylistic.
Contentrain Studio is deployment-flexible. The AGPL core can be self-hosted, and paid plans can also be operated as managed services. External services are accessed only through provider interfaces in server/providers/.
Do not:
- Import
@supabase/supabase-jsoutside provider implementations - Use provider-specific auto-injected composables in routes, pages, or components
- Leak vendor details into application logic
- Hardcode plan logic when
hasFeature()exists
Application code should depend on provider interfaces and factories in server/utils/providers.ts.
- Use the Studio semantic color system, not raw Tailwind color families
- Follow the atomic component structure: atoms, molecules, organisms
- Keep app layouts inside
app/layouts/ - Use
NuxtImgfor images - Preserve accessibility rules for button types, focus visibility, and ARIA usage
User-facing UI text belongs in the Contentrain dictionary and should be accessed through the generated @contentrain/query client.
If you add new UI text:
- Add the string to the relevant dictionary model
- Regenerate the client
- Use the generated lookup instead of hardcoding text
Contentrain Studio follows an open-core model:
- Core: AGPL
ee/: proprietary
Do not copy enterprise-only behavior into core or make core depend on ee/ implementation details. Graceful degradation should keep core functional without enterprise code present.
Studio is trunk-based: main is the single integration branch and the PR target for all work.
| Branch | Role | Deploy target |
|---|---|---|
main |
Trunk — default, PR target, OSS face | staging.contentrain.io auto; prod on v* tag |
feat/* |
Per-task feature branches | (no auto-deploy) |
fix/* |
Per-task bug branches | (no auto-deploy) |
- Fork the repo (or branch off
mainif you have push access) - Open your PR with
mainas the base branch (GitHub's default — no action needed) - CI runs lint, typecheck, tests, RLS, E2E, and build
- A maintainer reviews and merges → the change auto-deploys to
staging.contentrain.iovia Railway - When the maintainers cut a version tag (
v*), that tag triggers the production deploy
If you deploy Contentrain Studio yourself, track tagged releases (v0.1.0, v0.2.0, …), not main HEAD. Tags are the supported stability contract. main is stable-at-HEAD for CI but may include not-yet-released changes at any moment. See docs/RELEASING.md for the release cadence.
- Conventional Commits are enforced (commitlint + husky)
- Commits authored by the Contentrain MCP bot (
[contentrain] ...) are auto-ignored by commitlint; every human commit must follow the conventional format - Husky and lint-staged run on commits
- ESLint is the formatter/linter of record
- This repository does not use Prettier
Keep changes focused. Avoid mixing refactors, docs, and feature work in a single PR unless they are inseparable.
Choose the smallest relevant set, but cover behavioral changes with real tests whenever possible.
test:unit: pure logic and route handler teststest:integration: route integration and server wiringtest:nuxt: composables and Nuxt runtime behaviortest:rls: Row-Level Security behaviortest:e2e: browser and user-flow regressions
Before opening a PR, run:
pnpm lint
pnpm typecheck
pnpm test:ciAlso run pnpm test:rls and pnpm test:e2e when your change touches auth, permissions, delivery, forms, media, or end-user workflows.
When opening a PR:
- Link the relevant issue or discussion
- Explain the change and the user-facing impact
- Add or update tests for regressions
- Update documentation when behavior or contracts change
- Keep architectural boundaries intact
Large or risky changes should start as an issue or discussion before implementation.
Use GitHub issue templates for:
- Reproducible bugs
- Concrete feature proposals
Use GitHub Discussions for questions, architecture discussion, and early idea validation.
- Technical/product questions: GitHub Discussions
- Security reports: security@contentrain.io