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

Tutorials Laravel Package

guava/tutorials

Guava Tutorials is a Laravel package for adding in-app tutorials and guided onboarding flows to your application. Define steps, target users, and track progress through a simple, flexible API, helping new users learn features without leaving your app. It keeps tutorial content organized and easy to...

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish, selectively.

composer require guava/tutorials:^1.1
php artisan vendor:publish --provider="Guava\\Tutorials\\TutorialsServiceProvider"
php artisan migrate

The provider will offer several publish tags (config, migrations, lang, views, assets). Publish config, migrations, and lang immediately. Hold off on views and assets until you have an actual theming requirement — a published Blade view stops receiving upstream improvements the moment it lands in your repo, and you inherit a manual diff on every upgrade.

Read these four things before writing application code, in this order:

  1. config/tutorials.php. The description promises configurable models, routes, and views, so this file is the contract. Model bindings, route prefix/middleware, table names, and the view/component names are almost certainly here. Every customization decision later — custom model, custom route guard, custom overlay — is a config edit rather than a fork. Read it first so you do not accidentally build around hardcoded assumptions.
  2. The published migrations. These define the real schema: how steps are ordered, how actions are stored, how progress is keyed to users. You will be writing queries and reporting against these tables, so treat this like a schema review, not an install step.
  3. The published lang files. Persian is the only non-English locale evidenced in the 1.1.0 notes, and 1.1.0 shipped an RTL alignment fix. Note the namespace so you know where overrides go.
  4. The component/route surface. What renders a tutorial, what the overlay binds to in the DOM, and which middleware guards the routes.

First use case: prove the whole loop on one low-risk flow.

Pick something boring and stable — "create your first project" — with three steps, one overlay target on a page that will not churn, and one completion record. The point of the spike is not the tutorial; it is answering four questions that decide everything downstream:

  • Does php artisan migrate produce a schema you are willing to own and query?
  • Does the package ship pre-built CSS/JS, or does it require a Vite build step? This single fact decides whether theming is a config task or a CSS fork.
  • Does php artisan config:cache still boot cleanly once a tutorial definition exists (especially one containing a Closure)?
  • Is Filament or Livewire a hard transitive dependency? If you are putting this on a customer-facing surface, you need to know before the design review.

Budget one to two developer-days for that loop: install → define one tutorial with ordered steps → render on one authenticated page → complete a step → verify the progress row. Turning the same flow into production content with targeting, theming, and instrumentation is closer to a sprint.

Implementation Patterns

Keep content in data, keep logic in code.

The authored unit is a tutorial composed of ordered steps; each step carries markdown body content plus one or more actions (highlight a target, navigate, dispatch). The healthy split:

  • Seeders or an admin panel own titles, step order, markdown, and action targets.
  • Your application code owns selection, audience targeting, and instrumentation.

The failure mode is expressing tutorial definitions as PHP arrays in a service provider — at that point you have rebuilt the package inside your own repo and lost the ability for a content owner to edit anything.

Put a thin boundary between your app and the package.

This is the highest-leverage pattern in the whole assessment. Do not let controllers and Blade views call vendor models directly. Write one small service that answers "which tutorial, if any, should this user see on this surface right now?":

// Sketch — verify method names against the source.
final class TutorialRegistry
{
    public function forUser(User $user, string $surface): ?Tutorial
    {
        return $this->eligible($user)
            ->first(fn ($t) => $t->surface === $surface && ! $t->isCompletedBy($user));
    }
}

Everything downstream — the layout component, the completion endpoint, your analytics events — talks to that boundary. Audience eligibility depends on your roles, plans, and tenancy model, which the package can never know. Keeping selection in your code means the package stays an implementation detail you can swap or drop.

Use Closure action properties for segmentation, not for stored state.

1.1.0 added Closure support to action property types, and this is the feature that earns the package a place next to a static tour library. The valuable use is one approved content artifact that varies at runtime by plan, role, or tenant:

// Shape, not API contract.
'actions' => [
    ['type' => 'highlight', 'target' => '[data-tutorial="create-project"]'],
    ['type' => 'navigate',  'url' => fn (User $u) => $u->isOnTrial()
        ? route('billing.upgrade')
        : route('projects.create')],
],

That converts personalization from a content-duplication problem into a config problem. Two rules: closures live only in PHP files that ship with your code (never persisted to a DB column, never inside a cached config array), and they stay pure — no queries you would not want running on every overlay render.

