Browse all documentation

Required fields

Make a field required when documents already exist: how the migration audits stored values, what counts as missing, and how to backfill them.

Documents saved before a field became required may have no value for it. Ridu checks them when the schema changes, so a missing value stops the migration instead of breaking reads later.

A field becomes required when you add .Required() to it, add a required field to a collection or global that already has documents, require a child of an existing group, array row or block, or change the kind of a required field.

What the migration does

create adds an audit step to the migration and warns you about it:

terminal
$ ridu migrate create --name require-summary
warning  RIDU_REQUIRED_FIELD_AUDIT  require posts.summary; the
         migration audits stored documents after its data
         transforms and fails with RIDU_REQUIRED_VALUES_MISSING if
         any document that must be complete has no value

When up reaches the audit, it reads the stored documents and stops if any of them lacks a value:

ridu migrate up
1. change the schema
2. run the migration's data transforms, if it has any
3. audit the documents that must be complete
     posts/post_1   summary  ✓
     posts/post_4   summary  ✗ ──▶ stop: RIDU_REQUIRED_VALUES_MISSING
4. make the field required

The error names each field, its locale when it has one, how many documents lack a value, and up to five of their IDs:

terminal
RIDU_REQUIRED_VALUES_MISSING: stored documents have no value for
fields that become required: posts.summary in 1 document, for
example hello. …

ridu dev runs the same audit before it syncs. It keeps the current schema and lists the documents to complete; fill them in, or keep the field optional, and save again.

Fix missing values

Write the values before the field becomes required. Bind a compiled data transform that backfills them to the same migration. It runs before the audit:

terminal
ridu migrate create --name require-summary \
  --transform backfill-summaries

Two cases work differently:

  • Versioned collections and globals. A transform can’t change their fields, so backfill them in an earlier data-only migration, then require the field in the next one.
  • PostgreSQL defaults. PostgreSQL writes a top-level, untranslated field’s .Default(...) into existing rows when it adds the column, so a required field with a default needs no transform there. SQLite and MongoDB apply defaults only to new documents.

Otherwise, keep the field optional.

What counts as missing

A value is missing when it is absent or null, an empty string for text, select, relationship and upload fields, or an empty list for arrays, blocks, lists and multiple selections. That is the same rule a save applies.

  • Translations. A locale without a translation is optional, but an empty translation is missing, and so is a translated field with no translation in any locale. See Localization.
  • Nested fields are checked in every group, array row, block and translation that exists. An absent optional group has nothing to check.
  • Blocks. A field required inside a block is one requirement, named like block hero.heading, checked in every stored hero block of every collection and global.
  • Plugin payloads, such as the fields of a rich-text block, are not audited. Making one of those required needs a data transform.

Which documents are audited

Resource Audited
Without drafts every document, including trashed ones
With drafts every published document, and its working copy unless it’s a draft
Retained version snapshots never; they are history

Drafts may stay incomplete; publishing validates them. A stored document that still lacks a required value stays readable, with the field returned as null.

How each database applies it

  • SQLite applies the migration in one transaction, so a failed audit rolls back everything. ridu migrate down audits a rollback that makes a field required again.
  • PostgreSQL adds the column as nullable, runs the transforms, audits the rows under a lock that holds writers, then adds NOT NULL. The transforms, audit and constraint share one transaction, so a failed audit rolls them back together.
  • MongoDB can’t lock collections against old writers, so a migration with this audit needs --allow-maintenance on up. See Deploy migrations.