marshmallow/button-field
A Laravel package that adds a configurable button field to your forms and admin panels. Define labels, links, actions, styles, and confirmation prompts directly in your field definitions, keeping custom actions declarative and consistent with the rest of your form schema.
The package is a single-purpose Nova field that renders a button inside a resource's index, detail, or form views. There is essentially no public documentation and zero dependents, so treat the source as the spec.
Install it:
composer require marshmallow/button-field
Read these files first, in order:
composer.json — confirm the Nova version constraint (laravel/nova is usually a require-dev/peer dependency for field packages).src/ — find the field class (typically Button.php) and read its component(), fields(), and any withMeta()/setter methods. Those methods are the API.resources/js/ — read the Vue component to see exactly which props (field.text, field.url, field.target, etc.) are consumed.dist/js/field.js — confirm the built asset exists and that a service provider registers it via Nova::script(...). If there's no provider, you'll need to register/compile assets yourself.First use case: a "View on site" / "Open record" button on a Nova resource detail page.
use Marshmallow\ButtonField\Button;
public function fields(NovaRequest $request): array
{
return [
// ...normal fields...
Button::make('Open')
->text('View on site')
->url(fn () => $this->resource->publicUrl())
->target('_blank')
->onlyOnDetail(),
];
}
Verify the exact method names against the source before shipping; the names above are the conventional Nova-field pattern, not documented guarantees.
Sanity check: php artisan nova:check or simply load the resource. If the button doesn't render, the Vue component isn't registered — see Gotchas.
Link-out button (most common). Use a closure for url() so it evaluates per-resource rather than at field-definition time:
Button::make('Stripe')
->text('Open in Stripe')
->url(fn () => "https://dashboard.stripe.com/customers/{$this->resource->stripe_id}")
->target('_blank')
->canSee(fn (NovaRequest $request) => $request->user()->isAdmin()),
Index-only action column. Buttons are non-data fields, so scope them deliberately to avoid empty table columns:
Button::make('Actions')
->onlyOnIndex()
->text('Edit')
->url(fn () => Nova::url("/resources/users/{$this->resource->id}/edit")),
Per-row conditional rendering. Combine canSee() with resource state to make the button a status affordance:
->canSee(fn () => $this->resource->status === 'draft')
Compose with Nova's internal router. If the field's component uses <a href>, in-app links trigger a full page load rather than Nova's SPA navigation. If you control the Vue component, emit a click that calls this.$router.push(...); otherwise accept the reload or use it only for external URLs.
Reuse via a helper method. Because there's no config surface, wrap construction in a private method on the resource or a trait so every resource gets consistent text/icon/target behavior:
protected function externalButton(string $label, string $url): Button
{
return Button::make($label)
->text($label)
->url($url)
->target('_blank')
->onlyOnDetail();
}
Passing extra data. Anything not exposed as a fluent setter can go through ->withMeta([...]), then be read in the Vue component as field.<key>. This is the package's real extension point — use it instead of forking if you need an icon, tooltip, or CSS class.
Form-view buttons. A field placed in a create/update form sits inside the <form>. If the Vue component renders a native <button> without type="button", it will submit the form. If you need a form-context button (e.g. "Generate slug"), confirm the type and @click.prevent in the component before relying on it.
Nova::script(). If the button renders as an unknown/blank component, check for a service provider in src/ and that dist/js/field.js is present. Rebuild with npm install && npm run prod if missing.laravel/nova constraint supports your installed Nova 4.x/5.x; field components built against older Nova's Vue/Toolkit versions can silently fail on newer majors.fillable/validation and are not sortable or searchable. Don't add ->sortable() or ->rules() and expect behavior.withMeta() values must be JSON-serializable. Closures passed into meta (rather than into dedicated setters) will serialize to nothing. Resolve values eagerly or use url(fn () => ...) only if the setter itself accepts callables.canSee(). There's no policy integration; a visible button with a sensitive URL leaks the URL to anyone who can view the resource. Gate both visibility and the URL generation.composer.json, and vendor/audit the Vue component if it interpolates resource data into href or v-html (XSS surface).onlyOnIndex/onlyOnDetail), canSee(), then whether the Vue component name returned by component() matches the registered name — a mismatch fails silently.resources/js/components/Field.vue locally (after forking or path-repository-ing the package) and consume props via withMeta(). There is no config file or event system to hook into.How can I help you explore Laravel packages today?