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

Nova Wizard Laravel Package

zareismail/nova-wizard

A Laravel Nova package for building multi-step wizards that guide users through resource creation and complex forms. Define ordered steps, control validation and navigation, and deliver a cleaner guided workflow inside the Nova admin panel.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're adopting. zareismail/nova-wizard is a presentation/navigation layer over Nova's field system, not a workflow engine. It takes the flat array your Resource::fields() normally returns and slices it into ordered, labelled steps rendered inside Nova's existing form with next/back/finish controls. store()/update(), policies, actions, lenses and search all keep working exactly as before. Internalise that framing before you write a line of code — it explains every constraint below.

Prerequisites and the one thing to check first.

  • A licensed Laravel Nova install. This package is meaningless without Nova.
  • A pinned laravel/nova and php constraint in your app.

Before you commit anything, open the package's composer.json and check the laravel/nova constraint. Last release is 2023-08-11, so assume this is a Nova 4-era (Vue 3) package. On Nova 5, expect to fork or vendor-patch. Also check whether dist/ contains committed compiled assets or whether you must run a Node build with a matching Node/Vite toolchain.

Install and register.

composer require zareismail/nova-wizard

At 14 stars the source is the spec, so confirm in src/ whether the package exposes a Nova tool or just a base wizard class/field you consume from your resource:

// app/Nova/NovaServiceProvider.php — only if a tool is exposed
public function tools()
{
    return [
        new \Zareismail\NovaWizard\NovaWizard,
    ];
}

If it's a class you subclass, skip tool registration entirely and use it directly (see Implementation Patterns).

Where to look first, in order.

  1. src/ — the wizard class/contract. How are steps declared? What does a step return? Is submission one POST at the end or per-step? Is validation server-side or client-side only?
  2. resources/js/ — the Vue component. Read it before fighting the UI. The props/events available (validateStep, canAdvance, formData, step index) determine whether state forwarding is even possible.
  3. composer.json + dist/ — Nova/PHP constraints and whether you need a build step.
  4. README/examples — treat as a hint, not documentation.

Your first use case (do this, in a scratch resource). Rebuild one existing create form as a two-step wizard. The canonical fit is a User or Order create form that's grown past the point of comfort — identity → address → billing → review. A two-step spike surfaces ~90% of the real questions (state, validation timing, file uploads, refresh behaviour, payload shape) in an afternoon. Do not wire this into a production resource until the spike submits correctly end to end.


Implementation Patterns

Pattern 1 — Decorate the resource, don't replace it. Keep the resource intact and only swap the field array on the create/update path. Index, detail, lenses and actions stay untouched, so your policies and search keep working:

// app/Nova/Order.php
public function fields(NovaRequest $request)
{
    if ($request->isCreateOrAttachRequest() || $request->isUpdateOrUpdateAttachRequest()) {
        return $this->wizardFields($request);
    }

    return parent::fields($request); // or your normal flat array
}

$request->isCreateOrAttachRequest() / isUpdateOrUpdateAttachRequest() are the right guards — they keep the wizard out of index/detail rendering.

Pattern 2 — Keep steps as small, individually testable PHP methods. Whether the package expects an array of step definitions or an overridden method, the pattern that ages well is one method per step returning a plain Nova field array:

protected function stepIdentity(NovaRequest $request): array
{
    return [
        Text::make('Name')->rules('required', 'max:255'),
        Email::make('Email')->rules('required', 'email', 'unique:users,email,{{resourceId}}'),
    ];
}

protected function stepBilling(NovaRequest $request): array
{
    return [
        Country::make('Country')->rules('required'),
        Text::make('VAT Number')->rules('nullable', 'string', 'max:64'),
    ];
}

Why: you can unit-test each step's fields independently, you can conditionally return different fields per step based on $request or the authenticated user, and you keep conditional branching in plain PHP rather than in a JS config array.

Pattern 3 — One submission, one ruleset. Prefer single-POST at the end over per-step server round-trips. A single POST means:

  • One store()/update() call, so Nova File, Image and pseudo-fields behave normally.
  • One FormRequest enforcing the full ruleset server-side.
  • No server-round-trip surface to break when Nova upgrades.

Treat any per-step client validation the wizard does as UX, not security. Enforce everything again in a FormRequest or on the model:

// app/Http/Requests/StoreOrderRequest.php
public function rules(): array
{
    return [
        'name'        => ['required', 'string', 'max:255'],
        'email'       => ['required', 'email', Rule::unique('users', 'email')],
        'country'     => ['required', 'string', 'size:2'],
        'vat_number'  => ['nullable', 'string', 'max:64'],
    ];
}

If a step's fields are genuinely optional until later, scope the client-side rules with sometimes/nullable so the wizard doesn't demand everything at once, but keep the final FormRequest authoritative.

Pattern 4 — Multi-model flows: collect in the wizard, write in a transaction. For "create User + Profile + Subscription" style flows, gather all fields in the wizard and do the writes once on submit:

