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

Readonly On Update Laravel Package

bytelaunch/readonly-on-update

A Laravel package that makes selected Eloquent model attributes read-only after creation. Mark fields as immutable to prevent accidental changes during updates, saves, or mass assignment while still allowing them to be set when the record is first created.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require bytelaunch/readonly-on-update

There is no config file or migration to publish in a package of this shape — it is a Nova field decorator. Before writing any code, open two files in the repo:

  1. README.md — confirm the exact entry point (typically a field class ReadonlyOnUpdate or a Field::readonlyOnUpdate() macro).
  2. src/ and composer.json — check the Nova version constraint (laravel/nova: ^4.0 vs ^5.0) and whether it depends on laravel/nova directly or nova-kit/nova. A mismatch here is the single most common reason the tool silently does nothing.

First use case

The canonical use is protecting identity/immutable columns on the edit form while keeping them editable on create: slug, sku, employee_number, external_id, parent_id, and anything referenced by downstream systems.

In a Nova resource:

use Bytelaunch\ReadonlyOnUpdate\ReadonlyOnUpdate;

public function fields(NovaRequest $request): array
{
    return [
        ReadonlyOnUpdate::make('SKU')
            ->rules('required', 'max:64'),

        // or, if the package exposes a macro instead:
        Text::make('SKU')->readonlyOnUpdate(),
    ];
}

Then navigate to /nova/resources/{resource}/new (editable) and /nova/resources/{resource}/{id}/edit (locked). If the field is editable on both, the package is not detecting the update request — see Gotchas.

Implementation Patterns

Wrap the field, don’t subclass it. The package works by intercepting the field factory call and toggling readonly() based on $request->isMethod('PUT') / PATCH. Keep chaining Nova’s own fluent API (->rules(), ->help(), ->sortable()) after the wrapper so you don’t lose the underlying field type:

ReadonlyOnUpdate::make('Invoice Number')
    ->rules('required', 'alpha_dash', 'max:32')
    ->help('Cannot be changed after creation.')
    ->sortable(),

Apply it selectively by resource lifecycle. For resources where the record can be re-opened for edits after an import (e.g. a nightly sync rewrites the record), a plain "readonly on update" rule is wrong. Gate it:

ReadonlyOnUpdate::make('External ID')
    ->readonly(fn (NovaRequest $request) => $request->resourceId !== null
        && $request->findModelQuery()->first()?->source === 'manual'),

Use it for BelongsTo foreign keys deliberately. Locking owner_id on update is a very common requirement, but note that a readonly BelongsTo renders as text and hides the searchable select. Confirm that’s the UX you want, otherwise use ->canSee() + a policy instead.

Pair it with HasMany/Repeater children. If you have line items you want frozen after the parent is created, the request method is still PUT for the whole payload, so wrapping the child fields works — but only if Nova is posting the repeater in the same request. Verify with an actual save; Nova sometimes splits nested updates.

Guard the API layer too. Readonly is a UI hint, not a security control. Any field you lock should also be excluded from mass assignment at the model level ($fillable), or protected by an UpdateXRequest rule such as Rule::prohibitedIf($record->exists). Treat the package as UX polish, not authorization.

Testing. In a feature/Dusk test, assert both states by hitting the two routes:

$this->actingAs($user)
    ->get("/nova/resources/invoices/{$invoice->id}/edit")
    ->assertDontSee('name="sku"'); // or whatever the readonly render omits

If you can’t easily assert the DOM, drop a ->help(...) string on the field and assert its presence only in the update context.

Gotchas and Tips

  • Method detection is the whole trick. Nova’s update request is PUT to /nova-api/{resource}/{id} (occasionally PATCH from custom tooling, and POST from some Action implementations). If you call the resource through a custom controller or a Lens-driven bulk update, the package may not fire. Confirm with dd($request->method(), $request->path()) inside a temporary field callback.

  • readonly() ≠ excluded from the payload. In Nova, readonly fields are not submitted, so the underlying attribute is simply not updated. That is normally what you want — but if an UpdateResource observer or saving hook expects the value to be present, it will receive null. Check your model events.

  • Validation still runs. Keep required on the field. Because the value isn’t posted on update, a bare required rule on a readonly field is safe (Nova skips validation for absent readonly fields in most versions) — but if you see spurious "field is required" errors, switch to ->rules('sometimes', 'required', ...) or Rule::requiredIf(fn () => $request->isMethod('POST')).

  • Inline creation (BelongsTo “create new” modal) is a POST, so the field stays editable there — which is correct, but it surprises people who expect any nested form to be locked. Test the modal explicitly.

  • Policy conflicts. If a Field::canSee() or authorize() returns false, the wrapper’s readonly() never matters. Order of precedence in Nova is visibility → readonly → disabled. Debug by toggling each independently.

  • Nova 5 / nova-kit forks. The API surface of Field changed marginally between Nova 4 and 5 (notably around resolveUsing and fillUsing). If the wrapper swallows your ->fillUsing() callback, that’s the symptom — pin the package version that matches your Nova major, or fork and adjust the composer.json constraint.

  • Extension point. If the package is thin (likely, given its size), the fastest fix for edge cases is a local macro in a service provider:

    Field::macro('readonlyOnUpdate', function () {
        return $this->readonly(request()->isMethod('PUT') || request()->isMethod('PATCH'));
    });
    

    Two lines replicate the package and remove a dependency if it becomes unmaintained.

  • Debugging checklist when the field stays editable on edit: (1) is the package auto-discovered / is the provider registered if it ships one? (2) is the route actually hitting nova-api and not a custom endpoint? (3) is another field with the same attribute later in fields() overwriting yours? (4) is ->readonly(false) being chained after the wrapper by a trait or fieldsForUpdate() method?

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.
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
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk