lara-zeus/wind
Lara Zeus Wind is a Laravel package in the Zeus ecosystem that helps you build dynamic, configurable interfaces with minimal boilerplate. Install it via Composer, define your settings in clean PHP, and ship admin panels and forms faster. Open source and MIT licensed.
Verify the version matrix before anything else. Wind is a Filament plugin, so its installability is dictated by the Filament major it was built against. Three checks, in order:
composer show filament/filament livewire/livewire laravel/framework
# then read the require block on the exact tag you're installing:
composer show lara-zeus/wind --all | head -40
A v5-era tag targets the current Filament major; dropping it into a panel still on the previous major will resolve but break at runtime. Check PHP too — it's the usual silent blocker on older CI images.
Install and publish.
composer require lara-zeus/wind:^5.0 # pin, don't float
php artisan vendor:publish --tag=wind-config
php artisan vendor:publish --tag=wind-migrations
php artisan vendor:publish --tag=wind-translations
php artisan migrate
Skip wind-views on the first pass. Publish tags drift between majors — if a tag returns "no publishable resources," run php artisan vendor:publish interactively and select the LaraZeus\Wind provider entry instead of guessing.
Register the plugin in the panel provider that should own the inbox:
->plugins([
\LaraZeus\Wind\WindPlugin::make(),
])
Then clear caches (php artisan filament:optimize-clear, plus config:clear if you've touched config) and reload /admin. No navigation entry means the plugin registration is wrong — not that the package is broken.
Where to look, in this order:
vendor/lara-zeus/wind/src/ — the service provider shows what's registered and what's publishable; the Livewire component class is the submit pipeline.config/wind.php — routes, field list, notification recipient, layout. Read this before touching any view.resources/views/ in the package — the embeddable component and the markup you're inheriting.First use case. Do the whole loop in a scratch app first: embed the form, submit, confirm the row persists, confirm the notification arrives, confirm it shows in the panel.
{{-- resources/views/contact.blade.php --}}
<x-wind::contact-form />
{{-- confirm the actual tag name against the published/vendor views --}}
That hour buys you certainty about which failures are yours.
Wrap it in a single partial. Highest-leverage habit with a low-adoption dependency. Content pages @include('partials.contact-form'); only that partial references the package component. One route, one partial, one swap point — so replacing or rebuilding the form later touches zero content pages.
Own the schema. Publish the migration, move it into database/migrations with a real timestamp, and make every subsequent change in your own migrations. Add an index on created_at early — you will sort and filter by date, and the package's resource will not do it for you.
Configure, don't fork. Labels, redirect target, notification recipient, and layout belong in config/wind.php and language file overrides. The first edit under vendor/ is the moment you inherit maintenance you didn't budget for. If config can't express a requirement, reach for an observer or a wrapper component before a view override.
Hook the pipeline at the model. CRM sync, Slack pings, webhook fan-out — all of it belongs on the submission model's created event, not inside a patched Livewire component:
Submission::created(fn ($s) => SyncLeadJob::dispatch($s)->afterCommit());
afterCommit() is not optional here. Without it a queued sync can fire for a row whose transaction rolled back, and downstream systems end up with leads your database doesn't have.
Check the persistence/notification ordering, then fix it if needed. Read the submit method: the row must be written before mail is attempted, and a mail exception must not propagate. If the package sends synchronously, move the mail out entirely — flip to a queued notification, or suppress the built-in one and send from your created listener. A contact form that 500s on a slow SMTP handshake is worse than one with no notification at all.
Gate the resource explicitly. Filament's default authorization is permissive. Submissions are PII. Add a policy or canViewAny check, put the resource in a sensible navigation group, and register the plugin only in the panel that should see the inbox. "Any panel user can read every customer email" is a review finding waiting to happen.
Extend triage with Filament, not upstream. Read/unread state, assignment, CSV export, "mark all as read", saved filters — these are table actions, column toggles, and header actions on your own resource subclass. The package gives you a starting screen; Filament gives you the rest. Don't file an issue and wait.
Treat the endpoint as hostile. It's an unauthenticated write route. Rate-limit at the route or edge (not only in the component), add a honeypot or captcha to the partial, and lean on Laravel validation rather than hand-rolled sanitization.
Testing pattern. Livewire::test() the component, assertDatabaseHas() the submissions table, Notification::fake() for the mail assertion — plus one test that asserts the row still exists when the notification transport throws. That last test is the one that catches silent lead loss.
Observability. Count submissions per day and alert when the count drops to zero. A one-line log or metric in your created listener covers it. Ship a short runbook: where the form lives, where rows land, how to export, how to disable the form, who watches volume.
The repo description doesn't match the artifact. The marketing copy advertises generic Tailwind Blade components; the package is a contact form plugin for Filament. Trust composer.json and src/ over any listing text, and confirm the namespace from the vendor directory before writing imports.
Filament majors are the real upgrade event. Plugins pin to one major. Never adopt Wind in the same release train as a Filament v3 → v4-style bump, and re-test the form end-to-end on every major. Budget roughly a day a year and you won't be surprised.
Silent lead loss is the realistic failure mode. Not an outage — messages that were never persisted or never emailed, unnoticed for three weeks. The three countermeasures are the DB-before-mail assertion, queued mail, and an alert on submission-count drops. Everything else is secondary.
Stale assets are the #1 "worked locally" symptom. Filament and Livewire assets are published and versioned independently of your app build. If the form renders but submission silently no-ops or validation never fires, run filament:assets / livewire:assets and confirm cache-busting happens per release.
Don't re-publish over an owned table. Once the migration is yours, a future vendor:publish --tag=wind-migrations drops a duplicate stub into database/migrations — which either fails or recreates the table. Delete duplicates and note it in the runbook.
Config cache invisibility. In any cached environment, edits to config/wind.php do nothing until php artisan config:cache (or config:clear) runs. Same story for route changes if the package registers its own routes.
Published views win forever. Once you publish views, upstream fixes never reach you again. Prefer config; if you must publish, leave a comment with the package version you diverged from so upgrade diffs are tractable.
Dependency risk is the headline. ~35 stars, zero dependents, single maintainer, issue-tracker-only support, and a PR history dominated by dependabot bumps and translation chores — maintenance-mode stewardship, not active investment. Mitigations: pin the exact version, don't spread it across multiple apps, keep the partial swap point, and keep a written fork-or-rebuild decision on the shelf. A competent Laravel dev rebuilds the equivalent in one to two days, which is also your escape-hatch estimate.
PII obligations are yours. Email, IP, and message body in your database means retention windows, export, deletion-on-request handling, and a defined panel audience. None of it ships with the package. Decide before go-live.
Spam scales faster than legitimate traffic. The risk isn't DB throughput — it's table bloat, exhausted mail quota, and a noisy dashboard. Rate-limit at the edge and watch row growth for the first month.
Debugging order when something's wrong: (1) panel provider registration, (2) php artisan about version matrix vs the package's composer.json, (3) asset staleness, (4) config cache, (5) published-view divergence. That sequence resolves the overwhelming majority of "the plugin isn't working" reports without reading package source.
How can I help you explore Laravel packages today?