← Back to list

Building a Reusable dotCMS Site Blueprint: Lessons from a Real AI Catalog MVP

When building sites in dotCMS, the documentation can help you understand the map, but the code, the logs, and the Page Editor are usually…

Jsanca · 2026-06-09 21:02 · 0 claps · 8.2 min read
#dotcms #containers #template #themes #ai-tools
Open on Medium ↗
Wiki topics: AI · AI · General ☁️ · DevOps & Cloud 📋 · Product Management

Building a Reusable dotCMS Site Blueprint: Lessons from a Real AI Catalog MVP

dotCMS MVP

dotCMS MVP

When building sites in dotCMS, the documentation can help you understand the map, but the code, the logs, and the Page Editor are usually the real source of truth.

This blueprint documents the practical lessons learned while building a reusable catalog-driven site in dotCMS. The goal was not only to create a nice landing page, but to understand how to build a repeatable foundation for future business sites: content types, assets, templates, containers, starters, URL-mapped detail pages, and eventually an AI-powered product assistant.

The result is a small but real MVP:

  • A reusable dotCMS starter.
  • A product catalog loaded as structured content.
  • A landing page with editable promotional banners.
  • A product listing.
  • A basic product detail page using URL-mapped content.
  • A path toward an AI chat assistant connected to the catalog.

This is not a full ecommerce platform yet. There is no checkout, no cart, no advanced inventory, and no variant selection. That is intentional. The first goal was to prove a reusable content architecture.

1. The Business Idea

The original business idea was simple:

Take an existing business catalog, extract and normalize its product data, import it into dotCMS, and generate a reusable website foundation that can be customized for different clients with limited effort.

The commercial version of this idea is:

A smart catalog website with editable content, product detail pages, promotional banners, and an AI assistant that helps customers find products.

For many small and medium businesses, this is more immediately valuable than building a complete ecommerce system from scratch.

A realistic first version includes:

  • Landing page.
  • Product listing.
  • Product detail pages.
  • Contact or WhatsApp CTA.
  • Optional AI chat connected to the catalog.

A later version may include:

  • Shopify or Stripe for checkout.
  • WhatsApp Business integration.
  • CRM integration.
  • Inventory synchronization.
  • Analytics.
  • Personalized recommendations.

The important architectural rule is:

Dream big, build small, sell early, and expand only when someone pays for the next layer.

2. The Architecture in Layers

The site was built using a layered pipeline inspired by the medallion architecture pattern.

Conceptually, the flow looks like this:

existing catalog/site > extraction > Bronze > Silver > CT, contentlets, assets > PL & PDP > AI tools

existing catalog/site > extraction > Bronze > Silver > CT, contentlets, assets > PL & PDP > AI tools

The important idea is that the dotCMS site is not created manually from scratch every time. Instead, the content model, assets, contentlets, templates, and pages can be generated or pushed as part of a repeatable process.

This changes the project from “we built one website” into “we are learning how to manufacture websites.”

3. dotCMS Starters: The First Big Lesson

One of the first major lessons was around custom starters.

dotCMS can load a starter ZIP when the database is empty. The relevant internal property is:

STARTER_DATA_LOAD

However, when running dotCMS through Docker environment variables, the property must be passed with the dotCMS environment variable convention:

DOT_STARTER_DATA_LOAD

So this is wrong:

STARTER_DATA_LOAD: “/data/shared/assets/site_starter.zip”

And this is correct:

DOT_STARTER_DATA_LOAD: “/data/shared/assets/site_starter.zip”

The logs are the best way to verify this.

When the starter is not being picked up, dotCMS falls back to the default starter.

When the custom starter is being picked up correctly, the logs clearly show that the configured starter path is being used.

Another key lesson:

The starter only runs when the database is empty.

If there are already inodes in the database, dotCMS will not reinitialize from the starter.

For local testing, the clean reset flow is:

docker compose down -v
docker compose up -d
docker compose logs -f dotcms

This is destructive because it removes local volumes, but it is the correct way to test a fresh starter boot.

4. File-Based dotCMS Sites

The project used dotCMS file-based assets for templates, containers, themes, and pages.

A typical structure looks like this:

files/live/en-us/{site}/application/
containers/
templates/
themes/

The important folders are:

application/containers/
application/templates/
application/themes/

A theme may include:

themes/{theme}/template.vtl
themes/{theme}/css/styles.css
themes/{theme}/js/main.js

A template may include:

