Configuration
The complete reference for aphex.config.ts — schemas, adapters, auth, GraphQL, versioning, branding, and security.
The aphex.config.ts file is the central configuration for your Aphex project. It's created by createCMSConfig() and wires together your schemas, database, storage, authentication, and email.
import { createCMSConfig } from '@aphexcms/cms-core/server';
import { schemaTypes } from '$lib/schemaTypes/index.js';
import { db } from '$lib/server/db/index.js';
import { authProvider } from '$lib/server/auth/index.js';
import { email } from '$lib/server/email/index.js';
export default createCMSConfig({
schemaTypes,
database: db,
email,
auth: {
provider: authProvider,
loginUrl: '/login'
},
graphql: {
defaultPerspective: 'published',
path: '/api/graphql'
},
customization: {
branding: {
title: 'My CMS'
}
}
});Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
schemaTypes | SchemaType[] | Yes | — | Your content schemas (document and object types). |
database | DatabaseAdapter | Yes | — | Database adapter instance. |
storage | StorageAdapter | null | No | Local filesystem | Storage adapter for file uploads. |
email | EmailAdapter | null | No | null | Email adapter for sending emails. |
cache | CacheAdapter | null | No | null | Cache adapter for published-perspective reads. |
auth | object | No | — | Authentication configuration. |
graphql | boolean | GraphQLConfig | No | true | GraphQL API configuration. |
versioning | object | No | — | Document version history options. |
api | (app: Hono) => void | No | — | Register custom HTTP routes / middleware on the built-in Hono app. |
aiProvider | AIProviderAdapter | null | No | null | Model backend for the in-admin agent. Omit to leave the agent off. |
agentModel | string | No | — | Model id the agent calls. Required when aiProvider is set. |
agentSystemPrompt | string | No | built-in | Replaces the default agent system prompt. |
customization | object | No | — | Branding and theme options. |
logLevel | string | No | auto | 'debug' in dev, 'warn' in prod. |
security | object | No | — | Security options (asset signing). |
schemaTypes
An array of SchemaType objects defining your content model. Each schema is either a document (top-level collection) or an object (reusable nested structure).
import { schemaTypes } from '$lib/schemaTypes/index.js';
createCMSConfig({
schemaTypes
// ...
});Schemas are registered in the database on first startup and re-synced when they change during development. The aphex() Vite plugin (from @aphexcms/cms-core/vite, included in the template's vite.config.ts) watches aphex.config.ts and src/lib/schemaTypes/** and hot-swaps the engine config in place — the dev server keeps running, only the schema map updates. See Schemas for the full schema reference.
database
A DatabaseAdapter instance that handles all content storage. This is the only required adapter. The interface is part of the ports-and-adapters architecture so additional database backends can be added later, but the only adapter shipped today is PostgreSQL via Drizzle ORM (@aphexcms/postgresql-adapter).
import { db } from '$lib/server/db/index.js';
createCMSConfig({
database: db
// ...
});The PostgreSQL adapter (@aphexcms/postgresql-adapter) is the built-in implementation. It uses Drizzle ORM and supports Row-Level Security for multi-tenant isolation.
storage
A StorageAdapter instance for file uploads and asset management. If not provided, Aphex creates a local filesystem adapter automatically:
// Default (no config needed):
// Files stored at ./storage/assets
// Served via /assets/{id}/{filename}
// S3-compatible storage:
import { storage } from '$lib/server/storage/index.js';
createCMSConfig({
storage
// ...
});Available adapters:
- Local filesystem (default) — stores files in
./storage/assets. @aphexcms/storage-s3— S3-compatible storage (AWS S3, Cloudflare R2, MinIO).
An EmailAdapter instance for sending transactional emails (password resets, invitations, email verification). If not provided, email features are disabled.
import { email } from '$lib/server/email/index.js';
createCMSConfig({
email
// ...
});Available adapters:
@aphexcms/nodemailer-adapter— SMTP via Nodemailer. Includes acreateMailpitAdapter()shorthand for local development.@aphexcms/resend-adapter— Resend API for production.
In development, createMailpitAdapter() sends all emails to Mailpit on localhost:1025. The base template wires this up automatically — see Getting Started.
cache
An optional CacheAdapter that caches reads with perspective: 'published'. Entries are invalidated automatically when a document is published, unpublished, or deleted. Draft reads bypass the cache so the admin UI always sees the latest data.
import { InMemoryCacheAdapter } from '@aphexcms/cms-core/server';
createCMSConfig({
cache: new InMemoryCacheAdapter({ maxSize: 5000 })
});The base template exports a shared cacheAdapter from src/lib/server/cache/index.ts and passes the same instance to both the CMS config and Better Auth (for API-key lookup caching).
| Value | Behavior |
|---|---|
undefined or null | No caching (default). |
InMemoryCacheAdapter | LRU in-memory cache, shipped with @aphexcms/cms-core/server. |
Custom CacheAdapter | Implement the CacheAdapter interface for Redis, Upstash, etc. |
auth
Authentication configuration. If not provided, the CMS has no access control.
createCMSConfig({
auth: {
provider: authProvider,
loginUrl: '/login'
}
// ...
});| Option | Type | Required | Default | Description |
|---|---|---|---|---|
auth.provider | AuthProvider | Yes | — | Authentication provider instance. |
auth.loginUrl | string | No | '/login' | Redirect URL for unauthenticated users. |
The AuthProvider interface handles:
- Session auth — browser sessions for the admin UI.
- API key auth — programmatic access via
x-api-keyheader. - User management — fetching users, changing names.
- Password reset — request and confirm password resets.
The built-in integration uses Better Auth with organization support. See the scaffolded src/lib/server/auth/ directory for the full implementation.
graphql
Controls the built-in GraphQL API. Can be true (defaults), false (disabled), or a config object.
// Enabled with defaults
createCMSConfig({ graphql: true });
// Disabled
createCMSConfig({ graphql: false });
// Custom options
createCMSConfig({
graphql: {
defaultPerspective: 'published',
path: '/api/graphql',
enableGraphiQL: true,
defaultQuery: '{ allPost { id title } }'
}
});| Option | Type | Default | Description |
|---|---|---|---|
defaultPerspective | 'draft' | 'published' | 'published' | Default perspective when not specified in a query. |
path | string | '/api/graphql' | GraphQL endpoint path. |
enableGraphiQL | boolean | true | Enable the interactive GraphiQL IDE at the endpoint. |
defaultQuery | string | Built-in example | Default query shown in GraphiQL. |
See GraphQL API for the full reference.
versioning
Controls document version history. Each draft save and publish creates an entry in the cms_document_versions table. See Version History for the full reference.
createCMSConfig({
versioning: {
maxVersions: 25 // default — set 0 to disable rolling cleanup
}
});Prop
Type
api
The escape hatch for registering custom HTTP routes and middleware. Aphex's HTTP API runs on Hono — the function you pass receives the same Hono app the built-in routes mount onto, before they mount.
createCMSConfig({
api: (app) => {
// Add a brand-new endpoint
app.post('/send-email', async (c) => {
const { aphexCMS, auth } = c.var;
// ...
return c.json({ success: true });
});
// Wrap a built-in route with side effects (registration order
// matters: register before built-ins to intercept them)
app.use('/organizations/invitations', async (c, next) => {
await next();
if (c.res.status === 201) sendInviteEmail(/* ... */);
});
}
});Hono is registration-order-strict and first-match-wins. Registering before built-ins lets you wrap
them with app.use() or override them outright with app.METHOD(path, handler).
c.var exposes the same context the built-in routes use:
| Var | Type | Description |
|---|---|---|
c.var.aphexCMS | CMSInstances | Local API, services, adapters. |
c.var.auth | Auth | null | Resolved auth — session or API key. |
customization
Branding and theme options for the admin UI.
createCMSConfig({
customization: {
branding: {
title: 'My CMS',
logo: '/images/logo.png',
favicon: '/favicon.ico'
},
theme: {
colors: { primary: '#3b82f6' },
fonts: { sans: 'Inter, sans-serif' }
}
}
});Branding
| Option | Type | Default | Description |
|---|---|---|---|
title | string | 'Aphex CMS' | Display title in the admin UI. |
logo | string | — | URL to a logo image. |
favicon | string | — | URL to a favicon. |
Theme
| Option | Type | Default | Description |
|---|---|---|---|
colors | Record<string, string> | — | Custom color values (e.g. { primary: '#3b82f6' }). |
fonts | Record<string, string> | — | Custom font families (e.g. { sans: 'Inter, sans-serif' }). |
security
Security options for asset access control.
createCMSConfig({
security: {
assetSigningSecret: 'a-long-random-string-32-chars-minimum'
}
});| Option | Type | Default | Description |
|---|---|---|---|
assetSigningSecret | string | — | Secret key for HMAC-signed asset URLs. Enables temporary, expiring URLs for multi-tenant asset access without exposing API keys. Should be 32+ characters. |
aiProvider
Turns on the in-admin agent — a chat panel that can read and edit content through the same agent tools exposed over MCP. Off unless you configure it.
import { createOpenAIAdapter } from '@aphexcms/ai-openai';
createCMSConfig({
aiProvider: env.OPENAI_API_KEY
? createOpenAIAdapter({ apiKey: env.OPENAI_API_KEY })
: null,
agentModel: 'gpt-4.1'
});| Option | Type | Default | Description |
|---|---|---|---|
aiProvider | AIProviderAdapter | null | null | Model backend. When unset, POST /api/agent/chat returns 404 and the admin doesn't show the panel. |
agentModel | string | — | Provider-specific model id. Required when aiProvider is set — the route 501s without it. A request may override it per call. |
agentSystemPrompt | string | built-in | Injected fresh ahead of every turn, so edits apply to open conversations immediately. Replaces the default, which only carries behavioral guardrails (draft-first, confirm before broad changes) — schema and tool knowledge is self-describing via the describe_cms tool, so there's no need to restate it. Useful for giving a client's deployment its own tone or house rules. |
AIProviderAdapter is a port, same as database and storage. @aphexcms/ai-openai implements it for OpenAI and any OpenAI-compatible endpoint (OpenRouter, a local router); you can write your own for anything else.
Every agent write goes through the same validation, permission checks, and compare-and-swap guard as a human edit — an agent can't write shapes the admin UI couldn't, and can't silently clobber a document someone else is editing. Mutating tool calls are recorded per turn and can be undone from the Activity view.
How config is consumed
The createCMSHook() function in hooks.server.ts takes your config and creates singleton instances of all adapters:
import { createCMSHook } from '@aphexcms/cms-core/server';
import config from '../aphex.config.ts';
const aphexHook = createCMSHook(config);
export const handle = sequence(authHook, aphexHook);On the first request, the hook:
- Creates the storage adapter (or uses the default local filesystem).
- Initializes the
AssetServicewith the storage adapter. - Creates the
CMSEngineand registers schemas in the database. - Creates the
LocalAPI(unified data layer). - Calls your
api(app)hook (if provided), then mounts the built-in Hono routes. - Initializes GraphQL if enabled.
On every subsequent request, the hook injects these singletons into event.locals.aphexCMS, making them available in all route handlers and load functions.
What's available on event.locals.aphexCMS
event.locals.aphexCMS.localAPI; // LocalAPI instance
event.locals.aphexCMS.databaseAdapter; // DatabaseAdapter
event.locals.aphexCMS.assetService; // AssetService
event.locals.aphexCMS.storageAdapter; // StorageAdapter
event.locals.aphexCMS.emailAdapter; // EmailAdapter (or null)
event.locals.aphexCMS.cmsEngine; // CMSEngine
event.locals.aphexCMS.rolesService; // RolesService
event.locals.aphexCMS.auth; // AuthProvider (or undefined)
event.locals.aphexCMS.config; // Your resolved CMSConfig (includes `cache`, `versioning`, etc.)
event.locals.aphexCMS.graphqlSettings; // GraphQL endpoint info (or null)Last updated on