Usage Core v1.2.0

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

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
Migrations auto-load Package migrations also load from the service provider. Publishing them is optional. They are guarded with 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;
}

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:

  1. The user is authenticated on the panel.
  2. Spatie permission manage_custom_fields (config: dynamic-fields.permissions.manage), or role super_admin.
  3. 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

CommandWhat 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.

Login email vs custom email Standard seeding adds alternate_email, not a competing email custom field, so it does not collide with users.email.

Configuration

Published file: config/dynamic-fields.php.

KeyDefaultPurpose
tables.fields / options / valuescustom_fields, custom_field_options, custom_field_valuesTable names
cache.enabled / ttl / prefixtrue, 3600, spiggle_dynamic_fieldsMemoize definitions per target model
append_to_jsontrueAppend 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 / sortSystem, adjustments-horizontal, 90Filament nav
permissions.managemanage_custom_fieldsResource access
permissions.viewview_custom_fieldsReserved name
field_types15 typesLabels in the type select
form_state_keycustom_fieldsNested Filament state path
upsell.checkout_urlenv LEMON_SQUEEZY_CHECKOUT_URLBuy Pro URL shown in the Field Manager upsell. Empty = no checkout button (use GitHub Issues on Core).
tags.colorsFilament theme namesUsed by Pro tags badges; listed in Core config for the upsell UI

Trait / mapper API

MethodPurpose
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() / mutatorJSON 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