Custom Fields Logo
3.x
Essentials

Builder Scoping

Scope custom-field resolution to specific sections, and hook into code-uniqueness resolution

onlySections()

By default, every builder (CustomFields::form(), ::infolist(), ::table(), ::exporter(), ::importer()) resolves fields for the whole entity type. onlySections() constrains that resolution to a specific set of custom_field_sections rows:

use Relaticle\CustomFields\Facades\CustomFields;

CustomFields::form()
    ->forModel($model)
    ->onlySections([$sectionA->id, $sectionB->id])
    ->build();

Passing [] (the default) means "no scope" — existing call sites that never call onlySections() are unaffected.

This exists for consumers that version their form definitions — for example, cloning a section (and its fields) per form version. Field codes are normally unique per entity type, so two versions of "the same field" would collide on code unless resolution is scoped by section. onlySections() lets each version's builder only see its own section(s), so the same code can live in more than one section without one bleeding into the other's schema.

onlySections() is inherited by every builder from BaseBuilder, including through FormBuilder::build() / InfolistBuilder::build() — the scope is threaded through FormContainer / InfolistContainer as well, so it applies whether you call ->build() or ->values().

The persistence contract — read this before relying on section-scoped codes

onlySections() narrows resolution (which fields get loaded onto a form, infolist, or table). It does not change how values are saved. UsesCustomFields::saveCustomFields() iterates the model's custom-field-values relationship and writes each submitted value by field code. If two sections share a code and that relationship isn't scoped to match onlySections(), saveCustomFields() will silently write the same value to both field rows — a data-corrupting outcome that has nothing to do with whether resolution scoping itself is working correctly.

If you use onlySections(), scope your model's custom-field-values relationship (customFields() by default, or your override) to the same section(s). It is entity-scoped by default and overridable per model.

CodeGenerator::resolveUniquenessScopeUsing()

Auto-generated codes (FIELD_CODE_AUTO_GENERATE) are checked for collisions before use. Register a callback to narrow that check the same way onlySections() narrows resolution:

use Illuminate\Database\Eloquent\Builder;
use Relaticle\CustomFields\Support\CodeGenerator;

CodeGenerator::resolveUniquenessScopeUsing(
    fn (string $entityType, string $type, int|string|null $sectionId): ?Closure => $sectionId !== null
        ? fn (Builder $query): Builder => $query->where('custom_field_section_id', $sectionId)
        : null
);

The callback receives:

  • $entityType — the entity the field or section belongs to.
  • $type'field' or 'section', so you can scope differently per kind of code.
  • $sectionId — the section the code is being generated within, or null when there isn't one (for example, the sectionless field-management table).

Return null to leave the uniqueness check global — the default, backward-compatible behavior. Return a closure to narrow it: the closure receives the in-progress Builder and must return the query to apply. where()-style mutation also works (it returns the same instance), but a closure that hands back a different instance — e.g. $query->clone()->where(...) — is honored too, since the return value is always what's used.

Register the callback once, typically in a service provider's boot() method.

Copyright © 2026