Install, register, and configure Core
This page is for developers wiring the free Core package into a Laravel + Filament host. Admin field-creation steps are in Workflows. File uploads and Spatie Media are Pro.
Requirements
- PHP 8.2+
- Laravel 11+ (
illuminate/supportandilluminate/database≥ 11) - Filament
^3.3|^4.0|^5.0— the host starter uses Filament v5 Schema APIs - Optional:
bezhansalleh/filament-shield/ Spatie Permission formanage_custom_fields - Pro only:
spatie/laravel-medialibraryandfilament/spatie-laravel-media-library-pluginfor File fields
Composer package name: spiggle/dynamic-fields-core. PHP namespace: Spiggle\DynamicFields. Filament plugin id: spiggle-dynamic-fields. Public source: skillbobby/Spiggle-Dynamic-Fields-Core.
Install Core
Prefer Packagist when the package is listed:
composer require spiggle/dynamic-fields-core
Or a GitHub VCS repository:
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/skillbobby/Spiggle-Dynamic-Fields-Core"
}
],
"require": {
"spiggle/dynamic-fields-core": "^1.2"
}
}
Path repository (this starter):
{
"repositories": [
{
"type": "path",
"url": "packages/spiggle-dynamic-fields-core",
"options": { "symlink": true }
}
],
"require": {
"spiggle/dynamic-fields-core": "@dev"
}
}
The service provider and CustomFieldMapper facade alias auto-register via Composer extra.laravel. Download also via GitHub (clone or Releases).
Publish config and migrate
php artisan vendor:publish --tag=dynamic-fields-config
php artisan vendor:publish --tag=dynamic-fields-migrations
php artisan migrate
Schema::hasTable, so re-running is safe.
Do not add a users.custom_fields JSON column. Values belong in custom_field_values. File bytes belong to Spatie Media Library only when Pro is installed.
Register the Filament plugin
In the host panel provider (starter: app/Providers/Filament/AdminPanelProvider.php):
use Spiggle\DynamicFields\Filament\DynamicFieldsPlugin;
$panel->plugin(DynamicFieldsPlugin::make());
That registers the Custom Fields resource. Navigation group defaults to System, icon heroicon-o-adjustments-horizontal, sort 90 — all overridable in config.
Host model
The starter User already implements this pattern:
use Spiggle\DynamicFields\Traits\HasCustomFields;
class User extends Authenticatable
{
use HasCustomFields;
}
HasCustomFieldsis enough for every Core type.- File fields are Pro: they require
HasMedia+HasCustomFieldMediafromspiggle/dynamic-fields-pro.
Mass assignment
Filament nested state uses the key custom_fields (configurable). Add it to fillable so the mutator can queue values:
#[Fillable(['name', 'email', 'password', 'custom_fields'])]
Alternatively, in a page hook: $record->syncCustomFields($data['custom_fields'] ?? []).
Filament resource usage
use Filament\Schemas\Components\Section;
use Spiggle\DynamicFields\Facades\CustomFieldMapper;
Section::make('Custom Profile Data')
->schema(CustomFieldMapper::makeFormFields(User::class));
User::query()->withCustomFields()
...CustomFieldMapper::makeTableColumns(User::class)
CustomFieldMapper::makeInfolistEntries(User::class)
Always eager-load with withCustomFields() on list queries to avoid N+1. The starter Users table already does this.
Mapper methods return an empty array if something throws (missing tables during first boot, etc.), so the host resource still renders.
Permissions / Shield
CustomFieldResource::canAccess() checks:
- The user is authenticated on the panel.
- Spatie permission
manage_custom_fields(config:dynamic-fields.permissions.manage), or rolesuper_admin. - If Shield has not created that permission yet, authenticated panel users keep access so a fresh install is not locked out.
Config also lists view_custom_fields. The resource currently gates on the manage permission only.
Typical Shield generate (after the plugin is registered):
php artisan shield:generate --all
Then assign manage_custom_fields to the roles that should see Custom Fields.
Artisan commands
| Command | What it does |
|---|---|
php artisan dynamic-fields:seed-user-fields |
Seeds 23 standard User profile fields (identity, contact, work, address, social, preferences). Options: --model=, --fresh. |
php artisan dynamic-fields:seed-extended-fields |
Pro. Seeds Skills (multi-select), Interests (tags), Beta Access (toggle), Profile Documents (file), About (rich textarea), Favorite Team (select). |
php artisan dynamic-fields:verify |
Checks tables, mapper mapping, persistence, file media, collapsed repeater labels, and create-page redirect. Option: --model=. |
php artisan dynamic-fields:export |
Writes definitions + options to JSON. Options: --model=, --path= (default storage/app/custom-fields-export.json). |
php artisan dynamic-fields:import {path} |
Import that JSON. Option: --fresh deletes existing fields for the models in the file first. |
A seeder class Spiggle\DynamicFields\Database\Seeders\StandardUserFieldsSeeder calls the user-fields command.
alternate_email, not a competing email custom field, so it does not collide with users.email.
Configuration
Published file: config/dynamic-fields.php.
| Key | Default | Purpose |
|---|---|---|
tables.fields / options / values | custom_fields, custom_field_options, custom_field_values | Table names |
cache.enabled / ttl / prefix | true, 3600, spiggle_dynamic_fields | Memoize definitions per target model |
append_to_json | true | Append custom_fields on model JSON/array |
model_discovery.paths | ['app/Models'] | Scan for Target Model dropdown |
model_discovery.exclude | [] | FQCN list to hide |
navigation.group / icon / sort | System, adjustments-horizontal, 90 | Filament nav |
permissions.manage | manage_custom_fields | Resource access |
permissions.view | view_custom_fields | Reserved name |
field_types | 15 types | Labels in the type select |
form_state_key | custom_fields | Nested Filament state path |
upsell.checkout_url | env LEMON_SQUEEZY_CHECKOUT_URL | Buy Pro URL shown in the Field Manager upsell. Empty = no checkout button (use GitHub Issues on Core). |
tags.colors | Filament theme names | Used by Pro tags badges; listed in Core config for the upsell UI |
Trait / mapper API
| Method | Purpose |
|---|---|
customFieldValues() | Morph-many to value rows |
scopeWithCustomFields() | Eager-load values, field, and options |
getCustomFieldValue(string $name) | Cast value; File fields (Pro) return media metadata from Spatie |
setCustomFieldValue(string $name, mixed $value) | Upsert one value; fires CustomFieldValueSaved |
syncCustomFields(array $values) | Set many at once |
getCustomFieldsAttribute() / mutator | JSON append + queue Filament nested state until saved |
CustomFieldMapper::makeFormFields($class) | Filament inputs |
CustomFieldMapper::makeTableColumns($class) | Table columns (toggleable, searchable via EAV) |
CustomFieldMapper::makeInfolistEntries($class) | View/infolist entries |
CustomField::cloneDefinition(?$newName) | Copy definition + options |