jibaymcs/filament-tour
Bring Driver.js powered guided tours to your Filament panels. Register tours on any page with the HasTour trait and define steps with titles, descriptions, and element targets to elegantly onboard users.
Architecture fit. Filament Tour is a thin, opinionated presentation layer: page classes opt in via the HasTour/HasHighlight traits, tours are composed with a fluent Tour::make()->steps(Step::make(...)) builder, and the whole graph is serialized to JSON and handed to DriverJS on the client. That fits our stack well if we treat tours as content/configuration rather than application logic. The package owns almost no server-side behavior — no persistence, no migrations, no domain coupling — which makes it cheap to adopt and cheap to remove. The main architectural coupling is undesirable-by-design: steps target raw CSS selectors (.fi-avatar, .fi-header-heading) against Filament's internal DOM. That is a contract with Filament's markup, not with its public API, and it is the single largest design concern.
Integration feasibility. Straightforward: Composer install pinned to the Filament major (^2.0–^5.0), register FilamentTourPlugin::make() in the panel provider, optionally publish config/views, add the trait plus a tours() method to a page. Extensibility is reasonable — JSON tours loadable from disk or URL, and filament-tour::open-tour / open-highlight Livewire events allow custom triggers (buttons, conditional logic, role-based gating). The trait-based registration means tours live next to the page they describe, which is good for locality and bad for page-class bloat if we author many tours.
Technical risk.
FilamentAsset). Every Filament upgrade becomes a gated dependency upgrade for us. If the package stalls, tours become an upgrade blocker for the admin panel.body) rather than loud.only_visible_once is enforced via localStorage per browser, not per user. Clearing storage, private browsing, ITP/blocked storage, or a second device all replay or suppress tours unpredictably. There is no server-side seen-state and therefore no reporting.Tour::make(url: ...) fetches and renders arbitrary JSON, and description() accepts HtmlString. If the URL or JSON ever becomes configurable by a non-developer, that is a stored-XSS and SSRF surface. Only allow developer-committed, sanitized sources.only_visible_once, yet the documented event-listener recipe reads Config::get('filament-tour.tour_prefix_id'). Either that key is undocumented or the example is stale — a bad signal for a package we would be pinning.Key questions. Is the package actually receiving fixes for Filament v4/v5 (commit history, open issues, PR latency), or is it effectively in maintenance-freeze? Will we accept owning a fork? Do tours need to be measured (completion rates, drop-off) or is this purely onboarding polish — because nothing here emits analytics. Does the "seen once" semantic need to be per-user-account across devices, and if so, is a custom Livewire listener + DB column acceptable overhead? Which tours are business-critical enough to require regression coverage on every Filament upgrade?
Stack fit. Good, with one caveat. We are on a standard Filament panel with Livewire/Alpine and a Vite-built custom theme; DriverJS is a mature, dependency-light library and the package's job is mostly registration and serialization. The caveat is SPA navigation — Filament panels navigate via wire:navigate, and DOM-targeting tours triggered on page load are exactly the class of feature that races page swaps. Expect to spend integration effort on when tours fire (page mount/rendered hooks, event-driven opens) rather than on the API itself.
Migration path. Low switching cost in both directions. Adoption is additive: one Composer dependency, one plugin registration, one trait per page. Exit is equally cheap — the tour/step definition objects are simple value objects, so we can port them to a bespoke DriverJS wrapper (driver.js + a small Blade partial + a Livewire component) in roughly 3–5 engineering days, since we are only reimplementing the builder-to-JSON translation. Recommendation: do not architect around this package's traits. Author tours in our own TourDefinition registry (a plain PHP array/JSON committed to the repo, loaded via Tour::make(json: ...)), so that if we fork or replace the package, no page classes change. That converts the maintainer risk into a contained swap.
Compatibility. Pin the Composer constraint to the exact Filament major we run and treat it as a lockstep pair in our upgrade checklist. Confirm DriverJS asset registration works inside our Vite theme (not just Filament's default stack) — that is the most common integration break. Verify the filament-tour::loaded-elements event fires as documented, since our conditional tour logic depends on it. Check for a translation path for button labels and step copy; if absent, override via published views and set nextButtonLabel/previousButtonLabel/doneButtonLabel explicitly per locale.
Sequencing.
data-tour="..." attributes to our own Blade/table columns — over .fi-* internals). This is the single highest-leverage mitigation available.->visible(fn () => ...) and a config flag, with an event-driven open for conditional logic.dispatchOnNext events into our existing analytics so we can see step-level completion and abandonment.Maintenance. Budget for a fork from day one. Concretely: an owner in the platform team, a standing ticket at every Filament upgrade, and no more than a few days per year of touch time — but the cost is scheduled coupling, not hours. The absence of a second maintainer and near-zero dependent base means no community safety net; we should mirror the repo and be prepared to patch. Also budget for selector drift after any theme refresh; treat the tour suite as a tested artifact, not fire-and-forget content.
Support. No vendor channel — GitHub issues only, on a project whose maintainer has publicly stepped back. Internal first-line support will field "the tour shows up every time" (storage cleared / private browsing / new device) and "the tour highlights nothing" (selector drift) — both are predictable and should be pre-answered in an internal runbook. Because only_visible_once is client-side, we cannot resolve "I already dismissed this" complaints authoritatively without adding our own per-user persistence.
Scaling. Server-side cost is negligible: tour JSON is generated per page render and cached with the page. Client-side cost is one additional JS bundle (DriverJS + package) on panel pages; keep it off pages with no tours if asset registration allows. Two real scaling limits: (a) the "seen once" signal does not scale across users, devices, or tenants — if tours are part of an onboarding compliance story, localStorage is inadequate; (b) remote-URL JSON tours add a runtime network dependency per render — cache or commit them to the repo instead. For multi-tenant panels, remember selectors and tours are global; tenant-specific UI means tenant-specific selector breakage.
Failure modes. Ranked by likelihood × impact:
->uncloseable() combined with a broken selector is a hard block on a production page. Policy: never ship uncloseable tours outside tightly-scoped first-run flows; always keep an escape path.Ramp-up. Developer learning curve is short — half a day for the fluent API, one to two days to internalize the targeting and event model. Docs are above average for a community plugin (traits, JSON examples, full method references, a CSS-selector dev tool that is correctly disabled in production) but carry at least one internal inconsistency and a maintainer-abandonment banner that will spook stakeholders. Product/design ramp-up is the real work: someone must decide which workflows are worth a tour, write step copy, localize it, and own it after launch. Recommend naming a content owner before the pilot, and setting the expectation that tours are a maintained artifact requiring re-verification each release cycle — otherwise the feature quietly rots into a source of user-facing bugs within two Filament upgrades.
How can I help you explore Laravel packages today?