Using Core in Enterprise Environments
A practical guide to running @dyrected/core in larger teams, regulated environments, and multi-tenant deployments.
@dyrected/core is already structured for enterprise use, but enterprise projects usually fail on operating model, auth boundaries, and schema governance rather than on basic create/read/update/delete.
This guide focuses on the parts of core that matter when multiple teams, environments, and tenants are involved.
When to use @dyrected/core directly
Use @dyrected/core directly when you need one or more of these:
- Full control over deployment, networking, and data residency
- Existing database, storage, email, or identity infrastructure
- Tenant-aware schema resolution at request time
- Strong separation between application auth and Admin auth
- Custom hooks, workflows, and lifecycle-event handlers under your own review process
If your organization wants a managed control plane instead, start with Dyrected Cloud. If the requirement is infrastructure ownership and deeper integration into an existing platform, core is the right layer.
Recommended architecture
For enterprise teams, treat dyrected.config.ts as a reviewed application contract, not as local setup glue.
import { defineConfig } from "@dyrected/core";
import { PostgresAdapter } from "@dyrected/db-postgres";
import { S3StorageAdapter } from "@dyrected/storage-s3";
export default defineConfig({
db: new PostgresAdapter({ url: process.env.DATABASE_URL! }),
storage: new S3StorageAdapter({
bucket: process.env.S3_BUCKET!,
region: process.env.S3_REGION!,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
},
}),
collections: [],
globals: [],
cors: {
origins: ["https://admin.example.com", "https://www.example.com"],
},
redis: {
url: process.env.REDIS_URL!,
},
});Recommended boundaries:
- Keep config, hooks, and access rules in source control.
- Keep secrets only in environment variables.
- Treat slug changes and field renames like data migrations.
- Run the Admin UI behind a dedicated domain or route boundary.
Separate Admin auth from application auth
This is the first enterprise requirement to get right.
Collection-level auth: true is for application users. Admin access should use adminAuth, so dashboard access is governed independently from customer/member login.
export default defineConfig({
collections: [Customers, Orders],
globals: [],
adminAuth: {
mode: "external",
collectionSlug: "accounts",
provisioningMode: "jit_plus_membership_management",
providers: [
{
id: "okta",
type: "oidc",
displayName: "Okta",
issuer: process.env.OKTA_ISSUER!,
clientId: process.env.OKTA_CLIENT_ID!,
clientSecret: process.env.OKTA_CLIENT_SECRET!,
autoRedirect: true,
},
],
resolveAccess: ({ identity, siteId }) => {
const roles = identity.roles ?? [];
const allowed = roles.includes("cms-admin") || roles.includes("cms-editor");
return {
allowed,
roles,
data: { siteId },
};
},
},
});Why this matters:
- Compromising a frontend user account does not grant Admin access.
- Enterprise SSO can govern Admin login without reshaping customer auth.
- Provisioning and de-provisioning can follow the identity provider lifecycle.
See Separating Admin Auth for the local-collection pattern and Admin Configuration for Admin UI concerns.
Choose a provisioning model deliberately
adminAuth.provisioningMode is not a cosmetic setting. It changes how identity is admitted into the Admin layer.
| Mode | Use when | Tradeoff |
|---|---|---|
jit_only | Access is decided entirely at login time from external claims | Fastest to adopt, but membership is mostly owned by the identity provider |
jit_plus_membership_management | You want external login plus managed membership records in Dyrected | Better auditability and site-level control |
preprovisioned_only | Access must exist before first login | Strongest control, more operational overhead |
For larger organizations, jit_plus_membership_management is usually the practical middle ground.
Use tenant-aware schema loading for multi-tenant platforms
If different sites or business units carry different collections, globals, or Admin auth rules, use onSchemaFetch.
export default defineConfig({
db,
collections: [],
globals: [],
onSchemaFetch: async (siteId) => {
const site = await siteRegistry.lookup(siteId);
if (!site) return {};
return {
collections: buildCollectionsForSite(site),
globals: buildGlobalsForSite(site),
adminAuth: buildAdminAuthForSite(site),
};
},
});Use this when:
- Each tenant has distinct content models
- Tenant-specific Admin auth providers must be resolved dynamically
- A central platform team serves many business-owned sites
Do not use it to avoid source control. Even when schemas are loaded dynamically, the schema builder code should still be versioned and reviewed.
Lock down content governance
Enterprise teams usually need more than "who can log in."
Use these controls together:
- Collection and field
accessrules for authorization workflowfor draft/review/publish state transitionsaudit: truefor document-level change historyhooksfor validation, enrichment, and downstream side effects
import { defineCollection, publishingWorkflow } from "@dyrected/core";
export const Policies = defineCollection({
slug: "policies",
audit: true,
workflow: publishingWorkflow(),
access: {
read: ({ user }) => !!user,
create: ({ user }) => user?.roles?.includes("policy-author") ?? false,
update: ({ user }) => user?.roles?.some((role) => ["policy-author", "publisher"].includes(role)) ?? false,
delete: ({ user }) => user?.roles?.includes("admin") ?? false,
},
fields: [
{ name: "title", label: "Title", type: "text", required: true },
{ name: "body", label: "Body", type: "richText", required: true },
],
});Recommended discipline:
- Use access rules for authorization, not hidden UI controls.
- Enable audit on regulated or operationally sensitive collections.
- Use workflows where publishing must be reviewed separately from editing.
- Keep
afterChangeandafterDeletehooks idempotent (safe to run more than once) and failure-tolerant.
Relevant docs:
Treat lifecycle events as operational integration points
Enterprise systems usually need webhooks, downstream indexing, search sync, analytics, or ERP/CRM fan-out. That work should not be embedded loosely across request handlers.
Use events.handlers for durable lifecycle delivery:
export default defineConfig({
db,
collections: [Policies],
globals: [],
events: {
handlers: [
async (event) => {
await enterpriseEventBus.publish(event);
},
],
maxAttempts: 8,
retryDelayMs: 1000,
},
});This gives you:
- Retryable delivery state persisted in Dyrected
- A cleaner boundary between content writes and downstream processing
- Better visibility than ad hoc webhook calls in unrelated code paths
If you run multiple instances, configure redis and make event processing an explicit part of your runtime model.
Plan storage, email, and network controls up front
Enterprise adoption is often blocked by non-CMS requirements:
- Object storage must align with the company cloud account
- Email must use the approved provider and sender domains
- CORS must be explicit
- JWT secrets and provider credentials must rotate cleanly
Minimum checklist:
- Configure
storageif any collection uses uploads - Configure
emailfor invites, welcome mail, and password reset flows - Set
cors.originsto known frontend/Admin origins - Set
DYRECTED_JWT_SECRETin every environment - Keep per-environment values outside
dyrected.config.ts
For upload-heavy systems, also review Configuring Storage.
Roll out changes like platform changes
The highest-risk mistakes in enterprise deployments are usually schema drift and auth drift.
Operational rules that help:
- Review config changes like database migrations.
- Promote config through environments in the same order as application code.
- Smoke-test auth,
/me, uploads, and the highest-value list filters before each release. - Avoid direct field renames on live collections; use compatibility steps first.
- Keep generated API and schema docs close to the current build.
Use Production Checklist as the baseline, then add your environment-specific controls on top.
A practical enterprise baseline
If you need a conservative starting point, use this stack:
- PostgreSQL adapter for primary data
- S3-compatible storage adapter for media
- External
adminAuthwith OIDC - Dedicated Admin route or domain
audit: trueon critical collectionsworkflowon published contentevents.handlersfor downstream synconSchemaFetchonly when tenant-specific schemas are real, not hypothetical
That baseline keeps the system understandable while still covering the requirements that usually appear first in enterprise delivery.