Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Select Or Custom Laravel Package

patriziotomato/select-or-custom

A Laravel form field that lets users pick from a predefined list or enter their own custom value. Ideal for select inputs that need an "other" option, with simple setup for Blade forms and Filament-style components.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require patriziotomato/select-or-custom

The package has zero stars and a single 2023 release, so if Packagist resolution fails, pin it via a VCS repository entry and a commit hash rather than a floating constraint:

"repositories": [{ "type": "vcs", "url": "https://github.com/patriziotomato/select-or-text" }],
"require": { "patriziotomato/select-or-custom": "dev-master" }

Verify compatibility before anything else. Open the package composer.json and check the nova constraint and whether dist/js/field.js is compiled against Vue 2 or Vue 3. A February 2023 release most likely targets Nova 4; treat Nova 5 as untested until you've clicked through a form yourself.

Minimal use

use Patriziotomato\SelectOrCustom\SelectOrCustom;

SelectOrCustom::make('Industry')
    ->options([
        'saas'     => 'SaaS',
        'fintech'  => 'Fintech',
        'retail'   => 'Retail',
    ])
    ->rules('required', 'max:255'),

Back it with a plain string column — this field stores one scalar value, not a pair of {selected, custom} columns:

$table->string('industry')->nullable();

Where to look first, in this order

  1. src/ — the Field subclass. Read every public method; this is the entire server-side API surface and it's small. Note the exact method name for enabling the free-text mode (it is typically a flag or a sentinel option like ->customOption() / ->allowCustom()), because that is the one call you cannot guess.
  2. resources/js/components/*.vue — tells you when the text input appears (sentinel option selected? separate toggle? always visible alongside the select?) and what value gets emitted to the server.
  3. dist/js/field.js — the compiled bundle Nova actually loads. If you patch anything under resources/, you must rebuild or your change is invisible.

First use case worth shipping: a "Lead source" or "Referral channel" field. The list covers the common 90%, and the long tail (a partner's name, a conference, "heard about us on a podcast") gets typed in. You get clean grouping for reporting on the top options without blocking the sales team on a schema change every time a new channel appears.

Implementation Patterns

Enum/constant-backed options instead of inline arrays

SelectOrCustom::make('Source')
    ->options(LeadSource::options())   // ['web' => 'Web', ...]
    ->rules('required', 'max:255')
    ->help('Pick one, or type the channel name if it is not listed'),

This keeps the canonical list in one place so filters, exports, and validation all agree.

Validate "either a known key or free text" properly. A bare required will not stop ' ' or a typo of a valid key. Use a rule that accepts known keys or enforces your free-text rules:

->rules('required', 'max:255', new KnownOptionOrFreeText(LeadSource::keys()))
public function passes($attribute, $value): bool
{
    return in_array($value, $this->keys, true)
        || ($value !== '' && trim($value) !== '');
}

If you need to distinguish "was it custom?" later, encode it on write (e.g. prefix custom values) or add a companion ->onlyOnDetail() computed field — do not assume the package stores a flag.

Index and detail rendering. The raw stored string will usually be shown as-is. Normalise it at the field level so the index column shows a human label for known keys:

->displayUsing(fn ($value) => LeadSource::label($value) ?? $value)
    ->onlyOnIndex(),

Keep ->sortable() on a sibling read-only field or on the column itself — sorting by the raw string is fine, sorting by label is not.

Nova filters. Filters see the raw value, so a "Source" filter must cover both the enumerated keys and a text search for custom entries:

public function apply(Request $request, $query, $value)
{
    return $query->where('source', $value);
}

Add a separate Text filter (or a where('source', 'like', "%{$q}%") branch) for the custom values; a Select filter alone will silently hide them.

Cascading / dependent fields. Because this is a custom component, do not assume ->dependsOn() works — it requires the component to subscribe to Nova's field-change events. The reliable pattern is to compute options server-side in fields():

SelectOrCustom::make('Region')->options(Region::options($this->country)),

and place the field on a page that reloads. This is more requests but it never breaks on a Nova upgrade.

Wrap it once. Since the field is unmaintained, subclass it in your app and use your class everywhere:

namespace App\Nova\Fields;

class SelectOrCustom extends \Patriziotomato\SelectOrCustom\SelectOrCustom
{
    public function __construct($name, $attribute = null, $resolveCallback = null)
    {
        parent::__construct($name, $attribute, $resolveCallback);
        $this->rules('max:255');
    }
}

That single indirection means a Nova 5 break, a fork, or a swap to a maintained alternative is a one-file change instead of a grep across forty resources.

Reusable resource test. Assert both branches survive a Nova request:

Nova::actingAs($user)
    ->resource('leads')
    ->create(['source' => 'web', 'name' => 'Acme'])
    ->assertStatus(201);

Then repeat with source => 'A conference in Berlin'. The second case is the one that catches whitespace trimming and max length regressions.

Gotchas and Tips

  • Key vs. label storage is the #1 unknown. Read the Vue component and confirm what the select emits. If it emits the label and you later rename labels, your stored data silently changes meaning and your filter breaks. Prefer key storage; add ->resolveUsing()/->displayUsing() to translate for the UI.

  • Null vs. empty string. Custom components frequently emit '' when nothing is chosen, which defeats ->nullable() and unique indexes. Normalise in the model or with ->fillUsing():

    ->fillUsing(function ($request, $model, $attribute) {
        $model->{$attribute} = $request->input($attribute) ?: null;
    })
    
  • Whitespace-only free text. ' ' passes required. Trim on the server, not just in the browser — the browser-side trim in these packages is inconsistent.

  • searchable() and sortable() are inherited but not necessarily honoured. They live on the base Field class, so they compile, but searchability requires the component to implement Nova's search behaviour. Test with a real Nova search request; if it doesn't work, add a Text field for search or accept column-level search only.

  • No maintenance is a real constraint. Zero stars, one release, no dependents means no community answers on Stack Overflow and no upstream fixes. Budget a fork: vendor the repo, commit dist/js/field.js, and document the patch inside your app. Never rely on composer update pulling a fix.

  • Asset changes need a rebuild. Editing resources/js without running the package's build (check package.json for a dev/prod script and confirm the output path matches what Nova::script() points at) gives you a stale bundle and a very confusing debugging session. Clear the browser cache and, if you have one, your Nova asset cache.

  • Nova version drift. Nova 4 → 5 changed asset loading and Vue tooling. If forms render blank or the console shows a Vue mount error, that is the cause — not your resource code. Fall back to a Select + Text pair with a client-side toggle before you burn a day patching a 2023 bundle.

  • Debugging the wire format. Open the browser network tab on save and read the PUT /nova-api/.../ payload. That tells you the exact key and value the component emits — which resolves both the key-vs-label question and the null-vs-empty question in one glance. It's the fastest debugging move with this package.

  • Extension point when the component misbehaves: subclass the field and override component() to point at your own compiled Vue component. That gives you full control of the UI while keeping the field's PHP API — cheaper than forking the whole package.

  • Length limits. Custom text bypasses any enum constraint, so the underlying column must be sized (or TEXT) deliberately. A VARCHAR(20) chosen for short keys will start rejecting real-world custom values in production.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus