nextjs-cms

Project Structure

A tour of the files create-nextjs-cms generates, and which ones you are expected to edit.

A new project is a regular Next.js 16 App Router application. The admin UI lives in app/ and components/, and the CMS engine comes from the nextjs-cms package in node_modules.

Top-level files and folders

my-cms/
├── sections/               ← your content types (*.section.ts)
├── cms.config.ts           ← CMS configuration
├── .env                    ← auth secrets, API keys
├── .env.development        ← database credentials (dev)
├── dynamic-schemas/        ← generated Drizzle schema for your sections
├── .uploads/               ← uploaded files (media.upload.path)
├── public/                 ← static assets, logo
├── app/                    ← the admin application (App Router)
│   ├── (auth)/             ← login page
│   ├── (rootLayout)/       ← authenticated admin pages
│   │   ├── (plugins)/      ← catch-all route that renders plugin pages
│   │   ├── browse/  new/  edit/  section/  categorized/
│   │   ├── admins/  settings/  log/  dashboard/
│   ├── _trpc/              ← tRPC client and server callers
│   └── api/                ← auth, submit, file, and tRPC route handlers
├── components/             ← admin UI components (forms, inputs, layout)
├── lib/                    ← helpers
├── env/env.ts              ← typed environment variables (@t3-oss/env-nextjs)
├── proxy.ts                ← CSRF protection for mutating requests
├── drizzle.config.ts       ← used by the CLI to create system tables
└── next.config.ts

Files you edit

PathPurpose
sections/*.section.tsOne file per section. This is where you spend most of your time. See Sections.
cms.config.tsBranding, media and image defaults, localization, plugins. See Configuration.
.env*Secrets and connection strings. See Environment variables.
public/Your logo (referenced by ui.logo) and any static files.

Files you usually leave alone

PathPurpose
app/, components/The admin application. You own this code and can customize it, but most projects never need to. Upgrading nextjs-cms mostly means bumping the nextjs-cms package.
dynamic-schemas/schema.tsRegenerated by update-sections when schemaGeneration.drizzle.enabled is on. Don't edit it by hand. See Querying Content.
.uploads/Where uploaded photos, videos and documents are written. Serve it from your website or a CDN. See Media & Uploads.
proxy.tsRejects POST, PUT, PATCH, and DELETE requests without a valid CSRF token.

The sections folder

The CMS loads every file matching *.section.ts inside the folder set by sections.path in cms.config.ts. Subfolders are fine:

sections/
├── blog.section.ts
├── settings.section.ts
├── shop/
│   ├── products.section.ts
│   └── categories.section.ts
└── shared/
    └── seo-fields.ts          ← not a section: no .section.ts suffix

Rules for section files:

  • The file name must end in .section.ts. Other files in the folder are ignored, so you can keep shared helpers next to your sections.
  • Each file has exactly one export default: the object returned by simpleSection(), hasItemsSection() or categorySection().
  • Section names must be unique across the project (case-insensitive).
sections/shared/seo-fields.ts
import { textAreaField, textField } from 'nextjs-cms/core/fields'

// Plain helpers: call them from any section file to reuse field definitions.
export const seoFields = () => [
    textField({ name: 'meta_title', label: 'Meta title', maxLength: 70 }),
    textAreaField({ name: 'meta_description', label: 'Meta description', maxLength: 160 }),
]

Section files can import other section files. That's how you point a selectField at another section (see Relationships). Always import them without a file extension:

sections/products.section.ts
import categoriesSection from './categories.section' // ✅
// import categoriesSection from './categories.section.ts'  ❌ fails to load
// import categoriesSection from './categories.section.js'  ❌ fails to load
Good to know:

Section files are loaded at runtime with jiti, not by Next.js' bundler. In development, the CMS watches **/*.section.ts and reloads sections when you save, so you don't have to restart next dev.