Flex Field Groups
← Back to Table of ContentsWhy these migrations exist
Filament Flex Fields ships three optional database tables as part of the M8 schema product. They are not required for normal field usage, AudioField, orHasFlexFields with PHP/config schemas.
The package registers migrations via
loadMigrationsFrom, so php artisan migrate on a host app creates the tables even when the admin resource stays disabled. That is intentional: empty tables are harmless; enabling the feature later does not require a separate migration step.
You can also publish migrations explicitly:
The three tables
flex_field_groups
Stores editable field group definitions for the optional Filament resource.
Model:
Bjanczak\FilamentFlexFields\Models\FlexFieldGroup
Unique constraint: (tenant_id, slug) — same slug may exist for different tenants; global groups use empty string '' (not SQL NULL).
flex_field_schema_versions
Append-only version history for SchemaRegistry (enterprise control plane), not for individual Eloquent records.
Model:
Bjanczak\FilamentFlexFields\Models\FlexFieldSchemaVersion
Generic SchemaRegistry::publish() calls may omit flex_field_group_id; group publishes always set it. Use $group->schemaVersions() for the relationship.
flex_field_schema_heads
Stores the last issued and current live version for each schema ID. A locked head
serializes publication and prevents competing editors from issuing the same version.
It also retains issued version numbers when history is explicitly purged. Install
its migration together with flex_field_schema_versions when using persistent
snapshot history.
Architecture (what talks to what)
SchemaRegistry (enterprise versioning) is separate from FlexFieldSchemaRegistry (runtime form builder), but Field groups sync into the runtime registry automatically when schema.sync_on_save / schema.sync_from_database are enabled (default).
Do I need to run migrations?
If you never enable the resource and never call
SchemaRegistry::publish(), the tables stay empty and the package behaves like a config-only install.
Enable the Filament resource
- Run migrations (see above).
- Turn on the resource:
- Keep
FilamentFlexFieldsPluginon your panel — it registersFlexFieldGroupResourcewhen the flag is true.
/flex-field-groups slug under your panel path).
Flex Field Studio (Beta)
Whenschema.management_page_enabled is true, Flex Field Studio (Beta) lists groups per discovered entity. Each card shows the latest SchemaRegistry version and approval state (draft, review, or live) when the group has been published; otherwise it reads Not published.
The Studio flag currently defaults to true, but the page is registered only when schema.resource_enabled is also enabled. To use the Field groups resource with Studio disabled, set:
schema.management_page_enabled to false in config/filament-flex-fields.php. After changing a cached configuration, run php artisan config:clear.
SchemaRegistry persistence
Whenschema.registry_persistence is true (default) and flex_field_schema_versions exists, publish/rollback writes to the database. Otherwise SchemaRegistry uses in-memory storage (fine for unit tests).
draft, review, live.
Rows published via $group->publishToRegistry() store flex_field_group_id and are available through $group->schemaVersions().
Runtime sync (automatic)
When enabled (default), groups stay aligned withFlexFieldSchemaRegistry:
Key collision: DB groups registered after config schemas use the same string key — a DB group with slug
crm overwrites a config schema keyed crm. Use distinct slugs or disable sync_from_database.
One-line Filament integration (FlexFieldStudio)
After a model uses HasFlexFields and field groups are synced to the runtime registry, wire forms, tables, and infolists without hand-building each component:
Sections in runtime forms
When a field group defines sections in the admin UI, useflexFieldFormLayout() (instance or static) to render one Filament Section per schema section, plus an optional catch-all section for ungrouped fields:
flexFieldFormSection() remains available for backward compatibility: it returns a single section wrapping the layout when multiple sections exist.
Import, export, publish, and rollback all preserve sections and section_id on fields.
Tenant-aware resolution uses FlexFieldSchemaResolver (global schemas + tenantId:slug scoped groups). Field types can be limited per tenant via TenantFieldPacks::registerPack() and per user via FieldRbacMatrix.
Filament admin workflow
On Create / Edit field group, header actions include:
The repeater supports slug, label, type, sort, required flag, help text, placeholder, config JSON, and JSON condition rules (
visible_when, required_when, disabled_when). Saves run through FlexFieldGroupValidator (duplicate slug detection, type checks, FlexFieldDefinition parsing).
Programmatic API remains on the model:
Tenant resolver & admin scoping
Register how the active tenant is resolved for schema filtering and optional admin list scoping:scope_resource_by_tenant is true, the Field groups list shows global groups (tenant_id = '') plus groups for the resolved tenant.
FlexFieldGroup helpers
toRegistrySchema() returns a payload compatible with SchemaImportExport / blueprint packs. Set target_type on each group (or FLEX_FIELDS_SCHEMA_DEFAULT_TARGET) so HasFlexFields resolves the correct Eloquent model.
Security & RBAC
Default policy requires Gate abilitymanageFlexFieldSchemas (configurable):
publish / rollback on the policy. Enable scope_resource_by_tenant or add a custom policy / global scope for stricter tenant isolation.
Slug uniqueness (multi-tenant)
- Database: unique index on
(tenant_id, slug)withtenant_id = ''for global groups (no MySQLNULLduplicate slugs). - Filament form:
uniquerule scoped to the sametenant_idvalue.
Configuration reference
Tests
tests/Schema/FlexFieldStudioTest.php, tests/Schema/FlexFieldGroupResourceTest.php, tests/Schema/FlexFieldGroupRegistrySyncTest.php, tests/Schema/FlexFieldGroupEnterpriseTest.php, tests/Unit/EnterpriseControlPlaneTest.php — resolver/tenant/RBAC, FlexFieldStudio form+table builders, validation, CRUD, registry sync, publish/rollback/import/blueprint Livewire actions, composite slug uniqueness.
Related
- JSON schemas in config:
config/filament-flex-fields.php→schemas - Runtime builder:
HasFlexFields,FlexFieldFormBuilder,FlexFieldSchemaRegistry,FlexFieldStudio - Blueprint packs:
SchemaBlueprintPacks(CRM / HR / Booking / …) - Import / export:
SchemaImportExport - JSON conditions: Schema conditions
- Tenant access policies:
TenantFieldPacks,FieldRbacMatrix - Component monitoring: Laravel application events
Detect simultaneous schema edits
For an editor or API that publishes snapshots, remember the schema’s version when opening it and pass that version when saving. Zero means no snapshot has been published yet. Scope the schema ID to your tenant in application code.expectedVersion retain their publication behavior. Merely
reading currentVersion() does not activate conflict detection. This guard applies
to schema snapshots, not automatically to ordinary record edits, the field-group
editor or every application transaction. Persistent versioning requires both the
schema versions and schema heads migrations. Request-local in-memory history is
intended for isolated uses; it is not durable shared editor storage.
Validate exchanged schemas before saving
required
flag remains readable; explicit is_required or isRequired takes precedence.
Version-zero migration accepts the documented old field, name, default and
required aliases. It preserves an explicit canonical null or false and removes
redundant legacy aliases. Invalid records and future versions are rejected instead
of being replaced by empty fields.
Authorize importing/configuring a schema separately. Verify that its target
matches an application-approved target before applying it. The target is data;
import does not instantiate that class or grant access to its records. Built-in
configuration shape checks do not certify every component’s business rules. For
external families, use their typed family schema codec and configured adapter.
Boolean switches must use true or false, including encryption settings.
Numeric limits must be finite, steps positive, and minimums cannot exceed maximums.
Numeric strings accepted by the field configurators remain supported. Length limits
must be nonnegative integers or integer strings. Optional empty widths use the
normal full-width layout; other widths must be supported enum values. These checks
also run when a field group is saved, before it is stored or published.
Apply validated values to a record
UseapplySubmittedFlexFieldValues() on a model using HasFlexFields when accepting
values from an API or another interface outside a Filament form. Define the rules
and authorization policies in your application, rather than accepting them in the
request. This method applies a patch: omitted fields keep their existing values,
while an explicitly submitted null clears a field whose rules allow it.
save() inside your
transaction when the patch is accepted.
Nested rules use relative paths such as *.title or *.tags.*. Validate the parent
container too: array:title,tags rejects unexpected map keys, list requires a
consecutive list, and max:20 limits its length. Use present when an empty array
is allowed. Expansion is limited to 4,096 validation paths and 65,536 rules for one
submission. Overlapping expanded patterns are rejected.
Encrypted storage definitions automatically enable encryption on contracts for
those exact field paths. Text resembling encrypted data is treated as incoming
text and encrypted again as a new value. Declare an encrypted field as its own
contract; do not patch through its parent or child path. Existing stored ciphertext
in omitted fields remains unchanged.
This API keeps record permission, tenant selection, schema selection and transaction
control in your application. The trusted setters setFlexFieldValue() and
setFlexFieldValues() still prepare storage values; they do not perform these
request-level permission checks.
See value submission and data limits
for saving values outside a form. Laravel documents the underlying
transaction and
row-locking behavior.
Configure approved application references
Saved field configuration accepts the settings declared for that field type. Unknown names, invalid values and conflicting aliases are rejected before a group is saved or a form is built. Before updating an existing installation, review old schemas and remove or migrate settings that are no longer used. The package does not silently delete your saved settings. If a dynamic schema selects an application model, custom option view or Livewire method, approve that reference in your application configuration. For example:models covers UserSelect’s option_model (and its model alias) and Slug’s
spatie_model. views covers option_view and option_trigger_view. methods
covers VerificationCode’s auto_submit_method and TodoList’s remote_loader.
Each category accepts up to 128 explicit references for that field type. References
are denied by default; adding one to an imported file does not approve it.
These lists apply to dynamic schema configuration. You can still configure a
component directly through its PHP methods. Approving a reference does not grant
access to records: keep your configure/view/edit policies, tenant restrictions and
model query scopes in place.
To find obsolete configuration names before deploying an update:
config.formula or config.calculated receives
the same expression and dependency checks as a formula in the field metadata.