antonosipov/nova-live-edit-field
A Laravel Nova field that enables live, inline editing of values directly from the index and detail views, so you can update records without opening a separate form. Note: this package has no stars or score yet, so evaluate its maintenance status before use.
1. Confirm the package is installable. With 0 stars and 0 dependents this is unlikely to be a well-trodden Packagist entry. Check composer.json in the repo first — if it isn't published, add a VCS repository:
"repositories": [
{ "type": "vcs", "url": "https://github.com/tohaosipow/nova4_live_edit_index_field" }
]
composer require antonosipov/nova-live-edit-field
php artisan nova:publish # only if the package ships publishable assets
2. Read three files before writing any code. This package is small enough to audit in ten minutes, and you should:
src/*.php — the Field class. Note what it extends (Text? bare Field?) and which traits it pulls in (Sortable, Nullable, Required).resources/js/components/IndexField.vue — when the edit fires (blur? Enter? every keystroke?) and how it sends the update.dist/js/field.js + nova.mix.js / webpack.mix.js — whether a compiled bundle is committed and which Nova 4 minor it was built against.3. Wire up your first cell. The canonical use case is renaming or re-flagging a row from the index list without a full page navigation:
use Antonosipov\NovaLiveEditField\LiveEdit; // verify namespace in composer.json
public function fields(NovaRequest $request)
{
return [
ID::make()->sortable(),
LiveEdit::make('Name')
->rules('required', 'string', 'max:255')
->onlyOnIndex(),
Text::make('Name')
->onlyOnForms()
->hideFromIndex(),
Text::make('Status')->sortable(),
];
}
Keep a plain Text (or Hidden) field on the form side. Nova builds the set of fillable attributes for the update request from the resource's form fields — an index-only field can be silently dropped from the PATCH payload. Verify this on your first save by watching the nova-api request in the network tab; if the value doesn't persist, that's the cause.
4. First realistic use case: an admin toggling status, fixing a typo in a name, or editing an internal_note across 50 rows in one sitting. That's the sweet spot. Anything requiring validation messaging, side effects, or multi-field consistency belongs in the edit form or a Nova Action.
Wrap it once, reuse everywhere. Rather than repeating rules by hand across fifteen resources, subclass it:
namespace App\Nova\Fields;
use Antonosipov\NovaLiveEditField\LiveEdit;
class InlineStatus extends LiveEdit
{
public function __construct($name, $attribute = null, $resolveCallback = null)
{
parent::__construct($name, $attribute, $resolveCallback);
$this->rules('required', 'in:draft,published,archived')
->onlyOnIndex();
}
}
Now every resource gets identical validation and you have one file to patch when the upstream package rots — which, given the release date, it will.
Normalise the value in fillUsing. Live-edit text fields send strings. If the column is a boolean, an int, or an enum, coerce it before the model sees it:
LiveEdit::make('Is Active')
->fillUsing(function ($request, $model, $attribute) {
$model->{$attribute} = filter_var($request->{$attribute}, FILTER_VALIDATE_BOOLEAN);
}),
Gate it with the policy, not just visibility. Inline edits still hit Nova's standard PUT /nova-api/{resource}/{id}, so the update policy on the model must pass. If it doesn't, the cell will appear editable and fail on save. Hide it up front:
LiveEdit::make('Name')
->canSee(fn ($request) => $request->user()->can('updateName', $this->resource))
Scope validation to the row. ->rules() runs on update, so anything unique must ignore the current model:
->rules(['required', Rule::unique('posts', 'slug')->ignore($request->resourceId)])
Prefer ->updateRules() over ->rules() if the field exposes it — it keeps the rule out of the create path where $request->resourceId is null.
Pair it with an audit trail. Inline edits bypass whatever ceremony you built around the edit form ("reason for change", approval flags, etc.). If that matters, add a model observer or spatie/laravel-activitylog on updated and log the causer from Auth::user().
Don't fight the shape of the data. This field only works for a real, persisted, directly-fillable column. It is not for:
withCount, or appends — the write will 500 or no-opBelongsTo relations (you need a select, not a text input)Testing. The package almost certainly has no tests. Test the contract, not the component: a feature test that PUTs to the Nova update endpoint with the field's payload shape catches upstream breakage on composer update far better than a Dusk test of the cell:
$this->actingAs($admin)
->putJson("/nova-api/posts/{$post->id}", ['name' => 'Renamed'])
->assertOk();
$this->assertSame('Renamed', $post->fresh()->name);
Treat this as vendored code, not a dependency. Zero stars, zero dependents, last release 2023-05-11. Pin it to an exact version ("antonosipov/nova-live-edit-field": "1.0.0"), or better, copy the source into nova-components/live-edit/ and own it. There is almost certainly no Nova 5 path — Nova 5 moved field packages from Mix to Vite, and a committed dist/js/field.js built for early Nova 4 won't survive that jump. Budget for a replacement, not an upgrade.
Silent save failures are the default failure mode. Two independent causes: the attribute isn't in the form fields (see above), or it's not in $fillable / is guarded. Both produce a 200 with no change. Always verify the first edit against the DB, not against the UI.
Validation errors may never surface. Small live-edit components often swallow the 422. Test a deliberately invalid value (empty required field, over-max-length string) and watch the console. If errors vanish, patch IndexField.vue to render error.response.data.errors[field] — otherwise your ->rules() are decorative.
Check the commit trigger for request volume. If the component fires on input rather than blur/Enter, a 60-character note = 60 PATCH requests. Add a ~500ms debounce in the Vue component. Related: Nova's nova-api route group may be rate-limited; rapid inline editing can produce 429s that look like failures.
No optimistic-UI rollback. On a failed save the cell typically keeps showing the new value until a reload. Either force location.reload() on error or reset local state from the response — otherwise users believe a change stuck when it didn't.
No concurrency control. Last write wins, and the index data is stale the moment someone else edits. Fine for low-traffic admin panels; a real problem for anything with two people working the same queue. Consider updated_at-based Rule::unique-style conflict checks or just accept it and document it.
Confirm the cell escapes output. Check IndexField.vue renders with {{ }} / v-text, not v-html. A live-edit field storing free text and rendering it raw is a stored-XSS vector, and you won't find it in a code review of your own app.
After editing, the filter/search results are stale. If a row is filtered by status = draft and you inline-change it to published, the row stays visible until refresh. That's cosmetic but confusing — mention it in your release notes or trigger a refresh on save.
Debugging checklist, in order: network tab for the nova-api PATCH payload → storage/logs/laravel.log for 500s → the model's $fillable → whether the field appears in fieldsForUpdate(). Ninety percent of "it doesn't save" reports stop at step three.
Evaluate the alternatives before committing. Nova has no native inline index editing. If this field proves too thin, the usual escape hatches are outluch/nova-inline-edit, a purpose-built custom field (~40 lines of Vue + a Field class), or simply moving the workflow into a Nova Action with a small modal — which gives you validation messages, authorization, and an audit hook for free.
How can I help you explore Laravel packages today?