This directory contains proprietary feature implementations for Contentrain Studio. It is licensed separately from the AGPL-3.0 core — see LICENSE.
For what Studio is and how it fits the Contentrain ecosystem, see the root README, studio.contentrain.io, and docs.contentrain.io.
Edition is orthogonal to plan tier. Whether the enterprise features are accessible at runtime depends on two independent checks: is
ee/loaded (edition), and does the workspace's plan tier grant the feature (plan gate). Community Edition runs the same core withoutee/and force-disables everyrequires_ee: truefeature regardless of plan matrix values.
ee/
LICENSE — proprietary, not AGPL (see Managed Use vs On-Premises grants)
README.md — this file
cdn/ — Cloudflare R2 CDN provider + usage metering
media/ — Sharp image processor + variant generator + blurhash
enterprise/ — EnterpriseBridge surface: webhooks, conversation API,
BYOA key management, project-role normalization
server/utils/enterprise.ts does a dynamic import('../../ee/enterprise') at boot (wrapped by server/plugins/01.init-ee.ts). If the import fails — for example in a Community Edition build where ee/ has been excluded — the bridge resolves to null and the core degrades gracefully:
runEnterpriseRoute(handler, messageKey, event, featureKey?)returns 403 when the bridge isnull. WhenfeatureKeyis supplied the plan gate runs first, so Starter customers on Managed hit 403 before the bridge is consulted.normalizeEnterpriseProjectMemberAccess()returns{ role: 'editor', specificModels: false, allowedModels: [] }.resolveEnterpriseChatApiKey()returnsnull(no BYOA path).useCDNProvider()/useMediaProvider()returnnull.
This means: every code path the AGPL core takes must tolerate a missing bridge. If you add a new ee/ handler, the core call site must have a safe null or 403 fallback — it never short-circuits the product.
The authoritative plan × feature matrix lives in .contentrain/content/system/plan-features/. Each row carries requires_ee: true|false; the client-side useFeature() and server-side hasFeature() both apply the gate:
feature accessible ⟺ matrix.plans.includes(plan) AND (!matrix.requires_ee OR edition === 'ee')
See docs/EDITIONS.md for the full matrix and the orphan-feature cleanup rationale.
- Implement the functionality inside
ee/<domain>/. External PRs toee/are not accepted (seeee/LICENSE§5.3); contributions to the AGPL core go through the normal CLA/DCO path. - If it's exposed over HTTP, wire a core route in
server/api/...that delegates torunEnterpriseRoute(handlerName, messageKey, event, 'feature.key'). The fourth argument is the plan gate — skip it only when the feature is edition-gated but plan-agnostic (rare). - Add a row to
.contentrain/content/system/plan-features/data.jsonwithrequires_ee: "true"and the appropriate tier values. If the feature is advertised but unimplemented, also setroadmap: "true"so the UI renders a "Coming Soon" chip. - Run
npx contentrain generateto refresh the SDK client types. - Extend the test matrix in
tests/unit/license-content-parity.test.tsto pin the new row.
ee/LICENSE defines five grant types:
- Managed Use — contentrain.io subscribers; grants use of
ee/via the Managed Service only. - On-Premises Deployment — separately executed order form; grants install + operate on customer infrastructure.
- Evaluation — 60 days, 1 instance, 5 users, non-production.
- OEM / Embedded — separate agreement required.
- White-Label / Reseller — separate agreement required.
See docs/LICENSING.md for the full SKU × license type × scenario table and ee/LICENSE for the controlling text.
- Licensing:
info@contentrain.io - Legal:
info@contentrain.io - Commercial / on-prem support:
info@contentrain.io