Configuration
Companions:
- Configuration API reference — every
BaseConfig,ClientConfig, andServerConfigproperty, including defaults and runtime requirements. - Collections — how the collection tuple divides each content type into an isomorphic schema and an admin presentation config.
- Core composition — how
initBylineCore()validates and composes the server-side adapters registered here. - Client-config registration — why the admin config is registered from both the eager and lazy
_bylineroute graphs.
Byline configuration is application-owned code that tells the CMS what content exists, how editors work with it, and which server implementations provide storage, authentication, search, and rich text. In the reference application, these files live under apps/webapp/byline.
This split is slightly more involved than a single monolithic configuration file, but it gives each runtime a focused, type-checked surface and keeps server-only and admin-only dependencies out of the wrong bundles.
The important rule is that this directory crosses four different runtime boundaries. A value that belongs in one boundary can break builds, leak server code, or make scripts unusable when it is imported from another.
Configuration boundaries
Boundary | What belongs there | What must stay out |
Isomorphic schema | Plain collection and field definitions, serializable adapter settings, isomorphic-safe functions | React, CSS modules, Node-only modules, browser globals |
Server bootstrap | Database, storage, sessions, search, server-only hooks, rich-text server adapters | React components and admin presentation |
Admin module graph | Collection presentation, field widgets, block presentation, editor components | Database adapters, secrets, Node-only modules |
Public client facade | Locale and route data needed by the host frontend | Admin translations, editor code, server configuration |
The same collection schemas are imported by the server bootstrap and the browser admin. They are therefore isomorphic, not merely server-safe: every reachable schema import must work in both environments.
Files at a glance
Files you normally edit
File or directory | Boundary | Purpose |
| Isomorphic schema | Defines one collection's fields, workflow, search projection, paths, hooks, and structural options. |
| Admin module graph | Defines columns, layouts, preview URLs, custom list views, and per-field UI overrides for that collection. |
| Isomorphic schema | Exports the single canonical collection tuple consumed by code generation, the server, and the admin. |
| Server bootstrap | Registers collection and upload hooks whose modules import server-only code. |
| Isomorphic schema | Defines reusable block schemas. |
| Admin module graph | Defines block-scoped field presentation. |
| Server bootstrap | Calls |
| Admin module graph | Calls |
| Public client safe | Declares the host's admin-interface and content locale sets as dependency-free data. |
| Shared configuration | Assembles locale sets and admin translation bundles for the server and admin configs. Do not import it into public browser code. |
| Public client safe | Resolves the admin, API, and sign-in mount paths once for both configs and the host frontend. |
| Public client safe | Re-exports the small set of Byline route and locale values that public host code may import. |
Tooling and contract files
File or directory | Purpose |
| Loads |
| Initialize the server config and run application-owned seed routines. They are operational entry points, not configuration APIs. |
| Contains the reusable seed implementations invoked by the entry points. |
| Contains code generation, documentation import, and maintenance commands. |
| Proves at compile time that generated collection types exactly match the canonical collection tuple. |
| Verifies that generated declaration merging types the unparameterized client and server client getters. |
| Supplies the browser no-op used by the Vite alias for |
| Generated collection type projection. Run |
How configuration loads
Server startup
apps/webapp/src/server.ts imports byline/server.config.ts as a side effect. The module constructs the selected adapters, calls initBylineCore(), and waits for configuration validation and collection reconciliation before the server handles requests.
src/server.ts -> byline/server.config.ts -> collections/index.ts -> collections/server-hooks.ts -> i18n.ts + routes.ts -> database / storage / auth / search adapters -> initBylineCore()Node scripts use the same bootstrap after loading environment variables:
import './load-env.js'import './server.config.js'
import { getSystemBylineClient } from '@byline/client/server'
const client = getSystemBylineClient()initBylineCore() is asynchronous. The scaffolded server.config.ts uses top-level await, so any module that imports it continues only after the core has initialized.
Admin registration
admin.config.ts is not imported by the public application root. The _byline route loads it from two complementary locations:
route.tsxdynamically imports it frombeforeLoad, which protects child loaders.route.lazy.tsximports it for initial hydration and component rendering.
Both paths call defineClientConfig() against their own Vite module graph. Keeping those imports under _byline/* prevents admin and editor code from entering public bundles.
Public application code
Public browser code imports byline/public.ts, not admin.config.ts, server.config.ts, or i18n.ts:
import { contentLocales, routes } from '../../byline/public.js'
const docsUrl = `${routes.admin}/collections/docs`const supportedContentLocales = contentLocales.map((locale) => locale.code)The facade is intentionally small. Add a public export only when the public host genuinely needs the value and its complete import graph is safe for the browser.
Configure the server
The reference server config composes one installation-wide runtime:
const core = await initBylineCore<AdminStore>({ serverURL, i18n, routes, collections, hooks: serverHooks, db, adminStore, storage: localStorageProvider({ uploadDir: './uploads', baseUrl: '/uploads' }), sessionProvider, fields: { richText: { embed: lexicalEditorEmbedServer({ getClient: getAdminBylineClient }), populate: lexicalEditorPopulateServer({ getClient: getAdminBylineClient }), toMarkdown: lexicalEditorToMarkdownServer(), toText: lexicalEditorToTextServer(), }, }, search: postgresSearch({ pool: db.pool, defaultLocale: i18n.content.defaultLocale }),})initBylineCore() validates the combined configuration before it registers the replacement singleton. For example, search-enabled collections require a search provider, relation-bearing rich-text modes require their matching server adapters, and every configured admin locale requires translations.
The complete property contract is in the Configuration API reference.
Configure the admin
The client config registers presentation and browser-side adapters over the same schema tuple:
export const config: ClientConfig = { serverURL, i18n, routes, collections, admin: [DocsAdmin, NewsAdmin, PagesAdmin, MediaAdmin, NewsCategoriesAdmin], blockAdmin: [QuoteBlockAdmin, PhotoBlockAdmin, FAQBlockAdmin], fields: { richText: { editor: LexicalRichTextAi }, },}
defineClientConfig(config)The server and client configs share serverURL, i18n, routes, and collections. They do not extend each other: server-only adapters belong only on ServerConfig, while React-bearing presentation belongs only on ClientConfig.
The complete property contract is in the Configuration API reference.
Add a collection
Create the schema first and keep it isomorphic:
// apps/webapp/byline/collections/articles/schema.tsimport { defineCollection } from '@byline/core'
export const Articles = defineCollection({ path: 'articles', labels: { singular: 'Article', plural: 'Articles' }, useAsTitle: 'title', useAsPath: 'title', fields: [ { name: 'title', type: 'text', localized: true }, { name: 'content', type: 'richText', localized: true }, ],})Then add the admin presentation:
// apps/webapp/byline/collections/articles/admin.tsximport { defineAdmin } from '@byline/core'
import { Articles } from './schema.js'
export const ArticlesAdmin = defineAdmin(Articles, { columns: [ { fieldName: 'title', label: 'Title', sortable: true }, { fieldName: 'status', label: 'Status' }, ], layout: { main: ['title', 'content'] },})Finally, add Articles to collections/index.ts and ArticlesAdmin to the admin array in admin.config.ts. Run pnpm byline:generate after changing the schema, and commit the generated type projection.
The Collections API reference lists every collection and admin property. The Fields API reference lists every built-in field type.
Common boundary mistakes
Importing admin config from a public route
This pulls collection presentation, React components, and editor extensions into the public graph. Import byline/public.ts for route and locale data instead.
Importing server-only code from a schema
The schema also enters the browser admin bundle. Register hooks that depend on Node, secrets, server clients, or backend SDKs through collections/server-hooks.ts and ServerConfig.hooks.
Importing i18n.ts from public browser code
i18n.ts assembles admin translation bundles. Public code should import the dependency-free locale arrays through public.ts.
Changing only routes.ts
The route config changes the canonical URLs consumed by Byline; it does not rename the host application's physical TanStack route files. Use the CLI setup flow when moving an existing admin or sign-in mount so both sides stay synchronized.
Next
Use the Configuration API reference when you need an exact property or function signature. Continue to Collections when you are ready to model a content type, or to the Client SDK when you are ready to query it.