composer require marshmallow/nova-placeholder
There is no migration and, in the usual case, no config to publish — this is a display field, not a data type. Laravel package discovery registers the service provider; if the provider isn't auto-discovered, add it to config/app.php. The only setup question that matters is whether the package ships compiled JS in dist/ or expects you to run npm run build inside vendor/marshmallow/nova-placeholder — check before you assume the field will render.
Where to look, in this order:
README.md — one fields() example is usually all a field package documents.src/ — the field class (likely NovaPlaceholder.php extends Laravel\Nova\Fields\Field) plus any resolve()/fill() overrides. This package is small enough that reading the entire source takes a few minutes, and you should, because at 0 stars / 0 dependents there is no community knowledge to fall back on.composer.json — specifically the laravel/nova constraint. The last release (Aug 2024) predates Nova 5, so confirm the constraint before installing on a Nova 5 app.resources/js/components/ — the Vue component that actually renders the text; this tells you whether HTML, links, or styling are supported.First use case: a read-only informational row inside a resource form, where Nova's built-in fields don't fit — explaining a workflow before a group of choices, or surfacing a computed value the user cannot edit.
use Marshmallow\NovaPlaceholder\NovaPlaceholder;
public function fields(NovaRequest $request)
{
return [
NovaPlaceholder::make('Notice')
->text('Orders over €500 are routed to manual review.')
->onlyOnForms(),
Currency::make('Amount', 'amount_eur'),
];
}
Verify the exact namespace and setter name (text(), value(), or passing the string as the second make() argument) against src/ — the pattern is identical either way.
Inline help text instead of tooltips. Nova's ->help() is a hover affordance; a placeholder is always visible. Use it for anything a user must read before filling a field (policies, cut-off dates, data-retention notes). Keep the copy short — it renders as a row, not a block.
Derived values shown but not editable. If the value lives on the model, prefer a normal Text field with ->readonly(). Use the placeholder when the value is not a column:
NovaPlaceholder::make('Last synced')
->text(fn () => $this->resource->synced_at?->diffForHumans() ?? 'Never'),
Closures are resolved per-request, so the value stays fresh without duplicating it into a column. This is the pattern that justifies the dependency at all.
Conditional visibility. Since there's no column, you can afford to be liberal with ->canSee() / ->hideWhenCreating():
NovaPlaceholder::make('Review warning')
->text('This account is flagged. Editing requires approval.')
->canSee(fn (NovaRequest $request) => $request->resource?->flagged)
->onlyOnForms(),
Grouping and section breaks. Inside a Panel or a run of fields, a placeholder with an empty value (or a styled variant, if the component supports one) reads as a divider. Cheaper than a custom panel class and it survives Nova upgrades better than raw HTML via Nova::html().
In action modals. Nova Action fields are ordinary fields, so a placeholder works there too — useful for restating what an action will do before a destructive confirmation. Remember the action modal has no resource, so closures referencing $this->resource must be null-safe.
Wrap it, don't scatter it. Because the package is unproven, extend it once and use your class everywhere:
namespace App\Nova\Fields;
class Notice extends \Marshmallow\NovaPlaceholder\NovaPlaceholder
{
public function __construct(string $text)
{
parent::make(' ')->text($text)->onlyOnForms()->asHtml?? ...;
}
}
That single file is your escape hatch: if the dependency breaks or is abandoned, you delete one use statement.
Testing. Field packages like this rarely ship test helpers. In Nova 4/5 feature tests, assert on the field's serialized meta (or just ->assertSee() the copy in a browser test). Don't attempt ->rules() assertions — nothing validates.
$model->fill($request->all()) anywhere in a store/update handler, exclude the field's attribute or you'll write garbage into $fillable checks. Nova itself only fills attributes it recognises on the model, but your own controllers don't get that protection.->onlyOnForms() hides it; it doesn't stop submission. If the component renders an editable element rather than static text, mark it ->readonly() so Nova sends nothing back.->sortable(), ->filterable(), or ->searchable(). These will throw at query time, not at declaration time — the error surfaces on the index page, which is easy to misattribute.fields() also renders as a table cell on index and as a row on detail unless you scope it. Most teams end up with ->onlyOnForms() permanently; if you want it on detail, use ->onlyOnDetail() explicitly and keep the two separate.v-html to allow markup, never feed it user-generated content — this is a stored-XSS vector that looks harmless because "it's just a label". If you need links, check whether the package exposes a safe API first.Nova::script().placeholder chunk), then php artisan nova:publish and php artisan view:clear. Cached Nova assets are the usual culprit after a composer update.meta in the XHR response from /nova-api/.... If your text appears there but not on screen, the bug is in the component; if it doesn't appear, it's in your closure or canSee()."marshmallow/nova-placeholder": "1.*"), and be ready to vendor the single class into app/Nova/Fields/ if it stops tracking Nova releases. MIT license means that's a five-minute operation, which is the strongest argument for using it at all.How can I help you explore Laravel packages today?