templates/{template}/properties.vtl
templates/{template}/layout.json
templates/{template}/body.vtl

A container may include different files depending on the type of container.

This is where one of the most important lessons appeared.

5. The container_code.vtl Trap

At first, it is tempting to think that every file-based container needs a container_code.vtl.

That is not true.

The practical rule learned from the Page Editor was:

Static container:

max_contentlets = 0
uses container_code.vtl

Static container structure

Static container structure

Dynamic content container:

max_contentlets > 0

does not use container_code.vtl

uses container.vtl

uses {contentTypeVariable}.vtl

Dynamic container structure

Dynamic container structure

This matters a lot.

If a container is meant to accept content through the dotCMS content palette, then container_code.vtl can interfere with the Page Editor experience.

For a dynamic content-driven container, dotCMS expects a renderer file named after the content type variable.

For example, for this content type:

Hero Banner

variable: heroBanner

The renderer should be:

heroBanner.vtl

For a product content type:

Leather Product

variable: leatherProduct

The renderer should be:

leatherProduct.vtl

This became one of the core rules of the blueprint:

In dotCMS, rendering something visually is not the same as making it editable through the CMS.

A frontend implementation can look correct and still be wrong if the editor cannot manage the content through dotCMS.

6. Editable Hero Slider

The landing page needed a hero banner area, but not a static one.

The requirement was:

Editors should be able to add promotional banners through the content palette.

That means the hero section had to be a dynamic container that accepts multiple Hero Banner contentlets.

The content type contained fields like:

title
image
buttonText
buttonLink
contentHost

The correct structure was:

containers/syj-hero-slider/
container.vtl
heroBanner.vtl

The container allows up to a limited number of contentlets, for example five banners.

Each selected Hero Banner contentlet renders as one slide through heroBanner.vtl.

The slider behavior itself is frontend logic:

  • CSS hides inactive slides.
  • JavaScript activates the first slide.
  • JavaScript rotates between slides.
  • Dots or navigation can be added with vanilla JS.

The big lesson:

The container should be content-driven. The slider behavior should be frontend behavior. Do not hardcode promotional banners into the template.

7. Theme and Template Rendering

Another important discovery was related to how dotCMS applies themes.

A template that is not using the correct layout structure may render as plain HTML without the expected theme.

The practical lesson was:

If the template is drawn and has a layout.json, dotCMS applies the theme rendering path.

The theme template then becomes responsible for rendering the layout.

A typical theme uses paths like:

#dotParse("${dotTheme.path}html_head.vtl")

One small but important detail is avoiding accidental double slashes in paths.

This is better:

#dotParse("${dotTheme.path}footer.vtl")

Instead of:

#dotParse("${dotTheme.path}/footer.vtl")

because the theme path may already include the trailing slash.

8. Product Listing

Once the content type and products existed, the landing page could render a basic product listing.

The product cards were intentionally simple:

  • Product image.
  • Title.
  • Brand or category.
  • Price.
  • Link to the product detail page.

The important thing was not to build a full ecommerce catalog yet.

The product listing had one job:

Prove that real imported content could be rendered as a usable catalog page.

This is enough for an MVP.

9. URL-Mapped Product Detail Page

The next step was the product detail page.

The right dotCMS pattern for this is URL-mapped content.

The target URL pattern was:

/producto/{slug}

The content type configuration included:

"urlMapPattern": "/producto/{slug}",
"detailPage": "/producto-detail"

The detail page uses a template and a container, but the renderer should follow the same content-type-variable convention.

For the Leather Product content type, the correct renderer is:

leatherProduct.vtl

So the PDP container structure should be:

containers/syj-pdp/
container.vtl
leatherProduct.vtl

Not:

containers/syj-pdp/
container_code.vtl

The reason is that URL-mapped content is still content rendering. dotCMS resolves the contentlet from the URL map and then renders it using the content type renderer.

The product detail page can use:

$URLMapContent

as the resolved contentlet.

A basic PDP can render:

  • Main image.
  • Title.
  • Brand.
  • Family and subfamily.
  • Price.
  • Short description.
  • CTA to contact.
  • Back link to the listing.

The MVP PDP does not need:

  • Variants.
  • Cart.
  • Checkout.
  • Related products.
  • Inventory selection.

Those belong to a later commercial phase.

10. JSON Fields and Data Hygiene

While rendering the PDP, dotCMS produced warnings related to JSON fields:

No content to map due to end-of-input

