letmesplain

Installing Splain

Splain is an add-on for Filament (the admin-panel toolkit for Laravel/PHP apps). It adds a small helper dot to your admin pages — guided, code-anchored walkthroughs and page tours that live in your own database, so your in-app help can't silently go out of date. This page gets you from composer require to a working helper dot, and explains every switch you can flip.

Requirements: PHP 8.3+, Laravel 11–13, Filament 3.2+ or Filament 4.x (both majors tested in CI on every push), Livewire 3.

1. Require the package

composer require splain/splain

Not on Packagist yet. Splain is release-pending while the license is finalized. Today it installs via a private Composer repository for early-access hostsask for early access (one GitHub issue: your app and stack) and you'll get the repository line for your composer.json plus the @dev require. Want just a heads-up when the public release lands? Subscribe to the release announcement.

(Developing against a local checkout? A path-type repository entry pointing at your clone works the way you'd expect and symlinks the package.)

2. Run the migration

php artisan migrate

Splain creates four tables: splain_guides (where every guide lives — title, pages covered, steps), splain_tracks (ordered onboarding paths), and splain_guide_completions + splain_track_assignments, which stay empty unless you opt into server-side progress — completion tracking is off by default, and when it's on it stores a pointer to your user, never a copy of personal data (see progress.md). The migrations load automatically — there is nothing to publish first.

3. Register the plugin on your panels

SplainPlugin is playback — the helper dot your users see. Add it to every panel where guides should play.

use Splain\Filament\SplainPlugin;

$panel->plugins([
    SplainPlugin::make(),
]);

That's the whole base install — the helper dot plays your published guides on every panel where you register it. (Guides are authored as JSON and validated with splain:check; the Studio — Splain's built-in visual, point-and-click editor for creating and editing guides inside your app — is a separate Pro package, see Splain Pro (optional) below.)

4. Publish the assets

php artisan filament:assets

This copies Splain's pre-compiled JavaScript and CSS into your public/ folder. You never need Node, npm, or a build step — the bundles ship ready-made. Re-run this command whenever you update the package (most apps already run it on deploy).

5. See it work

