| name | dotcms-best-practices |
| description | Reference for doing any single thing in dotCMS correctly — create a content type, page, template or container; place content; author VTL; wire a headless Next.js frontend; or debug a page that renders blank. Indexed by intent, so you load one file for the task at hand. Use whenever working with dotCMS content types, fields, pages, templates, containers, content placement, VTL/Velocity, the Universal Visual Editor, or a headless dotCMS frontend — and especially when a page renders empty, a slot shows "no component", or a change doesn't appear in LIVE. |
dotCMS Best Practices
Load the file for the task at hand — never the folder. Building end to end? The ordered sequence
is reference/README.md. Planning a whole site? Start with
dotcms-create-sites.
Read this first
A missing piece renders blank or shell-only with HTTP 200, never an error. A successful
response is not evidence that anything rendered — which is why every build path ends in a verify
step.
Establish the delivery mode before any work that touches rendering — themes, containers,
templates, components, or diagnosing a blank page:
| Mode | dotCMS renders? | You need |
|---|
| VTL-rendered | yes — theme + container VTL | core/ + vtl/ |
| Headless | no — it serves the page, an app renders it | core/ + nextjs/ |
If the task touches rendering and the user hasn't said, ask. Content-model work — types,
fields, content — is identical in both modes; don't ask.
Building a page for the first time? Start with the wiring contract —
reference/core/00-what-must-exist.md.
I want to…
Content model
Pages and rendering scaffold
VTL-rendered delivery
Headless delivery (Next.js)
Next.js is the only framework branch here. Angular, Vue and Astro have upstream examples in
dotCMS/core but no branch in this skill. The SDK APIs belong to the @dotcms/client,
@dotcms/react and @dotcms/uve READMEs — these files cover the seam, never the API surface.
Something is wrong
First, split the symptom. Ask the user to view source: is there any markup, or is the body
genuinely empty? The two have almost disjoint causes.
Then establish the delivery mode — most rows below apply to one mode only.
Shell renders, content slots are missing
VTL: vtl/00-wiring.md has a missing-thing → what-you-see table.
Nothing renders at all
"I added a content type and it doesn't show up"
Three causes look identical in a browser. Check all three:
- The
Var isn't what you think — read it back from dotCMS rather than assuming it matches
the type name (core/02).
- The renderer isn't registered under that variable — a
<Var>.vtl file in VTL
(vtl/03), a component-map key in headless
(nextjs/01). Case-exact.
- The container has no registration stub for the type, so UVE never offered it and nothing was
ever placed — core/06.
A fallback component makes cause 2 invisible — no "no component" text reaches the screen, so
don't rule it out on that basis.
Also worth checking, either mode
| Check | Why |
|---|
| Language / locale | Content saved under one language and requested under another can render blank. This skill doesn't document dotCMS's resolution behavior — fetch it and verify |
| Page cache | A stale cached page serves old output; page verification reports this as a cache verdict — vtl/05 |
| Verify properly | VTL: vtl/05 — note /api/vtl/dynamic runs outside a request context and cannot see $CONTENTLETS, $dotContentMap, $URLMapContent or $dotTheme, so it validates only the parts that don't matter for a container. Headless: nextjs/05 — page verification does not apply |
Rules that bite
Var matching is case-exact. A container's <Var>.vtl filename and a component-map key
must both equal the type's Var exactly — read it back, never assume.
- Containers are folders. There is no create endpoint — you build the folder and its files.
- Placement replaces. Omitted slots are cleared, not left alone.
- Publishing is explicit and ordered. LIVE changes only on publish, and the page publishes
last — after content, placement, or a template edit.
And one that isn't a build failure but matters: a headless project's
a headless project's NEXT_PUBLIC_DOTCMS_AUTH_TOKEN ships to the browser. It should belong to a
restricted user, and often doesn't — never assume an existing project's is safe for production. See
nextjs/00-connect.md §B.1.
Working through the dotCMS MCP server
The server exposes dotCMS as a code API you write against, not a fixed menu of operations.
The normal loop is: search the spec to discover the endpoint, then write code that calls it.
That is the general path for anything, not a fallback.
Alongside it, some operations have purpose-built tools that absorb known traps — page
creation, content placement, page verification, asset transfer. Prefer those where they exist,
not because writing code is worse, but because those tools already handle the failure modes these
files would otherwise have to teach you.
Enumerate the MCP server's tools and read their descriptions — each description is the source of
truth for how to call it. These files name no tools, so they don't go stale when one is renamed
or added. What spec search returns is authoritative for anything the curated OpenAPI spec
expresses; these files keep only what it can't. Run raw VTL through POST /api/vtl/dynamic.
Index by build step
If you're working through a full build rather than a single task, the same files are indexed in
dependency order in reference/README.md.