The fields involved were JSON fields such as:

variantsJson

inventoryJson

The cause was not the PDP renderer. The cause was data hygiene.

Empty strings are not valid JSON.

For JSON fields, the import process should never write:

“”

It should write valid JSON defaults instead:

{}

or, when the field semantically represents a list:

[]

For fields that dotCMS expects to map as objects, {} is usually the safer default.

This became another blueprint rule:

Generated contentlets should never store empty strings in JSON fields. Always write valid JSON.

11. The MVP Boundary

One of the most important parts of this project was deciding what not to build.

The MVP includes:

  • Landing page
  • Editable hero slider
  • Product listing
  • Basic PDP
  • Contact CTA
  • Path toward AI chat

The MVP does not include:

  • Cart
  • Checkout
  • Payments
  • Product variants
  • Inventory synchronization
  • ERP
  • CRM
  • Advanced analytics
  • Personalization
  • Full ecommerce workflow

This boundary matters because it prevents the project from becoming too large before there is commercial validation.

The business can still sell a strong first version:

A catalog website with an AI-powered product assistant and editable content.

That is already valuable.

12. The AI Chat Layer

The next layer is the chat assistant.

The first commercial version does not need to be Amazon Rufus.

It only needs to answer catalog-related questions:

"I'm looking for men's shoes."

"Do you have wallets?"

"I'd like something as a gift."

"Which product do you recommend?"

The chat should use tools over the product catalog and return:

  • Product name.
  • Short explanation.
  • Price.
  • Link to PDP.
  • Optional image.

The first chat tool can be simple:

searchProducts(query, family, subfamily, limit)

A second tool can retrieve detail:

getProductBySlug(slug)

This keeps the assistant grounded in real catalog data.

The commercial story becomes:

Your website is not only a catalog. It has a virtual assistant that helps customers find products.

13. Lessons Learned

The most important lessons from this build were not abstract. They came from debugging real dotCMS behavior.

The code is the source of truth

Documentation helps, but the actual implementation, existing demo sites, logs, and Page Editor behavior reveal the real rules.

Logs are not noise

The logs showed when the starter was not being loaded, when dotCMS was falling back to default behavior, and when JSON fields had invalid data.

The Page Editor is part of the architecture

A page that renders publicly is not enough. It must also work correctly in the dotCMS editor.

Static and dynamic containers are different worlds

Using container_code.vtl in the wrong place can break the editable content model.

Content type variable names matter

For content-driven rendering, dotCMS expects renderer files named after the content type variable, such as:

heroBanner.vtl
leatherProduct.vtl

URL mapping should not be reinvented

For product detail pages, URL-mapped content is the right dotCMS pattern.

Generated data must be clean

Importers should generate valid defaults, especially for JSON fields.

Keep the MVP small

A small, working, reusable system is better than an unfinished ecommerce monster.

14. Final Blueprint

The reusable dotCMS site blueprint now looks like this:

  1. Extract existing catalog

  2. Normalize products into a clean model

  3. Generate/import dotCMS content types

  4. Upload assets

  5. Create contentlets

  6. Build reusable starter

  7. Create theme

  8. Create landing template

  9. Add editable hero slider

  10. Add product listing

  11. Configure URL-mapped PDP

  12. Render PDP with {contentTypeVariable}.vtl

  13. Add chat tools over the catalog

  14. Customize branding per client

This is the foundation for a repeatable service:

Build smart catalog websites in dotCMS with editable content, reusable templates, product detail pages, and an AI assistant layer.

The technical work was not just about getting one site to render.

It was about discovering the real dotCMS development model:

  • files
  • templates
  • themes
  • containers
  • content type renderers
  • URL maps
  • starters
  • logs
  • Page Editor behavior

Once those pieces are understood, the system becomes repeatable.

And that is where the business opportunity begins.


메타데이터
post_id
fa94ed9bfba7
slug
building-a-reusable-dotcms-site-blueprint-lessons-from-a-real-ai-catalog-mvp-fa94ed9bfba7
url
https://medium.com/@jsanca/building-a-reusable-dotcms-site-blueprint-lessons-from-a-real-ai-catalog-mvp-fa94ed9bfba7
canonical_url
https://medium.com/@jsanca/building-a-reusable-dotcms-site-blueprint-lessons-from-a-real-ai-catalog-mvp-fa94ed9bfba7
author_url
https://medium.com/@jsanca
status
ok
fetched_at
2026-06-10 12:26:30