aureuserp/custom-fields
A Laravel package for Aureus ERP that lets you attach dynamic custom fields to modules without schema changes. Define fields, store values, and render them in forms and views to extend core entities with flexible, configurable data.
composer require aureuserp/custom-fields. If you're working from source, add the VCS/path repository entry first and require @dev.php artisan about or config/app.php) and that the package's migrations are picked up — run php artisan migrate before touching any UI.php artisan vendor:publish --provider="AureusERP\CustomFields\CustomFieldsServiceProvider". Typical publishable assets are the config file, migrations, and translations. Publishing migrations is a one-way door — do it before the first production migrate, not after.config/custom-fields.php — table names, supported field types, model/morph configuration.src/Models/ — the custom field definition model and the value-storage model; this tells you whether values live in a JSON column or a separate EAV table.src/Enums/ (or wherever field types are enumerated) — the authoritative list of what you can add.src/Filament/ — the form/table integration, since this package is designed to plug into Filament resources.database/migrations/ — column types and indexes, which dictate what you can query efficiently later.vat_number on Customer. Create the definition (through the admin UI if the package ships one, otherwise via the model), attach it to the customers entity, open a Customer form, save a value, then re-open and confirm it round-trips. That single loop exercises definition storage, schema rendering, validation, persistence, and hydration — if any of those are broken you'll find out immediately rather than three entities deep.Treat definitions as data, not code. The core pattern is that field definitions are rows, not migrations. That means:
updateOrCreate keyed on entity + code) because you will re-run them.code/slug stable and immutable once values exist. Users edit labels freely; the code is the join key between definition and stored value.Attaching fields to a model. Most implementations of this pattern use either a polymorphic relation or an entity/table-name string. Pick one convention and enforce it:
Relation::enforceMorphMap([...])) in a service provider before any definitions are created. Without it, renaming a model class silently orphans every value row. This is the single highest-cost mistake in this pattern.customers isn't hardcoded across a dozen resources.Rendering in Filament. The usual integration is to merge dynamically built components into an existing form(Schema $schema):
Section or Fieldset so they're visually separated from first-class columns.TextColumns with the same labels. Consider toggleable(isToggledHiddenByDefault: true) — custom fields multiply quickly and will wreck table density.Validation. Derive rules from the definition's type and its validation/options payload rather than hand-writing them. A small CustomFieldRuleFactory that maps type → rules (numeric, date, in: from options, max: from config) pays for itself the first time someone adds a "percentage" field and types abc. Apply the same rules in the Filament form and in any API/import path — the package will not enforce them for you at the persistence layer.
Reading and writing values. Encapsulate access in a trait (HasCustomFields) on the consuming models with:
getCustomField(string $code, $default = null)setCustomField(string $code, $value)customFieldValues(): array returning code → value for form hydration.Never let resources touch the value table directly; the moment storage moves from JSON to EAV (or gains a company_id scope) you want one place to change.
Multi-tenancy / company scoping. In an ERP context, check whether definitions and values are scoped per company. If they are, every query path — UI, API, exports, seeders — needs the scope applied. If the package does not scope and you need it, wrapping the definition model in a global scope is far easier before you have production data than after.
Import/export and API surface. Custom fields are a common source of "the CSV import dropped my field" bugs. Map user-facing labels back to codes in one place and reuse it for imports, exports, and API payloads. Validate on import that the definition exists and is active.
php artisan optimize:clear in your local loop and an explicit cache invalidation in the definition's saved/deleted model events.where('custom_fields->vat_number', ...) works but is unindexable on most drivers; MySQL/Postgres generated columns or functional indexes are the escape hatch. If you anticipate filtering or sorting in list views on a custom field, know that constraint before you let users create fifty of them.(bool), (float), Carbon for dates) or you'll get "0" being truthy in Blade and null vs '' inconsistencies between UI save and seeder insert.database/migrations manually.RefreshDatabase and seed the definitions your tests need via a dedicated test seeder rather than the admin UI. Filament resource tests need the panel registered; test the rendering of a custom field once, then test business logic through the trait instead of re-rendering forms everywhere.__() where the definition has a translation key. Mixing the two produces fields that are half-translated depending on where the user looks.0.x-style API surface, breaking changes are plausible between minor releases. Constrain the version in composer.json and read the diff before upgrading — particularly on the migrations and the morph/entity key format.$fillable/handled by the trait, or is it being silently dropped by mass assignment? (4) does the value row exist after save but fail to hydrate on reload — that points at a morph map or cast problem, not a persistence one.How can I help you explore Laravel packages today?