ContextHub is an open-source, multi-tenant headless CMS for agencies and multi-brand organizations.
Run content, media, forms, menus, custom data and digital experiences for multiple tenant-scoped sites from one API and administration stack. ContextHub is built for teams that want an API-first alternative to duplicating WordPress or CMS installations for every client, brand or project.
ContextHub Cloud · Cloud documentation · Developer documentation · Integration guide · Comparisons
- Agency and multi-brand operations – manage many customers, brands and sites from one deployment without mixing their users, roles, content or configuration.
- Tenant isolation as a platform concern – tenant context,
tenantIddata partitioning, tenant-scoped RBAC and scoped API tokens are part of the core model. - API-first delivery – use structured content across websites, mobile applications, portals and other channels without coupling presentation to the CMS.
- More than content storage – forms, webhooks, placements, personalization, feature flags, A/B tests, funnels and realtime reporting share the same tenant boundary.
- Open core and extensible – deploy the MIT-licensed core yourself and add trusted integrations through the versioned Plugin API.
You need Node.js 24.13.1 or newer, pnpm 10.13.1 and a running MongoDB instance. Redis is recommended for shared rate-limit and usage state, but local development fails open when it is unavailable.
git clone https://github.com/devburak/contextHub.git
cd contextHub
corepack enable
pnpm install
MONGODB_URI=mongodb://127.0.0.1:27017/contextHub pnpm devThe API starts on http://localhost:3000, its health endpoint
is /health, and interactive OpenAPI documentation is available at /api/docs.
The Vite development server prints the local Admin URL when it starts. Copy
.env.example to .env when you need to configure storage, trusted origins, email,
the edge gateway or production security settings.
flowchart LR
A[Admin and editors] --> UI[React Admin]
C[Web, mobile and other channels] --> SDK[REST API and SDKs]
UI --> API[Fastify API]
SDK --> API
API --> TENANT[Tenant context, RBAC and scoped tokens]
TENANT --> DB[(MongoDB)]
TENANT --> MEDIA[(Cloudflare R2)]
TENANT --> CACHE[(Redis)]
TENANT --> EVENTS[Webhooks and domain events]
One deployment serves multiple tenants. Requests resolve a tenant context before tenant-owned resources are accessed; content, media, collections, forms, roles, settings, placements and integration tokens remain tenant-scoped.
- Multi‑tenant by design – a single deployment can serve multiple tenants and sites. Each tenant has its own users, roles, content and configuration. The API partitions tenant data by
tenantId. - Headless content platform – editorial content is stored as structured data (Lexical JSON) and served as JSON or HTML, with drafts and scheduled publishing.
- Storage service – integrated with Cloudflare R2 for storing images, documents and other assets. Files can be delivered via signed URLs or public links.
- Placements and experiments – tenant-aware decision rules, weighted experiences, frequency caps, event collection, A/B-test reporting, funnels, realtime statistics and journeys power personalized banners, popups and inline experiences.
- User management and RBAC – users can be members of multiple tenants. System roles (
Owner,Admin,Editor,Author,Viewer) and tenant-scoped custom roles define permissions. - Generic forms – custom forms can be defined without code. Submitted data is stored in the database and can trigger webhooks or notifications.
- Analytics – placement events, A/B-test and funnel reports, realtime breakdowns, journeys and dashboard API-usage summaries are available through tenant-scoped endpoints.
- Presentation integration – APIs and SDKs support content, collections, media, menus, forms and placements in custom frontends.
- Tokens for service integration – owner-managed API tokens combine a role with read, write and delete scopes, optional expiry and revocation.
- Flexible custom data – tenants can define their own collections based on JSON schema for bespoke applications (e.g. election campaigns).
- Open extension contract – trusted deployment plugins can register API routes, event consumers, tenant settings and admin contributions through the versioned Plugin API.
The hosted ContextHub Cloud product, operational guidance and managed capabilities are documented at ctxhub.net/docs. Features that are not backed by a working route or service are intentionally not listed above.
This repository follows a modular monorepo layout using pnpm workspaces. All packages share a single node_modules directory. The top-level package.json exposes common scripts, while each app or package has its own package definition.
contextHub/
├── apps/
│ ├── api/ # Fastify back‑end service
│ └── admin/ # React admin interface (placeholder for now)
├── packages/
│ └── common/ # Shared code (types, utilities, RBAC, etc.)
├── scripts/ # Helpers to execute shared tooling (eslint, prettier, vitest)
├── pnpm-workspace.yaml
├── package.json # root package with workspace configuration
└── README.md # this file
Tooling that every package uses (ESLint, Prettier, Vitest, etc.) now lives only in the root package.json. Workspace scripts call node ../../scripts/run-tool.mjs <binary> [...args], which delegates to the single copy of the CLI in node_modules/.bin. This keeps package manifests lean, ensures the per-package node_modules folders contain just workspace links, and avoids duplicating the same devDependencies across the monorepo. A shared .eslintrc.cjs at the repo root defines the base lint rules so all packages lint consistently.
Prerequisites:
- Node.js 24.13.1 or newer, below Node.js 25.
- pnpm 10.13.1. pnpm is required to manage workspaces.
- MongoDB for persistent application data.
- Redis is recommended for shared rate-limit and usage state.
To bootstrap the repository:
pnpm installThis will install all dependencies and link packages together. To start the back‑end service in development mode:
pnpm dev:apiThe API will start at http://localhost:3000 with a /health endpoint. Environment variables can be set via a .env file at the root of the repository (see .env.example when available).
To deploy the admin panel to a production server:
# Build and deploy one exact tagged release
pnpm deploy -- --release v0.1.6
# Or separately:
pnpm build:admin
pnpm deploy:admin -- --release v0.1.6
pnpm rollback:adminDeploy configuration is managed via environment variables in .env:
adminUser=your_ssh_user
adminPassword=your_ssh_password
adminDeployPath=/path/to/deployment/directory
# Optional; defaults to "${adminDeployPath}.releases"
adminDeployReleaseRoot=/path/to/immutable/admin-releases
adminDeployServer=your.server.comThe deploy script will:
- Refuse branches, floating tags, dirty trees, and tag/package version drift
- Upload once to an immutable
vX.Y.Z-<commit>release directory - Atomically switch
currentwhile preservingprevious - Roll back by atomically swapping
currentandprevious
This public/core script deploys only the community-compatible admin artifact. Hosted
API releases that compose private plugins are owned by the ctxhub-commercial
release manifest, preflight, frozen lockfile, and entitlement verification flow; the
core deploy command must not copy private plugin sources into this repository.
Keep production hostnames, credentials, release procedures, and operational runbooks outside the public repository. The commands above document only the community-compatible deployment interface.
For an API managed by PM2, run pnpm api:pm2:env:check on the server before a
release. It compares each running API process with the local .env and reports only
drifting variable names. After changing .env, use pnpm api:pm2:reload to reload
with the file's values, verify all instances, and save the corrected PM2 state.
With BILLING_CUSTOMER_NOTIFICATIONS_ENABLED=true, verified subscription payments
(initial and renewal) and successful prorated plan changes enqueue a separate
internal.iyzico.payment.customer.notify billing job. Enable the flag only after
all API workers have been updated to recognize this new internal job type.
The recipient is the billing profile's billingEmail, not the tenant creator or
the operations mailbox. Customer confirmations use global SMTP, independently of
HOSTED_OPERATIONS_NOTIFICATIONS_ENABLED. HTML and plain-text versions describe
the actual captured amount; upgrade confirmations distinguish the one-time
difference from the next recurring payment. These messages are not invoices.
The verified order/payment ID deduplicates customer jobs across callback,
webhook, and recovery. Recipient snapshots are encrypted with the existing
BILLING_PII_ENCRYPTION_KEY; only minimal receipt details are retained under
BILLING_EVENT_PAYLOAD_RETENTION_DAYS. SMTP failure retries only the failed
recipient. A recorded SMTP acknowledgement prevents ordinary retries from
resending mail, but SMTP cannot guarantee exactly-once delivery across a crash
between provider acceptance and persisting that acknowledgement.
Already-processed historical payments are not automatically replayed. An
authorized one-off backfill must verify the exact payment/account and call
queueCustomerPaymentNotification with its persisted internal payment event,
then processEvent with the returned customer job ID. Never reset the operations
event or replay a refunded payment to send a customer confirmation.
The deployable core is released as a single version: the root package.json, apps/* and packages/common always carry the same number, and an annotated git tag points at it. @contexthub/promo-sdk is excluded — it is published separately and keeps its own version.
Versions follow MAJOR.MINOR.PATCH. While the core is below 1.0.0, a minor bump may contain breaking changes; from 1.0.0 onward, breaking changes require a major bump.
# keep every core manifest on the same number
pnpm version:set 0.1.1
# verify they have not drifted (also runs in CI)
pnpm version:checkCutting a release:
git checkout main && git merge --no-ff develop
pnpm version:set 0.1.1
git commit -am "chore(release): v0.1.1"
git tag -a v0.1.1 -m "v0.1.1"
git push origin main --follow-tagsTags are immutable: never move or re-point a tag that has been pushed. Deployments and downstream builds pin to a tag and its verified commit SHA, never to a branch.
The domain event contract and supported event types are documented in the Webhook & Domain Event Primer. Runtime manifests, versioning, consumer registration and the tenant-scoped read-only source facade are documented in the Plugin API.
On extensions: the core stays open source, and no capability that is already in this repository will be moved behind a paid tier. Commercial add‑ons are built as separate plugins against the documented extension API, never as patches to the core.
The project uses conventional commits and enforces code style via ESLint and Prettier. Tests should be written using vitest. Pull requests must include unit tests and updates to documentation when relevant.
This project is released under the MIT license.