plin-code/laravel-custom-fields
Laravel Custom Fields is a package for adding configurable, model-specific custom fields to Laravel applications. Define field types and validation, assign fields to models, and manage extra metadata or form inputs without schema changes—useful for CMS, CRM, and admin interfaces.
Architecture fit. The package occupies the Eloquent persistence layer: typed custom-field definitions attached to models, plus a filter layer that translates query input into SQL predicates against the stored values. That is a sound fit for Laravel monoliths and modular monoliths where field shape is operator-controlled rather than developer-controlled — CMS content types, per-tenant CRM records, configurable intake forms, admin-built schemas. It is a poor fit for services that need schema-migrated, statically-typed columns, or for anything where another service must query the same data: the filter vocabulary becomes a de facto API contract that only this package understands.
Both v1.0.1 fixes land in the two highest-risk areas of any dynamic-schema package — input coercion on the read path and concurrent writes on the persistence path. That v1.0.0 shipped with a coercion bug that silently returned wrong rows tells us the package is still early on its hardening curve; treat 1.0.x as "usable with tests" rather than "boring."
Integration feasibility. High. composer update, no migration, no config change, patch-level version. The work is not in the install, it is in the behavioral delta for existing clients.
Technical risk.
number filter that previously accepted true/false and matched value 1, and a contains that searched for "1", now throw InvalidFilterValueException (400). Previously-silent, previously-wrong results become hard client-visible failures. Clients that were sending boolean-ish strings to numeric filters will start erroring on deploy — this is a correctness win but an availability regression for those callers, and it should be treated as a contract change, not a bugfix, in the changelog we surface.UniqueConstraintViolationException leak, which is good, but it also means concurrent edits silently clobber with no version guard, no 409, no lost-update signal. If any caller treats a write as "create only," that assumption is now false.InvalidFilterValueException renders as a 400 out of the box (e.g. extends ValidationException) or needs an explicit handler registration determines whether consumers see 400 or 500. This is the single highest-impact unknown.integer rule, which accepts booleans) is a pattern, not an incident.Key questions.
InvalidFilterValueException extend, and what status/body shape do API consumers actually receive without a custom handler?number filter still accept "1", "1.0", 1.5, and locale-formatted decimals? What is the exact accepted-value grammar post-fix?number and contains consume coerced values, and were they audited in #28/#29/#30?ROLLBACK TO SAVEPOINT before continuing, given that a failed statement aborts the subtransaction on PG?updated_at, and trigger any queued or broadcast side effects that the insert path did not?Stack fit. Assumes Laravel 10/11, PHP 8.1+, and MySQL/PostgreSQL/SQLite. The savepoint-plus-upsert pattern is the one place where DB engine differences matter; validate on the engine we actually run, not just the one in CI.
Migration path. No database or config migration. The only migration is at the client boundary: audit request logs for boolean-ish values (true, false, possibly "TRUE", "on", "yes") sent to number filters. Those callers need to send numbers or nothing. If we cannot enumerate them, ship the version and watch the 4xx rate by route and filter name — the errors are now explicit, which makes this cheap.
Compatibility. Pin the version and verify exception rendering in a staging environment against a real HTTP request before rollout. If the exception does not render as 400 by default, register a handler in the same deploy — otherwise a correctness fix presents as 500s.
Sequencing.
number/contains → 400; numeric strings still accepted; concurrent write of the same field on the same host → single row, later value, no exception surfaced.Rollback is trivial at the code level (composer require the prior patch), but only if we catch the issue before clients adapt to the stricter filters — after that, reverting reintroduces silent wrong-row reads, which is worse.
Maintenance. Small surface, patch-sized diffs, good release hygiene (linked PRs, explicit "no migration, no config change"). The governance question is the fork: a single-maintainer package in the query layer of a core record type is a bus-factor risk. Budget for the possibility of pinning at a known-good patch and carrying our own backports.
Support. Expect a short, sharp support bump at deploy: previously working (if incorrect) requests now return 400. Pre-brief support with the error message, the accepted value grammar, and the list of affected filters. The upside is that future field-data bugs surface as errors instead of as quietly wrong reports — which is the failure mode that actually costs money.
Scaling. The unique index constrains writes per (host, field) key, so a hot field on a hot host serializes on that row; the savepoint-and-update conversion trades an exception for lock contention rather than removing contention. Under sustained concurrent writes to the same key, watch for lock waits and deadlocks. Last-write-wins means no lost-update detection — acceptable for "first value wins on create," risky if any consumer treats these records as collaboratively edited.
Failure modes.
Ramp-up. Low for developers — the API is idiomatic Laravel and the diff is small. The real onboarding cost is documenting two things that are invisible in the code: the filter type matrix (what each filter accepts and rejects) and the write-conflict semantics. Put both in the internal package notes; neither is discoverable from reading a controller.
How can I help you explore Laravel packages today?