lynndigital/select-or-custom
Laravel package that adds a select-or-custom form field: users can pick from a predefined list of options or type their own value instead, with validation covering both the chosen option and any custom text they enter.
lynndigital/select-or-custom (published from the 0x15f/select-or-text repo) is a Laravel Nova form field that behaves like a Select but lets the user type a value that isn't in the options list. It's aimed at the common "pick from these categories, or add a new one on the fly" case — source/channel fields, tags, department names, referral sources — without forcing a second free-text field into the form.
Minimal steps:
composer require lynndigital/select-or-customcomposer.json extra.laravel.providers block to config/app.php.npm install && npm run prod inside the package (or copy its resources/js component into your own field) and php artisan nova:publish.Select:use Lynndigital\SelectOrCustom\SelectOrCustom; // confirm exact class in src/ or README
SelectOrCustom::make('Source')
->options([
'organic' => 'Organic',
'referral' => 'Referral',
'paid' => 'Paid',
])
->rules('required', 'max:255'),
Where to look first: the README for the field's actual API, then src/ for the field class (especially how it hydrates/fills and what it writes back), then resources/js/components/Field.vue for how the custom value is captured and submitted. The Vue component is where most surprises live (trimming, empty-string handling, whether typing a value that matches an option key is treated as the option or the custom string).
First realistic use case: a single varchar column that today is populated by a Select, where ops occasionally needs an option you didn't anticipate. You get the constrained UX most of the time and an escape hatch without a schema change.
string (or nullable string) and store the chosen key or the typed text in the same place. Don't try to model this as a belongs-to-many.->options(config('crm.sources'))
Model::distinct()->pluck('source') so previous custom entries become selectable next time. Sort and dedupe; watch for case-only duplicates.->rules('required', 'max:255') and, if you want a controlled vocabulary, a Rule::in(array_keys($options)) — but only when you actually want to forbid custom values. Most of the time you don't, so skip in: and rely on max/string.->displayUsing(fn ($value) => $options[$value] ?? $value) so custom values render as themselves and known options render as friendly labels.DependsOn like other fields; wire ->dependsOn(['country'], ...) if the option list is derived from another field, but confirm the component forwards the dependency (it may not, given the package's size — test before relying on it).->fillUsing(...), trim whitespace and optionally Str::slug/lowercase custom entries so "Paid " and "paid" don't both end up in the column. Do this centrally; the field itself likely doesn't.composer.json, and be prepared to fork it into your own packages/ (or a private VCS repo) the first time a Nova upgrade breaks it. Budget an afternoon for that; it's a small field, so the fork is cheap.lynndigital/select-or-custom but the repo is 0x15f/select-or-text. The namespace/class name may not match the package name — verify in src/ before copy-pasting use statements from the README, and check composer.json for the actual autoload.psr-4 mapping.paid is already an option key, you'll get the same stored string either way — usually fine, but it makes "was this a curated option or a custom entry?" unanswerable later. If that distinction matters, store a parallel flag column.in: validation silently breaks the feature. The most common self-inflicted bug: adding Rule::in($options) "for safety" and then wondering why custom entries fail validation. Audit any request-level FormRequest rules on the same column too.'' rather than null. Add ->nullable() and normalize in a mutator if downstream code checks is_null.->searchable() on a text column, and test WHERE filters against both curated and custom values. Sorting by this column will be alphabetical on the raw string, not on display order.dist/js/field.js is missing or stale, the field renders as a plain text input or nothing at all. Check public/vendor / vendor/.../dist and rebuild. Browser console errors after a Nova upgrade usually mean the Vue component targets an older Nova mix setup.varchar, the field may submit "3" (string) while your model expects 3. Cast consistently with $casts and check the resolved value before it hits the database.fillUsing logic rather than patching vendor/. Keep your subclass in app/Nova/Fields/ so a composer update can't clobber it.nova:check/the form at all, (2) inspect the network payload on save to see whether the custom value is sent, (3) dd() in the field's resolve/fill methods to confirm what's written, (4) check the Vue component's modelValue watcher if the value doesn't repopulate on edit.How can I help you explore Laravel packages today?