Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Custom Fields Laravel Package

relaticle/custom-fields

Laravel/Filament plugin that adds dynamic custom fields to any Eloquent model without database migrations. Supports 20+ field types, conditional visibility, multi-tenancy, CSV import/export, optional encryption, and custom field types.

View on GitHub
Deep Wiki
Context7

Getting Started

This package adds user-defined custom fields to any Eloquent model without new migrations, and renders them natively in Filament. It is a Filament-first package: the value it delivers is almost entirely in the panel UI (forms, tables, infolists, and an admin screen for defining fields), with a thin persistence layer underneath.

Minimum setup:

  1. Confirm the stack. PHP 8.3+, Laravel 12 (13 supported since v3.1.6), and Filament 5.0+. If you are on Filament 3 or 4, stop — v2 required Filament 4, v3 requires Filament 5, and there is no supported path for older panels.
  2. Install and publish the package, its migrations, and config/custom-fields.php.
  3. Register the plugin in your panel provider: ->plugins([CustomFieldsPlugin::make()]).
  4. Opt the model in by implementing HasCustomFields and applying the trait. Note the namespace has moved more than once (v2 relocated InteractsWithCustomFields to Relaticle\CustomFields\Concerns, while the README shows Models\Concerns\UsesCustomFields) — verify the FQCN against vendor/relaticle/custom-fields/src for your installed version rather than copying from a blog post.
  5. Wire the form by dropping CustomFields::form()->build() into your existing $schema->components([...]).

The first use case to validate is deliberately boring: add a "Notes" text field and a "Priority" select to a Post, confirm it renders in the create/edit form, saves, and appears in the table. That single loop exercises the trait, the builder API, the field-type registry, and the value persistence layer. If that works, the rest is configuration rather than integration.

Where to look first: the panel plugin registration and the fluent builder API (CustomFields::form(), table(), infolist()). The older CustomFieldsComponent::make() component API is deprecated as of 2.0 — don't build on it.

Implementation Patterns

Builder composition, not field-by-field wiring. The idiomatic pattern is to treat custom fields as one opaque block appended to a schema. Keep your native fields and the custom fields block in separate concerns so you can reason about ordering, and so a future field deletion doesn't churn your resource code. The table and infolist builders are separate calls — a resource typically uses all three.

Model opt-in is per-model and explicit. Only models implementing the contract get the values relation. This is the right granularity: custom fields are usually a tenant-facing feature on a handful of core entities (customers, projects, deals), not on every table.

Field definition happens at runtime, not in code. Admins create fields and sections in the Filament management screen; you write zero migrations for new fields. This changes your release process — "adding a field" becomes an operational task, not a deploy. Plan for that: seed sensible defaults for new tenants, and document which fields are effectively load-bearing for reporting.

Conditional visibility is where the real product value sits. Since v3.1.0 you can gate a field or a whole section on another field's value or on a model attribute. The model-attribute variant is underused — it lets you show a section only when the record is in a given status, which is often closer to what users actually mean than chaining field conditions.

Multi-tenancy should be configured before the first field is created, not after. The package provides tenant isolation and context management; retrofitting isolation onto existing value rows is far more painful than starting with it. Get the tenant context resolution correct in your service provider and test it with two tenants before you ship.

Typed field formats are structured, not strings. Since v3, phone fields store JSON with a country code and email fields store JSON arrays (multi-value). Treat these as structured data: cast/accessor them at the model boundary, and don't assume $model->custom_field_x is a scalar when writing validation or exports.

Record fields are the relational escape hatch. The record field type references other entities with avatar display configuration, and lookups are capped (default 50) with configurable ordering. Use this instead of a "lookup text field" — v3 explicitly removed lookup_type from non-relational field types, so the supported path is the record type.

CSV import/export is built in; lean on it rather than writing a parallel export path, but validate that structured fields (phone, multi-email, record references) round-trip through your expected format before telling users it works.

Extension points worth knowing: custom field types via BaseFieldType with a FieldSchema and automatic discovery; the field-type configurator for enabling/disabling types per deployment; the SYSTEM_SECTIONS feature flag if you want sectionless mode; and soft deactivation of non-system fields and sections rather than hard deletion.

Testing: run the package migrations in your test database, set tenant context explicitly in tests, and assert on the rendered schema for visibility conditions rather than only on stored values — conditional visibility bugs are silent in the data layer.

Gotchas and Tips

Licensing is the first decision, not an afterthought. AGPL-3.0 means your whole application must be open source; closed-source projects need a commercial license. If you're evaluating this for a commercial SaaS, price the commercial license before you write any code against it. This is also the most likely reason a team picks an alternative.

You are pinned to a narrow stack. Filament 5 + Laravel 12/13 + PHP 8.3. Filament's major-version cadence is fast, and this package tracks it with its own majors (2.0 → Filament 4, 3.0 → Filament 5). Budget for coordinated upgrades, or pin relaticle/custom-fields and defer Filament upgrades until the package catches up.

Read minor-version changelogs before upgrading. v3.8.0 changed user-visible behavior: selects only render a search box above an option threshold (default 10, set selects.searchable_threshold to 0 for pre-3.8 behavior). It also changed lookup ordering to model key descending for determinism. If you were relying on "whatever the DB returns," your dropdown contents shifted. Pin versions in composer.json and upgrade deliberately.

Config published before 3.8 has no selects block. Reads pass the default for each missing key, so nothing breaks — but you lose the ability to tune it until you re-publish and merge. Merge the new block in rather than diffing blindly; you'll clobber your own customizations otherwise.

If you override record_lookup.order_column to updated_at, index it. The default model-key ordering is index-backed and effectively free (0.036 ms on a 50k-row tenant per the release notes). updated_at desc without an index was measured at 7.59 ms with a top-N heapsort over 50,004 rows. The package appends the model key as a tiebreaker, so ordering stays deterministic either way.

Upgrading from 2.x requires the migration command. vendor/bin/custom-fields-upgrade rewrites phone and email formats to the new JSON structures. Run it on a copy first and diff a sample of rows — a half-migrated tenant will produce confusing validation failures.

Watch for known-fixed bug classes if you're on an older patch. Duplicate table() invocation in InteractsWithCustomFields (fixed 3.1.5), non-scalar crashes in UniqueCustomFieldValue (3.1.1), $guarded models and MariaDB driver issues (3.1.1), and boolean required validation (3.1.4). If you see double-rendered tables or spurious unique violations on array-backed fields, check your version before debugging your own code.

Table performance is the likely bottleneck. Custom values are stored relationally, so a resource table that renders five custom columns can turn into per-row queries. Eager-load the values relation and verify query count on a seeded table — this rarely shows up in a five-record dev database.

Conditional visibility is evaluated via normalized values, and null handling has been patched more than once. When a condition "doesn't fire," dump the normalized value for the driving field first; the bug is usually a null or a type mismatch, not the condition definition. If you're injecting values into JS for client-side conditions, prefer structured encoding — addslashes was replaced with json_encode for exactly this reason.

Single maintainer, 168 stars, zero dependents. It's a well-maintained-looking package with a real changelog and CI, but the bus factor is one and the ecosystem footprint is small. If custom fields are core to your product rather than a nice-to-have, plan for the possibility of vendoring it or maintaining a fork — which the AGPL makes simpler than the commercial license does.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log