Record progress idempotently.

Users double-click, they refresh mid-step, they finish a step twice. Completion writes should be firstOrCreate-style no-ops keyed on (tutorial, step, user) rather than inserts. Separately, decide up front what happens when a tutorial is unpublished: prefer a published flag over deletion, because orphaned completion rows are harmless but confusing, and reordering steps silently invalidates existing progress. Retire, do not delete.

Render once, high in the authenticated layout.

Register the overlay component once in your app's authenticated layout and let the registry decide whether anything renders. Per-page registration invites double-mounting and makes "which tutorial wins" unanswerable. The overlay must sit above whatever page it targets, so the layout is the natural home.

Bind to semantic selectors, not CSS classes.

Adopt a convention — data-tutorial="create-project" — and point actions at those attributes instead of classes or DOM structure. Class names are the most volatile thing in a frontend; a dedicated attribute is a contract you can grep for, assert on in CI, and refactor against with confidence.

Instrument from the first PR.

The package renders guidance; it does not measure it. There is no funnel analytics, no A/B variant assignment, and no holdout group in evidence. Fire your own tutorial_shown, tutorial_step_viewed, tutorial_completed, and tutorial_dismissed events through your existing pipeline as part of the pilot. Retrofitting analytics onto onboarding means relaunching to get a baseline.

Three CI checks that pay for themselves:

  • Assert every configured tutorial target selector exists in the rendered DOM of its intended page. The package cannot catch overlay breakage caused by your own UI refactors.
  • Run config:cache in CI with your Closure-bearing definitions present, and an Octane smoke test if you run Octane.
  • A visual regression baseline with one RTL locale and one LTR page, so an RTL fix never ships as an LTR regression.

Gotchas and Tips

Closures are not serializable — this is the headline risk. If a definition containing a Closure ends up inside a config array or a database column, config:cache will fail or silently drop it, and Octane's long-lived workers may hold stale bindings across a deploy. Test this explicitly before you rely on closures anywhere real, and if your release step runs config:cache, make that step part of the spike — not part of launch day.

Selector drift is your risk, not the package's. Overlay tutorials bind to DOM in your application. Every refactor of a target page is a potential silent breakage: no exception, just a tutorial highlighting nothing. Mitigate with the data-tutorial convention and the CI existence check. Consider a step-validity guard in your boundary service that skips a step whose target is absent, rather than rendering a broken overlay.

Debugging "nothing renders" is hard, because the failure mode is silence. No tutorial, no error, and you cannot tell whether it was the audience rule, the published flag, the ordering, an existing completion row, or a missing selector. Log the registry's decision — which tutorial was selected, which were filtered and why — behind a debug flag. That log turns a 40-minute mystery into a one-line answer.

Swapping models is a config edit with consequences. Overriding the tutorial or step model in config is straightforward; keeping your custom model compatible with the package's expectations (table name, ordering scope, relationships) is where people get hurt. Extend the vendor model rather than reimplementing it, and re-read the vendor class after every minor bump.

Route and auth configuration is on you. Tutorials expose content through configurable routes. Check the middleware stack in a fresh config file — it is easy to end up with a tutorial route outside auth, or outside your tenancy scoping, simply because the default did not match your app.

Localization coverage is unstated. Persian exists and an RTL alignment fix shipped as a bug fix in a minor release, which tells you the presentation layer's locale and theme coverage is still maturing. Per-locale key completeness is not documented. Missing keys usually render as raw keys to end users — very visible. Publish the lang files, diff key sets per locale before enabling a locale, and contribute gaps upstream rather than maintaining a permanent override.

Upgrades are deliberate, not automatic. Pin ^1.1 and read each release. Treat the visual layer as a theming pass against your design system rather than a drop-in, and keep published view/asset surface area minimal so upgrades stay cheap.

Maturity and bus factor are real. Single-maintainer agency package, version 1.1.0, effectively no dependent ecosystem, no stated SLA or commercial support path. None of that precludes using it, but it does mean: name an internal owner, budget a few hours a quarter for release review and smoke tests, keep the boundary wrapper so forking is a days-not-months decision, and do not put this on a business-critical surface without a fallback.

Verify, don't assume. Everything above is derived from the 1.1.0 release notes and the package description — no source, composer constraints, or test coverage was available to review. Confirm the actual API surface, PHP/Laravel version constraints, and whether Filament or Livewire is a hard dependency during the spike, before any of these patterns become load-bearing.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin