›
byrcsc/laravel-custom-fields · 1.x
Define a field on an Eloquent model, write a value, read it back, and filter records by it.
The Customer model already stores the details every customer shares, such as
their name and email address. This guide adds an account tier without changing
the customers table. It then writes, reads, validates, and filters that field.
It assumes the package is installed and HasCustomFields is
on the model.
use App\Models\Customer;
use ByRcsc\LaravelCustomFields\Enums\FieldType;
use ByRcsc\LaravelCustomFields\Facades\CustomFields;
CustomFields::define(Customer::class, [
'key' => 'account_tier',
'type' => FieldType::Select,
'options' => ['standard', 'premium'],
'label' => 'Account tier',
'default' => 'standard',
'rules' => ['required' => true],
]);Run this wherever your application decides its fields exist: a seeder, a
console command, or the controller behind an admin screen. It writes one row to
custom_field_definitions and returns the CustomFieldDefinition model.
The key is what every later call names the field by. It is lowercase, starts
with a letter, and continues with letters, digits, or underscores. label is
what validation messages call the field, and defaults to the key with its
underscores replaced, so due_date becomes Due date.
Defining the same key twice on the same model, for the same tenant, throws
InvalidDefinitionException. So does a select without options, an unknown
type, or a misspelled attribute name. See defining
fields.
$customer = Customer::query()->findOrFail(1);
$customer->setCustomField('account_tier', 'premium');The write persists immediately: there is no save() to follow it with. It also
validates against the field's rules first. Writing 'enterprise' would throw
Laravel's ValidationException because the field does not offer that option.
$customer->getCustomField('account_tier'); // 'premium'Or read every field the current tenant can see, keyed by key:
$customer->customFields; // Collection: ['account_tier' => 'premium']
$customer->customFields->all(); // ['account_tier' => 'premium']customFields is an Illuminate\Support\Collection, ordered by each field's
sort_order and then its key.
Values come back as the PHP type the field promises. A number is an int or
a float, a date is a Carbon instance, a multi_select is a list of
strings. See field types and storage.
A customer that has never set the field reads the definition's default:
$fresh = Customer::query()->create([
'name' => 'Ada Lovelace',
'email' => 'ada@example.com',
]);
$fresh->getCustomField('account_tier'); // 'standard'No value row exists for that customer. Defaults are resolved when you read, never written as rows. Changing a definition's default therefore changes what every unset record returns.
Writing null clears a field and returns it to the default:
$customer->setCustomField('account_tier', null);
$customer->getCustomField('account_tier'); // 'standard'Customer::query()->whereCustomField('account_tier', 'premium')->get();
Customer::query()->whereCustomFieldIn('account_tier', ['standard', 'premium'])->get();Both compile to a comparison on one typed column inside an exists clause, and both compose with everything else on the query:
Customer::query()
->where('active', true)
->whereCustomField('account_tier', 'premium')
->orderByDesc('created_at')
->get();The generator hands a form request its rules array:
use App\Models\Customer;
use ByRcsc\LaravelCustomFields\Facades\CustomFields;
use Illuminate\Foundation\Http\FormRequest;
final class StoreCustomerRequest extends FormRequest
{
public function rules(): array
{
return CustomFields::rulesFor(Customer::class, prefix: 'custom_fields');
}
public function attributes(): array
{
return CustomFields::attributeNamesFor(Customer::class, prefix: 'custom_fields');
}
}prefix nests the keys for a form posting its custom fields under one name, so
the rules above are keyed custom_fields.account_tier. Without attributes(), a
failure message names the key instead of the label.
Reading custom fields on a page of records costs one extra query for the whole page, as long as you eager load the relation:
Customer::query()->with('customFieldValues')->paginate();Leave it out and each record fetches its own values. See queries and eager loading.
define() accepts.unique
guarantees.