The helper dot only appears on pages that a guide actually covers, so an empty splain_guides table means an invisible Splain. Write your first guide by hand (see authoring.md — it's one JSON file), seed it into the table, and check it before expecting it to play:

php artisan splain:check              # validates every guide in the database
php artisan splain:check --strict     # also fails on warnings — the publish bar

Configuration

php artisan vendor:publish --tag=splain-config

That gives you config/splain.php. Every key, in plain language:

pii_default (default: false)

Whether Splain should assume your screens show personally identifiable information (PII — any sensitive personal data) unless told otherwise. Off by default. Today this is a declared default only — what actually drives masking is the per-step privacy flags inside each guide, plus privacy.masks below. Leave it false unless a future release tells you otherwise.

capture.strategy (default: 'code-only')

How guide drafts get created. code-only — the only mode that exists today — means guides are drafted from your app's source code, not from watching a live environment. The other values you'll see in the file's comment (demo-credentials, local-copy, splain-managed-ephemeral) are reserved names for future capture modes. Leave this alone.

overlay.load_on_request (default: true)

When true, the playback JavaScript is only sent to the browser on pages where a guide matched — pages with no guides pay nothing. When false, it loads on every panel page. One reason to go eager: Privacy Mode. With on-request loading, doing a hard page reload on a page no guide covers means no masking engine is present, so masks won't show there. If you're screen-recording across the whole panel and need gap-free masking, set this to false.

playback.serve_drafts (default: false)

Fail-safe by default: draft guides play only for people who pass the splain.preview-drafts gate (authors and reviewers preview their work in place; the gate falls back to the Studio's umbrella gate, and in local environments any authenticated user passes). Set SPLAIN_SERVE_DRAFTS=true only in throwaway dev environments where drafts should reach everyone — never in production, since walkthroughs guide users through real actions on real data. In the free package a guide's published status controls who sees it: with serve_drafts off (the default), only guides you've flipped to published reach real users, and drafts stay preview-only. Flipping that status is the free publish. The governed, named-human attested publish sign-off of record is a separate Pro gate (splain/pro — proprietary).

privacy.masks (default: [])

Extra page regions to cover whenever Privacy Mode is on, beyond what the guides themselves flag — think app chrome like the user menu or global search results, which no guide knows about. Each entry is a CSS selector plus how to hide it:

'masks' => [
    ['selector' => '.fi-user-menu', 'mode' => 'blur'],   // or 'block' for an opaque cover
],

A typo in mode degrades to blur (still masked), never to "shown in the clear".

Honest scope: Privacy Mode is a demo and screen-recording aid, not a security control. Masked content is hidden visually but remains in the page's HTML and accessibility tree — anyone with browser dev tools can read it. Never rely on it to keep data from the person at the keyboard.

Splain Pro (optional)

Everything above is the complete free package (splain/splain, Apache-2.0): playback, splain:check, Privacy Mode, guides-as-code, AI generation, and the published-status publish. You never need anything else to ship guides.

Splain Pro (splain/pro — a separate, private, proprietary package) adds the visual on-page Studio (the element picker and review inbox), the Hub (Filament resources — the pages that create/list/edit one kind of record, e.g. Employees — for non-dev guide management), track assignment, onboarding-completion reporting, and the governed attested publish. To install it:

composer require splain/pro

Then register StudioPlugin alongside the free SplainPlugin. Add StudioPlugin::make() to panels where your guide editors work, and call ->hub() on the one panel that should host the guide management pages:

use Splain\Filament\SplainPlugin;
use Splain\Studio\StudioPlugin;

// Your admin panel — free playback plus the Pro Studio hub:
$panel->plugins([
    SplainPlugin::make(),
    StudioPlugin::make()->hub(),
]);

// Any other panel — playback, and on-page editing for people who pass the gates:
$panel->plugins([
    SplainPlugin::make(),
    StudioPlugin::make(),
]);

Registering StudioPlugin widely is safe: everything it renders is permission-checked on the server, so users who aren't allowed to edit see nothing extra at all.

Who can use the Studio (gates)

The Studio (a splain/pro feature) ships no roles or permissions of its own — your app already has those. Instead it asks Laravel gates that you define (a gate is Laravel's built-in yes/no permission check — a small function returning whether a user may do something). If you define nothing:

So on production, nobody can touch the Studio until you define a gate.

The model is two layers: grant everything with one broad "umbrella" gate, then override single abilities with narrower gates. A narrower gate, when you define one, always wins over the umbrella for that one ability.

The umbrella gate

One gate to grant everything Studio-related:

use Illuminate\Support\Facades\Gate;

// e.g. in a service provider's boot():
Gate::define('splain.manage-guides', fn ($user) => $user->hasRole('admin'));

Splitting off single abilities

Every Studio action first checks its own, more specific gate and only falls back to the umbrella if you haven't defined one. The specific gates that exist today:

Gate What it controls
splain.view-guides seeing the guide pages in the hub
splain.edit-steps the on-page editor, step edits, and the hub's review-flag actions
splain.publish publishing and unpublishing guides

The classic use: an editor helps write guides but shouldn't ship them. Give the team the umbrella, then carve publishing out:

Gate::define('splain.manage-guides', fn ($user) => $user->onGuidesTeam());
Gate::define('splain.publish', fn ($user) => $user->isAdmin());

Editors can now view and edit, but the publish buttons never appear for them — the specific gate overrides the umbrella for that one ability. The splain.preview-drafts gate works the same way: passers see draft guides in place of published ones while everyone else sees only published work.

Something not working? splain:doctor

php artisan splain:doctor

One command checks the whole installation: tables migrated, published assets present AND fresh (the classic trap after updating the package — a stale public/ copy makes the UI half-work), the plugin registered on a panel, every guide passing splain:check, and config states worth knowing about (drafts served in production, half-configured generation, and misconfigured server-side progress). The free doctor flags your opt-in progress config; per-person and onboarding-completion reporting on top of that data is a splain/pro capability. Exit code is non-zero only for real breakage, so it's safe in a deploy pipeline.

Content-Security-Policy (strict-CSP hosts)

Splain is CSP-clean by design and adds no requirements beyond what your stack already needs (Filament/Livewire themselves render inline style attributes and Alpine needs its own allowances — your policy already accounts for your stack).

If you use Laravel's nonce pattern (Vite::useCspNonce() in a middleware), Splain picks it up automatically: every inline element it emits (the launcher's style block, the payload script, the progress bridge, the engine script tags) carries your nonce, and the JS bundles re-apply that nonce to everything they inject at runtime (the privacy-mask stylesheet, the standalone skin). Nothing to configure.

The engine itself is proven under script-src 'nonce-…'; style-src 'self' 'nonce-…' — no unsafe-inline anywhere — by a CI browser test (tests/browser/csp.spec.ts, harness: examples/standalone/csp.html).


Rendered from splain@a738cf8. The documentation is rendered from the package repository — the same files that ship with Splain — so the site can't drift from the code.