Skip to content

Template blocks & overrides

A template is not one big HTML file. It is assembled from blocks -- small, named pieces of markup plus settings. This page covers the two tables that hold them: template_blocks (a template's own blocks) and site_block_overrides (a site's per-element tweaks on top of those blocks), and the precise order in which the renderer resolves the final HTML and settings for an element.

template_blocks

A template_blocks row (model TemplateBlock) is a block of markup + settings within a template, keyed by element_type. Besides the shared CMSBase columns:

Column Type Notes
template_id INT FK → templates.id, NOT NULL, indexed The owning template.
element_type VARCHAR(128) NOT NULL, indexed String reference to template_elements.name (not a FK). Which element this block fills.
html_content LONGTEXT, nullable The block's markup. LONGTEXT because rendered markup can exceed TEXT's 64 KB limit.
settings JSON, nullable The block's settings.
description TEXT, nullable Free-text description.
edited_by INT FK → users.id, nullable, indexed Edit tracking (AISD-126).
creator_type VARCHAR(64), nullable String reference to creator_types.name.

Relationship: template (the owning Template).

site_block_overrides

A site_block_overrides row (model SiteBlockOverride) is a site-specific override of a template block, keyed by the same element_type string. It lets one site tweak a shared template without forking it. Besides CMSBase:

Column Type Notes
site_id INT FK → sites.id, NOT NULL, indexed The owning site.
element_type VARCHAR(128) NOT NULL, indexed String reference to template_elements.name -- the element this override applies to.
html_content LONGTEXT, nullable Override markup (null = inherit).
settings JSON, nullable Override settings (merged over the block's).
edited_by INT FK → users.id, nullable, indexed Edit tracking (AISD-126).
creator_type VARCHAR(64), nullable String reference to creator_types.name.

Relationship: site (the owning Site).

The element_type string key

Both tables key on element_type, a string that names a row in template_elements (e.g. header, footer, sidebar, article_card). It is deliberately not a foreign key, and it is indexed on both tables for the render-time lookup.

The names come from the template_elements lookup, seeded in 0001_seed_lookups.sql -- page elements (homepage, article, category, author, 404, the static pages) and component elements (layout, header, footer, sidebar, article_card, breadcrumb, pagination, author_card).

Why a string key, not a FK to template_blocks.id? Because an override is attached to the element name, a site's overrides survive a template switch. If a site moves from celebrity-v1 to some celebrity-v2, its override for the footer element still applies, because both templates have a footer element. A foreign key to a specific block id would point at a row that belongs to the old template and would dangle the moment the site switched. The string key follows the schema-wide "string values, lookup tables for admin validation" decision: admin tooling validates the name against template_elements, but the database does not enforce it as a FK.

Resolution order (most specific wins)

When the renderer needs the final HTML and settings for an element on a given site, it layers three sources, from least to most specific.

HTML

template_blocks.html_content  ->  site_block_overrides.html_content
        (template default)              (site override, wins)

The template's block markup is the default; a site override for the same element_type replaces it. Most specific wins.

Settings

template_elements.default_settings  ->  template_blocks.settings  ->  site_block_overrides.settings
        (global element default)            (template layer)              (site override, wins)

Settings layer in three steps, each overriding the previous:

  1. template_elements.default_settings -- the global default for that element kind.
  2. template_blocks.settings -- the template's values for its block.
  3. site_block_overrides.settings -- the site's overrides, the most specific layer.

Mental model

Read the chains left to right as "general → specific". The site override is always the last word; where it is absent, the value falls back to the template layer, then to the global element default.

History

Both tables have a flat history mirror populated by an AFTER UPDATE trigger: template_block_history (via trg_template_blocks_history) and site_block_override_history (via trg_site_block_overrides_history). They follow the same decoupled-mirror pattern as every other history table -- see Data model overview.