Schema reference

Conditional rendering

visibleIf, path syntax, operators, composite conditions, and whenHidden — make fields appear and disappear based on the values of other fields.

Not verified yet

The CMS supports field-level conditionality via the visibleIf knob on every field. A field with visibleIf only renders when its condition evaluates true; when false it's hidden from the editor AND skipped by validation (so a hidden required field doesn't block Publish with "is required" the author can't see).

This is the schema-language feature that lets you build forms that adapt to the author's choices — "show seats only when tier is pro or enterprise", "show the discount-code field only when featured: true", "show the secondary-CTA group only when the primary CTA is set."

The shape

visibleIf is either a single leaf condition or a composite of leaves under all / any / not.

- key: seats
  type: number
  label: Seats
  visibleIf:
    field: tier
    op: in
    value: [pro, enterprise]

A leaf has three keys:

KeyTypeEffect
fieldstringPath to the field whose value drives visibility. See Path syntax.
opstringComparison operator. See Operators.
valueanyValue to compare against. Optional for presence operators.

The field path resolves against the current scope — siblings by default, parents / root / array-item ancestors via the $prefix syntax.

Path syntax

The CMS's path resolver supports four prefixes:

PrefixResolves againstUse when
keySibling at the current group level.Most common — the controlling field is right next to this one.
$parent.keyWalk one group level up; chainable.Inside a nested group, controlling field is one level up.
$root.path.xFrom the page-frontmatter root.Need a top-level field regardless of nesting depth.
$item.flagNearest enclosing array-item scope.Inside an array item; controlling field is on the same item.

Sibling (key)

The default. Use when the controlling field is at the same group level as the dependent one.

- key: tier
  type: select
  label: Tier
  options: [free, pro, enterprise]
- key: seats
  type: number
  label: Seats
  visibleIf:
    field: tier # sibling
    op: in
    value: [pro, enterprise]

Walking up ($parent)

Each $parent. segment walks one group level up. Chainable ($parent.$parent.foo).

- key: meta
  type: group
  label: Meta
  fields:
    - key: enableSchema
      type: boolean
      label: Enable schema
    - key: schemaContent
      type: paragraph
      label: Schema content
      visibleIf:
        field: enableSchema # sibling inside `meta`
        op: truthy
- key: extra
  type: group
  label: Extra
  fields:
    - key: customSchemaContent
      type: paragraph
      label: Custom schema content
      visibleIf:
        field: $parent.meta.enableSchema # walk up to root, descend into meta
        op: truthy

Absolute ($root)

Reaches the page-frontmatter root regardless of how deep the current field is. Useful for "the entire site behaves this way when X is set on the root."

- key: blocks
  type: array
  items:
    - $ref: pricing-table
      # fields inside the pricing-table block...
      # ...one of those fields:
      - key: addon
        type: text
        visibleIf:
          field: $root.tier         # root frontmatter regardless of array depth
          op: eq
          value: enterprise

Array-item scope ($item)

The nearest enclosing array-item carries $item. Useful when fields inside a multi-shape array item want to depend on the item's discriminator or one of its own siblings.

- key: blocks
  type: array
  items:
    - $ref: media-text
      # inside media-text:
      - key: layout
        type: select
        options: [left, right, wide]
      - key: wideOptions
        type: group
        visibleIf:
          field: $item.layout
          op: eq
          value: wide
        fields: [...]

Operators

OpCompares
eqStrict equality with value.
neqStrict inequality.
invalue is an array; the field's value is in that array.
notInInverse of in.
truthyJS-truthy (true, non-empty string, non-zero number, …).
falsyJS-falsy.
empty"", [], null, undefined.
nonEmptyInverse of empty.
gt / ltNumeric compare with value.
matchesvalue is a regex source string; tests against the field's value.

The four presence operators (truthy, falsy, empty, nonEmpty) skip the field resolvability warning the schema validator otherwise emits. Missing paths legitimately read as "absent" — exactly what truthy / empty are for.

Composite conditions

Wrap leaves under all / any / not for boolean composition.

all — every sub-condition must be true

visibleIf:
  all:
    - field: tier
      op: eq
      value: pro
    - field: $root.featureFlags.advancedSeats
      op: truthy

any — at least one sub-condition must be true

visibleIf:
  any:
    - field: tier
      op: eq
      value: pro
    - field: tier
      op: eq
      value: enterprise

(For "value is one of N" prefer op: in over any of eqs — it's shorter and the schema-warnings validator handles it the same way.)

not — invert a sub-condition

visibleIf:
  not:
    field: tier
    op: eq
    value: free

(For simple negation prefer op: neq over not of eq.)

Composing — all / any / not nest arbitrarily

visibleIf:
  all:
    - any:
        - field: tier
          op: eq
          value: pro
        - field: tier
          op: eq
          value: enterprise
    - not:
        field: $root.frozen
        op: truthy

whenHidden

Controls what happens to the stored value when visibleIf flips false.

- key: discountCode
  type: text
  label: Discount code
  visibleIf:
    field: $root.featured
    op: truthy
  whenHidden: drop # purge value on Publish when hidden
ValueEffect
keepDefault. The stored value is preserved across visibility flips.
dropThe value is purged at the YAML boundary on Publish when the field is hidden.

Use drop when carrying a stale value forward would itself be a data quality problem — credit-card details that shouldn't survive a payment-method switch, secret keys, scheduled-publish dates that no longer apply.

Use the default keep when the field's value is intrinsically meaningful even when hidden (a stored draft, a setting the author might want to flip back on later).

Edge cases

Missing referenced field

When visibleIf.field references a path that doesn't resolve, the leaf evaluates undefined — the dependent field stays hidden.

The schema-warnings validator flags this as a severity: warning so the schema author sees the authoring mistake (typo, renamed sibling, $parent chain that walked too far). It doesn't block save; the warning is advisory.

Presence operators (truthy / falsy / empty / nonEmpty) skip the warning — for them, "missing" is a legitimate signal, not a typo.

Cyclic dependencies

Two fields can hide each other based on a third. Not deadlocked — the evaluator is one-shot per render, no fixpoint required. Just document the chain so future maintainers don't get confused.

Multi-shape array items

Switching shape inside a multi-shape array item changes which fields exist. If a sibling visibleIf referenced a now-deleted sibling, the dependent field's "Add value" affordance appears / disappears based on the new shape's content. Acceptable — the schema editor's per-shape preview already shows the author what each shape contains.

Hidden fields × history

The History tab guards the per-field "see history" button so it doesn't appear for fields the author can't currently see. A field that gets a whenHidden: drop purge still has a paper trail in git — but the working-copy editor surfaces won't expose it until the visibility flips back on.

When NOT to use visibleIf

  • For "this field doesn't apply to this content type" — declare a different collection / block schema instead. visibleIf is for fields that legitimately exist on this content but only apply some of the time.
  • For "this field is computed from another" — use computed instead.
  • For "this field is hidden from the editor but used by the build pipeline" — use hidden: true (always hidden, value still seeded from default) — much simpler than a visibleIf: { ... falsy }.

On this page