Boundaries and Layout

SchemaBoundary and layout containers solve independent problems.

ConstructPurposeChanges database schema?Creates UI structure?
SchemaBoundaryOptionally tags collections with feature ownershipAdds featureTags to the orchestration result; collections compile normallyNo
FormStepDefines a step around child render nodesNoYes
ConditionalBlockDefines conditional visibilityNoYes
MultiPopulatorDefines repeated entries and binds them to JSONB or a secondary collectionNo; the referenced field or collection owns storageYes
OrConditionDefines alternative pathsNoYes

A boundary needs no group or step

const tagged = createFormSchemaOrchestrator([
  SchemaBoundary(
    { featureId: 'crm', name: 'CRM' },
    contacts,
    projects,
  ),
])

This compiles direct render_collection nodes and records:

tagged.featureTags.contact === 'crm'
tagged.featureTags.project === 'crm'

The untagged form is equally valid:

const direct = createFormSchemaOrchestrator([contacts, projects])

Use a boundary only when feature ownership is useful.

Repeaters separate interaction from persistence

MultiPopulator always represents an array of repeated entries in the form state. Its output says where that array will be persisted:

Output strategySchema ownerRepeater entry shape
separateTableA child or explicitly referenced collectionThe collection's fields
jsonbAn existing RAW_JSON or ARRAY field with .strategy('jsonb')The repeater's entryFields

The repeater does not create storage invisibly. A secondary-table collection still compiles as a normal collection. A JSONB field still belongs to its parent collection and appears in databaseSchema.toJSONSchema().

This distinction keeps three concepts separate:

  1. MultiPopulator controls repeated form state.
  2. output binds that state to a persistence target.
  3. The target field or collection defines the physical schema.

Add layout only for real interaction structure

const stepped = createFormSchemaOrchestrator([
  FormStep('project', { showTitle: true }, projects),
  FormStep('contacts', { showTitle: true }, contacts),
])

In the public 0.9.50 types, a SchemaBoundary accepts collections and nested boundaries—not layout containers. Keep boundaries and layout as separate top-level shapes.

Next: Schema Declaration Overview.