Field Contracts
Advanced Custom Fields
Foundry handles advanced custom fields as a theme-owned contract. Themes declare what
fields exist, which pages they apply to, and which shared/global groups are editable.
Documents keep their own page-specific values, while shared values live in
content/custom-fields.yaml.
Core model
-
The theme owns the schema. Contracts are declared in
theme.yamlunderfield_contracts. -
The document owns page-specific values. Those stay in frontmatter
under
fields:. -
Shared/global values live in one file. Foundry stores them in
content/custom-fields.yaml. -
The admin resolves fields from the active theme. The editor shows the
matching document contract for the current page. The
Custom Fieldsscreen shows shared contracts. -
Plugins must keep derived metadata out of persisted fields.
Runtime-only values like TOC or reading time belong in render-time context data such
as
ctx.Data.
Theme contract example
field_contracts:
- key: marketing-page
title: Marketing Page
description: Standard hero fields for marketing pages.
target:
scope: document
types: [page]
layouts: [page]
slugs: [pricing, about, contact]
fields:
- name: hero_eyebrow
type: text
label: Hero Eyebrow
- name: hero_title
type: text
label: Hero Title
required: true
- name: hero_body
type: textarea
label: Hero Body
- name: cta_label
type: text
label: CTA Label
- name: cta_url
type: text
label: CTA URL
- key: site_marketing
title: Shared Marketing Copy
description: Shared CTA defaults for the whole site.
target:
scope: shared
key: site_marketing
fields:
- name: primary_cta_label
type: text
label: Primary CTA Label
- name: primary_cta_url
type: text
label: Primary CTA URL
Document values
---
title: Pricing
slug: pricing
layout: page
fields:
hero_eyebrow: Clear pricing
hero_title: Pricing that keeps infrastructure calm
hero_body: Foundry keeps teams on a predictable operational surface.
cta_label: Start with Foundry
cta_url: /contact/
---
Page body content here.
Shared values
values:
site_marketing:
primary_cta_label: Launch with Foundry
primary_cta_url: /contact/
Template access
Document values are read with field .Page "...". Shared values are exposed
through .Site.CustomFields by contract key.
<section class="hero">
<div class="eyebrow">{{ with field .Page "hero_eyebrow" }}{{ . }}{{ else }}Marketing{{ end }}</div>
<h1>{{ with field .Page "hero_title" }}{{ . }}{{ else }}{{ .Page.Title }}{{ end }}</h1>
<p>{{ with field .Page "hero_body" }}{{ . }}{{ else }}{{ .Page.Summary }}{{ end }}</p>
{{ $shared := index .Site.CustomFields "site_marketing" }}
<a class="button" href="{{ with field .Page "cta_url" }}{{ . }}{{ else }}{{ index $shared "primary_cta_url" }}{{ end }}">
{{ with field .Page "cta_label" }}{{ . }}{{ else }}{{ index $shared "primary_cta_label" }}{{ end }}
</a>
</section>
How the admin behaves
- When a user opens a page in the editor, Foundry matches the active theme contracts by document type, layout, and slug.
- The editor shows the matching contract title so the user knows why those fields are available on that page.
-
The
Custom Fieldsadmin section shows shared contracts declared by the active theme. -
Users with
config.managecan edit and save shared/global field values. -
Users without
config.managecan still inspect shared field groups, but the save controls remain disabled.
Migration from the old model
Advanced field schemas no longer belong in content/config/site.yaml. If
you still have an older site using config-owned field schemas, migrate them with:
foundry theme migrate field-contracts
Related docs
- Theme authoring for manifest structure and layout contracts
- Plugins for runtime-data guidance and field safety rules
- Configuration for the remaining site config reference
