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

Filament Tour Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Pin the package major to your Filament major — this is not optional. ^3.0 for Filament v3, ^4.0 for v4, ^5.0 for v5. Mixing majors will silently break the Blade/Alpine wiring rather than throwing a clear exception.

composer require jibaymcs/filament-tour:"^3.0"   # match your Filament version
php artisan vendor:publish --tag="filament-tour-config"
php artisan vendor:publish --tag="filament-tour-views"   # optional, only if you need to restyle

Register the plugin in your panel provider:

use JibayMcs\FilamentTour\FilamentTourPlugin;

public function panel(Panel $panel): Panel
{
    return $panel->default()
        // ...
        ->plugins([
            FilamentTourPlugin::make()
                ->onlyVisibleOnce(false),   // dev only — see Gotchas
        ]);
}

Where to look first: your panel's Dashboard page class. If you don't have one, generate it (php artisan make:filament-page Dashboard) and point the panel at it. Add the HasTour trait and implement the mandatory tours() method — the trait expects it to exist even when returning [].

namespace App\Filament\Pages;

use Filament\Pages\Dashboard as FilamentDashboard;
use JibayMcs\FilamentTour\Tour\HasTour;
use JibayMcs\FilamentTour\Tour\Step;
use JibayMcs\FilamentTour\Tour\Tour;

class Dashboard extends FilamentDashboard
{
    use HasTour;

    public function tours(): array
    {
        return [
            Tour::make('dashboard')
                ->route('/admin')
                ->steps(
                    Step::make()
                        ->title('Welcome to your Dashboard')
                        ->description('Let me show you around.'),
                    Step::make('.fi-avatar')
                        ->title('Your avatar')
                        ->description('Account settings live here.')
                        ->icon('heroicon-o-user-circle')
                        ->iconColor('primary'),
                ),
        ];
    }
}

First use case: a 3–5 step tour introducing the sidebar, global search, and the user menu on first login. A Step::make() with no selector behaves as a centered modal — that's your opener and closer. Selector-based steps highlight real UI. Get that shipped before touching Highlights or JSON tours; it's the 80 % case.

Implementation Patterns

Tour IDs are your API. The id you pass to Tour::make('dashboard') is the handle you'll use everywhere else — from buttons, listeners, and tests. Treat it like a slug: stable, unique per panel, and versioned when the content changes (dashboard-v2).

Trigger from a button instead of page load. This is the most common real-world pattern and it's a one-liner:

<button wire:click="$dispatch('filament-tour::open-tour', 'dashboard')">Replay tour</button>
<button x-on:click="Livewire.dispatch('filament-tour::open-highlight', 'avatar')">What's this?</button>

The prefix filament-tour:: is required; dropping it fails silently.

Conditional tours via the loaded-elements listener. When a tour should only fire based on data state (e.g. user has zero posts), listen for the package's hydration event and dispatch the open yourself. Note the ID prefix handling — the package prefixes tour ids, so strip it before re-dispatching:

use Livewire\Attributes\On;

#[On('filament-tour::loaded-elements')]
public function maybeShowOnboarding(bool $only_visible_once, array $tours, array $highlights): void
{
    if (Post::where('user_id', auth()->id())->exists() || $tours === []) {
        return;
    }

    $prefix = config('filament-tour.tour_prefix_id', '');
    $this->dispatch('filament-tour::open-tour', substr($tours[0]['id'], strlen($prefix)));
}

Simpler alternative: gate with a closure on the tour itself — ->visible(fn () => ! auth()->user()->hasCreatedPost()). Reach for the listener only when you need to interleave multiple tours or react to something that happens after load.

Chain step events to cross page boundaries. The Step event API is what turns a tour from a slideshow into a walkthrough. Combine clickOnNext + dispatchOnNext to open a modal before the next step tries to highlight something inside it:

Step::make('.fi-ta-header')
    ->title('Open a record')
    ->description('Click the row actions menu.')
    ->clickOnNext('.fi-ta-actions button'),