DB::transaction(function () use ($request) {
    $user = User::create($request->only(['name', 'email']));
    $user->profile()->create($request->only(['country', 'vat_number']));
    $user->subscription()->create(['plan' => $request->input('plan')]);

    return $user;
});

Never persist an intermediate model per step. If a user abandons the flow at step 3, you've orphaned rows and now need cleanup jobs. One transaction or nothing.

Pattern 5 — Non-CRUD flows belong on a Nova tool page or lens. Onboarding questionnaires, CSV imports, "configure the integration" assistants — these have no meaningful index/detail. Wrap the wizard in a Nova tool page posting to a custom controller rather than shoehorning it into a resource's create form. Keeps fields() honest and keeps the wizard out of Nova's resource routing assumptions.

Pattern 6 — Feature-flag the rollout. Gate the wizard behind a per-resource toggle or Nova tool flag so a single resource can be flipped back to the flat form without a deploy:

protected function wizardEnabled(): bool
{
    return Nova::whenServing(fn () => Feature::active('nova-wizard:orders'))
        ?? false; // or a config/DB setting
}

Pattern 7 — Pin and document. Exact version in composer.lock, a runbook entry covering install tag, asset publish tag, upgrade smoke test, and rollback (remove package → restore prior fields()). Assign a named owner. Admin wizard throughput is low-concurrency, so scaling isn't the concern; upgrade compatibility is.


Gotchas and Tips

Version drift is the whole risk. A 2023-08-11 release on a fast-moving admin framework means the first Nova major upgrade is your decision point: patch forward, fork, or replace. Smoke-test the wizard after every Nova/Laravel/PHP bump. Verify dist/ ships compiled assets — if it doesn't, you're maintaining a Node/Vite toolchain for a 14-star package, which usually tips the build-vs-fork decision.

"All steps validate at once." This is the most common first-week bug. If Nova's rules() on each field fire on every submit, later steps' required fields block earlier ones. Fix by scoping rules to the active step (if the package supports it) or using sometimes/nullable client-side and holding the full ruleset for the final FormRequest. Then verify the final request actually enforces it — test with curl or a deliberately invalid payload, not just by clicking through the UI.

Client-only validation is bypassable. If per-step validation lives only in Vue, a crafted POST walks straight past it. Confirm server-side enforcement with a test that submits an incomplete wizard payload and asserts a 422. Treat the wizard's step gating as a courtesy to the operator, never as authorization or validation.

Authorization gaps on wizard-submitted attributes. Nova applies policies at the resource level. If your wizard collects attributes outside the base resource (extra profile fields, plan selection), confirm they can't be used to escalate — e.g. a user without updateSubscription permission submitting a plan field. Guard by filtering the request payload in the controller or adding a dedicated policy check, not by hiding the field in the wizard.

State loss on refresh/back. Assume wizard state is client-only unless you find otherwise in src//resources/js/. For short wizards (2–3 steps) that's fine — accept it. For long ones, either persist drafts to session/DB (the heavier, fork-prone path) or keep the wizard short. Test: hit refresh mid-flow, hit browser back from step 3 to step 1, then submit. Record what happens; put it in the runbook.

File uploads and orphans. With single-POST, File/Image fields behave normally — that's the argument for single-POST. If you go per-step or persist drafts, watch for orphaned uploads: add transactional writes, idempotency keys, and a scheduled cleanup for unreferenced files. Test uploading on step 1, abandoning at step 3, and confirming nothing dangles.

"The wizard doesn't submit" is almost always payload or assets. Debug in this order:

  1. Open DevTools → Network → inspect the actual POST payload. Log the serialized payload server-side before it hits the model (Log::debug($request->all()) in the controller). Most failures are field name mismatches or a step that never contributed its fields.
  2. Check Vue props/state in the component inspector — is formData populated with all steps, or only the visible one?
  3. Check the asset pipeline: is the compiled JS actually served? Nova's publish tag / nova:publish / Vite manifest mismatch bites hardest after upgrades.

Don't fight the Vue component blind. Read resources/js/ before changing the UI. If the component doesn't expose a prop or event you need (e.g. a hook before advancing), you're forking — decide that deliberately rather than discovering it on day three.

Extension points, cheapest first. In rough order of maintenance cost: (1) composing standard Nova fields with different rules(); (2) subclassing the base wizard class and overriding step methods; (3) overriding the Vue component via Nova's component replacement; (4) a full fork. Prefer (1) and (2). If you land on (4), vendor it and document the fork diff so future upgrades are a rebase, not an archaeology project.

Scope your ambition to match the ecosystem signals. 14 stars, 0 dependents, last release 2023, GitHub-issues-only support, no SLA. MIT makes forking the real mitigation. That's a perfectly good trade for an internal, low-blast-radius admin wizard where a custom build would cost more than a controlled fork — and a bad trade for anything customer-facing, compliance-sensitive, or revenue-critical.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment