Skip to content

The config file

An instance is configured by one TypeScript object, built with defineConfig() from @manablox/core. A project keeps it in manablox.config.ts at its root, which is what the manablox CLI loads; see Create a project. manablox create writes three files around it: manablox.config.ts for the management instance, manablox.public.config.ts for the public delivery instance, and content-model.ts and manablox.plugins.ts, which both configs import for the content types, field types and plugins they share. Another file is loaded with --config <file>.

Every value that differs between environments (the database, secrets, hostnames, the storage driver) is read from the environment inside that file, so a deployment needs no code change. Every value that is part of the product (content types defined in code, custom field types, plugins, media presets) is written into it.

The shape

import { defineConfig } from '@manablox/core';
import { manabloxFields } from '@manablox/fields';
export default defineConfig({
database: { url: process.env.DATABASE_URL! },
auth: { secret: process.env.AUTH_SECRET! },
server: {
port: 3000,
publicUrl: 'https://cms.example.com',
cors: { origin: ['https://admin.example.com', 'https://www.example.com'], credentials: true },
// Which surfaces this process mounts, and whether it is the management or the
// public half. See "One image, two modes" below.
scopes: ['rpc', 'auth', 'uploads', 'media', 'graphql'],
mode: 'management',
},
storage: { driver: 's3', s3: { bucket: 'assets', /* ... */ } },
media: { presets: { hero: { width: 1920, format: 'webp' } } },
cache: { redisUrl: process.env.REDIS_URL, ttl: 60 },
plugins: [manabloxFields()],
contentTypes: [
{
name: 'page',
fields: [
{ name: 'summary', type: 'string', settings: { editor: 'textarea' } },
{ name: 'components', type: 'blocks', settings: { types: [] } },
],
},
],
});
SectionWhat it holdsDetail
databaseConnection URL, pool size, SSLEnvironment variables
authThe secret that signs sessions and media URLs, trusted origins, session lengthEnvironment variables
serverHost, port, public URL, admin URL, CORS, rate limit, scopes, modebelow
publicApiSettings that apply only in public mode: the pinned space, limits, persisted operationsThe public API
graphqlPath, depth and complexity limits, introspection, the preview headerGraphQL
storage, mediaWhere files go, upload limits, image presetsStorage and media
cacheValkey/Redis URL, TTL, how often to check for content types saved elsewhereCaching
mailHow mail leaves: SMTP, Mailpit, Gmail, Microsoft 365 or a mail API, for workflows and notificationsMail
pushWeb Push keys, for workflows and notificationsWorkflows
netWhat outbound calls to configured URLs (workflow nodes, webhooks, AI providers) may reach, and their ceilings on response size and redirectsSecurity
fieldTypes, contentTypes, pluginsThe model and the extensionsContent types in code, Extending
loggingLevel, destinations (console, file, HTTP, or an adapter a plugin registers), redactionLogging

manabloxFields() is the plugin that provides the built-in field types. Leave it in. The first-party plugins, such as workflowsPlugin() with a workflow’s ceilings on run time and crawled pages, webhooksPlugin(), aiPlugin() and websitePlugin(), take their settings as options; see Workflows, AI and Designed sites.

Code-defined and runtime-defined content types

A content type declared in contentTypes and one built by an editor in the admin are the same shape, in the same registry, stored the same way and served by the same GraphQL schema. The only difference is where it came from:

  • code: from the config file. Read-only in the admin, versioned with your repository, deployed with your code. Available in every space unless it names a spaceId.
  • runtime: created in the admin, stored in the database, scoped to one space.

Use code-defined types for structures your frontend depends on, and runtime types for whatever editors need to invent without a deploy. The admin can also turn a space’s runtime types into the code that would declare them. See Moving a space.

One image, two modes

The same server code runs as the management API (what the admin uses) and as the public API (what a website reads). Which one a process is comes from two settings:

  • server.scopes names the surfaces to mount: rpc (the management procedures), auth (login), uploads, media (serving files), graphql, delivery (the public REST surface), control (the control API, management mode only).
  • server.mode is management (the default) or public. Public mode is more than a list of scopes: it removes the draft code path, pins one space, masks errors and keys the rate limiter on the client’s IP.
  • server.rateLimit is { window, max } per client and window, or false to turn it off. The counters live in the process unless cache.redisUrl is set, in which case every replica counts into the same Redis keys and shares one budget. It is the default of the rate limit rules the control API can set per space; see Rate limits.
  • server.trustedProxies lists the proxies (addresses or CIDR ranges) whose client-IP headers are believed; [] believes none. Defaults to TRUSTED_PROXIES; unset believes every peer. See Client addresses.

Presets: management mounts rpc, auth, uploads, media, graphql; public mounts graphql, media, delivery. No preset mounts control: name it in server.scopes and set control.apiKey (CONTROL_API_KEY), or the process refuses to start. The cms-api image reads both from the environment variables SCOPES and SERVER_MODE (with envScopes() and envServerMode() of @manablox/core, which a config of your own can call too). See The public API and Operations.

Validation

The config is validated once, at boot, and every problem is reported at once with its path: an unknown scope, a port out of range, a missing database URL or auth secret. The error keys start with config.. See Error keys.