Aphex

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.

aphex.config.ts
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

OptionTypeRequiredDefaultDescription
schemaTypesSchemaType[]YesYour content schemas (document and object types).
databaseDatabaseAdapterYesDatabase adapter instance.
storageStorageAdapter | nullNoLocal filesystemStorage adapter for file uploads.
emailEmailAdapter | nullNonullEmail adapter for sending emails.
cacheCacheAdapter | nullNonullCache adapter for published-perspective reads.
authobjectNoAuthentication configuration.
graphqlboolean | GraphQLConfigNotrueGraphQL API configuration.
versioningobjectNoDocument version history options.
api(app: Hono) => voidNoRegister custom HTTP routes / middleware on the built-in Hono app.
aiProviderAIProviderAdapter | nullNonullModel backend for the in-admin agent. Omit to leave the agent off.
agentModelstringNoModel id the agent calls. Required when aiProvider is set.
agentSystemPromptstringNobuilt-inReplaces the default agent system prompt.
customizationobjectNoBranding and theme options.
logLevelstringNoauto'debug' in dev, 'warn' in prod.
securityobjectNoSecurity 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).

email

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 a createMailpitAdapter() shorthand for local development.
  • @aphexcms/resend-adapterResend 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).

ValueBehavior
undefined or nullNo caching (default).
InMemoryCacheAdapterLRU in-memory cache, shipped with @aphexcms/cms-core/server.
Custom CacheAdapterImplement 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'
	}
	// ...
});
OptionTypeRequiredDefaultDescription
auth.providerAuthProviderYesAuthentication provider instance.
auth.loginUrlstringNo'/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-key header.
  • 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 } }'
	}
});
OptionTypeDefaultDescription
defaultPerspective'draft' | 'published''published'Default perspective when not specified in a query.
pathstring'/api/graphql'GraphQL endpoint path.
enableGraphiQLbooleantrueEnable the interactive GraphiQL IDE at the endpoint.
defaultQuerystringBuilt-in exampleDefault 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.

aphex.config.ts
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:

VarTypeDescription
c.var.aphexCMSCMSInstancesLocal API, services, adapters.
c.var.authAuth | nullResolved 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

OptionTypeDefaultDescription
titlestring'Aphex CMS'Display title in the admin UI.
logostringURL to a logo image.
faviconstringURL to a favicon.

Theme

OptionTypeDefaultDescription
colorsRecord<string, string>Custom color values (e.g. { primary: '#3b82f6' }).
fontsRecord<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'
	}
});
OptionTypeDefaultDescription
assetSigningSecretstringSecret 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.

aphex.config.ts
import { createOpenAIAdapter } from '@aphexcms/ai-openai';

createCMSConfig({
	aiProvider: env.OPENAI_API_KEY
		? createOpenAIAdapter({ apiKey: env.OPENAI_API_KEY })
		: null,
	agentModel: 'gpt-4.1'
});
OptionTypeDefaultDescription
aiProviderAIProviderAdapter | nullnullModel backend. When unset, POST /api/agent/chat returns 404 and the admin doesn't show the panel.
agentModelstringProvider-specific model id. Required when aiProvider is set — the route 501s without it. A request may override it per call.
agentSystemPromptstringbuilt-inInjected 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:

src/hooks.server.ts
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:

  1. Creates the storage adapter (or uses the default local filesystem).
  2. Initializes the AssetService with the storage adapter.
  3. Creates the CMSEngine and registers schemas in the database.
  4. Creates the LocalAPI (unified data layer).
  5. Calls your api(app) hook (if provided), then mounts the built-in Hono routes.
  6. 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)
Edit on GitHub

Last updated on