Step::make('.fi-dropdown-panel')
    ->title('Actions')
    ->description('Everything you can do with a record.'),

Order matters: the click fires, Livewire re-renders, and the following step resolves its selector against the new DOM.

Highlights for persistent, discoverable help. Tours interrupt; highlights sit quietly. Use HasHighlight on the same page for a permanent "?" affordance next to risky or non-obvious controls:

use JibayMcs\FilamentTour\Highlight\HasHighlight;
use JibayMcs\FilamentTour\Highlight\Highlight;

public function highlights(): array
{
    return [
        Highlight::make('.fi-header-heading')
            ->element('.fi-header-heading')
            ->title('Contextual title')
            ->description('Changes per module.')
            ->position('top-right'),
    ];
}

Build tours from JSON when content is owned by someone else. Tour::make(json: $string) or Tour::make(url: $url) lets a PM/CS person author steps without a deploy. Store the JSON on a disk and read it in tours(). Keep it in version control alongside your code — remote URLs are a hidden runtime dependency and a GDPR/latency footgun.

Use the CSS selector dev tool aggressively. FilamentTourPlugin::make()->enableCssSelector() (disabled in production automatically) gives you Ctrl/Cmd+Space to inspect, Ctrl/Cmd+C to copy the selector of whatever you're hovering. Do all selector authoring with it — guessing .fi--prefixed classes from memory is how you end up with broken tours after a Filament minor upgrade.

Gotchas and Tips

only_visible_once is a trap during development. The "seen" flag lives in localStorage. You'll write a tour, run it once, then spend an hour wondering why nothing renders. Either leave ->onlyVisibleOnce(false) in local/APP_DEBUG environments, or clear site data / use an incognito window. In production, remember that changing the steps does not make the tour reappear for users who already dismissed it — bump the tour ID (dashboard-v2) or set ->alwaysShow().

Route matching is a literal string compare. ->route('/admin') will not match /admin/users or a resource page. If you want a tour everywhere, use ->ignoreRoutes(). If you want it on a resource's List page, register the trait on the List page class itself (ListPosts), not on the Dashboard, and set the route to /admin/posts.

Selectors must resolve at render time. A step targeting a table row, a dropdown, or modal content fails if that markup doesn't exist yet. The fixes, in order of preference: (1) precede it with a clickOnNext/dispatchOnNext that opens the container; (2) target a stable parent; (3) write the step as a modal with no selector. There is no waitForElement in the API — don't hunt for one.

Filament's .fi-* classes are not a stable API. They're a pragmatic choice but they shift between majors and occasionally between minors. When you upgrade Filament, re-run every tour manually. Centralise selectors as constants or a small config array so a rename is a one-line fix rather than a grep across ten page classes.

Traits on page classes, not resources. HasTour/HasHighlight go on Panel Page classes (including custom Dashboards). On resource pages, extend or override the generated ListRecords/CreateRecord page rather than the resource itself. Also watch for trait method collisions — if your page already implements tours() or highlights() for something else, rename or extract.

notifyOnNext needs a real Filament Notification, not an array. The JSON tour format accepts arrays, the fluent PHP API expects Filament\Notifications\Notification::make()->title(...)->success(). Mixing the two is a common source of "why is nothing firing".

The package is effectively unmaintained. The README says so explicitly. That's fine for a read-only UI nicety, but pin the exact version in composer.json, commit the lockfile, and be prepared to fork. Never let a tour sit on a critical path (e.g. blocking a required setup step) — degrade gracefully if the plugin is removed.

Customise through published views, not by editing vendor/. DriverJS theme overrides and button markup go in resources/views/vendor/filament-tour. If you need per-tour colouring without publishing, ->colors($light, $dark) on both Tour and Highlight is enough for most branding.

Debugging checklist when a tour won't fire: (1) is FilamentTourPlugin::make() registered and does the panel boot without error; (2) does the page actually use HasTour; (3) does tours() return non-empty; (4) does the route match; (5) has the user already seen it; (6) is the dispatched ID prefixed correctly. That order resolves ~95 % of reports.

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.
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera