Install into your Nova 4 app (peer-installs against laravel/nova, so no version gymnastics):
composer require eom-plus/nova-input-group
Nova auto-discovers the service provider and JS assets — no nova:publish or npm install in the consuming app for the compiled field. Where to look first:
->prepend() / ->append(), or ->prefix() / ->suffix() — the two conventions are interchangeable in most Nova input-group packages, so confirm the actual method names before you code against them).vendor/eom-plus/nova-input-group/src/ to see which Nova field it extends (almost certainly Laravel\Nova\Fields\Text, which is good — you inherit every Text method for free).vendor/eom-plus/nova-input-group/resources/js/components/FormField.vue to see how the prefix/suffix is rendered and what meta keys drive it.First use case: a currency or unit field where a bare input looks unfinished. Wire it up in a Resource's fields():
use EomPlus\NovaInputGroup\InputGroup;
InputGroup::make('Price')
->prepend('$')
->append('.00')
->rules('required', 'numeric', 'min:0'),
That's the whole loop — field renders, stores a plain string exactly like Text, and Nova handles everything else.
Treat this as a presentation wrapper on Text, not a new data type. The submitted value is unchanged, so all your rules, casts, and observers keep working.
Currency without a separate column. Store the raw decimal (1999.00) and use the group for visual context in the form:
InputGroup::make('Price', 'price_cents')
->prepend('$')
->step(0.01) // inherited from Text if supported
->rules('numeric', 'min:0'),
Units on numeric fields. Weight, duration, percentages — the suffix does the labelling so you can drop the help text:
InputGroup::make('Weight')->append('kg')->rules('required', 'numeric'),
InputGroup::make('Discount')->append('%')->rules('between:0,100'),
InputGroup::make('Duration')->append('minutes')->rules('integer'),
URL / handle prefixes. Especially nice for website or social handles where users fumble the scheme:
InputGroup::make('Website')
->prepend('https://')
->rules('url')
->hideFromIndex(),
Then normalise in your mutator/Saving event, because the prefix is not prepended to the stored value.
Consistent resource-wide styling. If half your resources need money fields, wrap it in a helper:
// app/Nova/Fields/Money.php
class Money extends InputGroup
{
public function __construct($name, $attribute = null, $resolveCallback = null)
{
parent::__construct($name, $attribute, $resolveCallback);
$this->prepend('$')->rules('numeric', 'min:0');
}
}
Combining with Nova built-ins. ->help(), ->placeholder(), ->nullable(), ->sortable(), ->default(), ->onlyOnForms(), ->readonly() all still apply if they're inherited from Text. Reach for ->withMeta([...]) only if the package documents extra meta keys.
Detail/index views. Input groups usually render as a plain value on the index and detail pages. If you want the $ visible there too, use ->displayUsing(fn ($value) => '$'.number_format($value, 2)) rather than relying on the prepend.
value sent to Laravel is what the user typed — no stripping, no concatenation. Never validate assuming the prefix is present ('url' will fail on https:// if you actually typed a full URL).composer.json. 3 stars, 0 dependents and a very low ecosystem score means you're on the bleeding edge of an unmaintained-looking package. Use an exact constraint ("eom-plus/nova-input-group": "1.0.*") so a silent composer update in CI doesn't break the field on a Friday deploy.EomPlus\NovaInputGroup\InputGroup, EomPlus\NovaInputGroup\Field, and Operative\NovaInputGroup\InputGroup across versions. An editor autocomplete is faster than guessing.Text + ->displayUsing() or a Stack field.FormField component's field prop — every meta value the package passes through is visible there. If prepend isn't showing, the meta key name is wrong for your version, not a CSS problem. Also check that the compiled dist/js/field.js is being served (browser Network tab) — if it 404s, run php artisan nova:publish and re-clear php artisan view:clear.nova-input-group.php config in this package; if you need more than the README exposes, subclass the field and override jsonSerialize() (PHP side) or copy FormField.vue into your own custom field. For anything truly custom (buttons, dropdown addons, live-calculated suffixes), build a dedicated Nova field — don't bend this one.Text underneath, a quick php artisan tinker store or a Dusk/feature test through the Nova form will confirm the field serialises correctly before you replicate it across ten resources.How can I help you explore Laravel packages today?