---
title: 
date: 0001-01-01
canonical: https://mariothomas.com/yaml-migration-map/
---

# Frontmatter Field Classification & `params.mt` Migration Proposal

*Generated 2026-06-21. Companion file: `yaml-migration-map.xlsx`. Source inventory: `yaml-frontmatter-audit.*`.*

Every field currently used across the 267 index files, classified against Hugo's reserved front-matter fields (verified against the Hugo 0.163.3 documentation), with a proposed target for everything that isn't Hugo's.

## How Hugo decides what's "its own"

Hugo reserves a fixed set of front-matter field names (`title`, `date`, `type`, `layout`, etc.). Reserved names are case-insensitive — Hugo lowercases keys on read, so `lastMod` resolves to Hugo's `lastmod` and `Layout` resolves to `layout`. Any name **not** on that list is a custom field, and since Hugo 0.123 custom fields are only exposed to templates if they live under the `params` key. That single rule explains both buckets below: legitimate Hugo fields stay at the top level, and everything else belongs under `params.mt`.

## 1. Hugo reserved fields — keep at top level (14)

These are genuine Hugo fields and should remain exactly where they are, as siblings of `params`.

| Field | Type seen | Keep / action |
|---|---|---|
| `title` | string | keep |
| `description` | string / null | keep |
| `keywords` | string / null | keep (Hugo expects a list, but tolerates a string) |
| `draft` | bool | keep |
| `date` | datetime / date | keep — **normalise to one format** (audit conflict) |
| `lastMod` | date / datetime | keep — resolves to `lastmod`; **normalise** (audit conflict) |
| `publishDate` | datetime / null | keep |
| `expiryDate` | null only | keep — Hugo field, currently always empty; omit when unused |
| `type` | string | keep — `blog` / `page` / `section` / `briefings` (drives templates) |
| `layout` | string / null | keep |
| `linkTitle` | string | keep |
| `slug` | string | keep |
| `url` | null only | keep — Hugo field, currently always empty; omit when unused |
| `params` | map | keep — this is the container that will hold `params.mt` |

One fix in this group: **`Layout` (capital L)** on `tags/_index.md` is the same field as `layout` (Hugo lowercases it). Rename it to lowercase to remove the duplicate.

## 2. Custom fields sitting at the top level — in error (4 blocks)

These are **not** Hugo fields, yet they sit at the top level where, under current Hugo, templates can't reliably read them. They should move under `params.mt`.

| Current | → Proposed | Where it occurs |
|---|---|---|
| `seoTitle` | `params.mt.seoTitle` | 217 files |
| `hero.*` | `params.mt.hero.*` | `about/_index.md` |
| `videoSpotlight.*` | `params.mt.videoSpotlight.*` | root `_index.md` |
| `showVideo` | `params.mt.showVideo` | root `_index.md` |

## 3. Custom data already under `params` — move into `params.mt.*` (the bulk)

This is the real mariothomas.com data. The proposal **dissolves the redundant `params.content` wrapper** — since `params.mt` now *is* the content namespace — promoting its children up one level, while keeping logical sub-groups (`author`, `thumb`, `audio`, `links`) intact.

| Current path | → Proposed |
|---|---|
| `params.content.type` | `params.mt.type` |
| `params.content.category` | `params.mt.category` |
| `params.content.featured` | `params.mt.featured` |
| `params.content.file` / `.mime` / `.time` | `params.mt.file` / `.mime` / `.time` |
| `params.content.url` / `.url_title` | `params.mt.url` / `.url_title` |
| `params.content.x` / `.x_title` | `params.mt.x` / `.x_title` |
| `params.content.location` / `.location_title` | `params.mt.location` / `.location_title` |
| `params.content.thumb.*` | `params.mt.thumb.*` (`src, alt, caption, type, width, height, version`) |
| `params.author.{name,email}` | `params.mt.author.{name,email}` |
| `params.audio.{source,duration,size,text}` | `params.mt.audio.*` |
| `params.links[]` + `.links_title` / `.links_description` | `params.mt.links[]` + `.links_title` / `.links_description` |
| `params.abstract` | `params.mt.abstract` |
| `params.hide` | `params.mt.hide` |
| `params.tags` / `params.categories` | `params.mt.tags` / `params.mt.categories` **— see note** |

## 4. Drop (2)

Always-null, and duplicate the Hugo top-level fields of the same name:

- `params.url` → drop (use Hugo's top-level `url` if a URL override is ever needed)
- `params.resources` → drop (Hugo's `resources` is a reserved top-level field for page-resource metadata)

## One decision to make: tags & categories

`params.tags` and `params.categories` are the one ambiguous case. If you want them to remain **plain data** (labels you render yourself), `params.mt.tags` / `params.mt.categories` is correct. But if you want Hugo's **taxonomy system** — term pages, `/tags/...` listings, `.GetTerms` — those keys must sit at the **top level** (`tags:` / `categories:`), not under `params` at all. You already have a `tags` taxonomy and a `tags/` section, so this is worth resolving deliberately rather than defaulting.

## Summary of the move

Of the 71 distinct key paths: **14 stay** as Hugo fields (plus the `Layout`→`layout` casing fix), **2 are dropped**, and the remaining **54 consolidate under `params.mt.*`** — whether they were mis-placed at the top level (`seoTitle`, `hero`, `videoSpotlight`, `showVideo`) or already living in `params` (`content.*`, `author`, `audio`, `links`, etc.). The proposed `params.mt` tree is laid out on the second sheet of the workbook.

The colour-coded, file-count-weighted version of this mapping — every path, its classification, and its target — is in `yaml-migration-map.xlsx`.
