laravel-afterburner/jetstream
Laravel Afterburner for Jetstream extends Laravel Jetstream's authentication, profile, and team scaffolding with opinionated enhancements and reusable tooling, helping you build account and admin features faster with less boilerplate.
Before you type anything, resolve the information gap. The v1.0.0 release note is a changelog URL and nothing else — no feature list, no migration guide, no Laravel/PHP support matrix. Your first hour is discovery, not coding.
composer require laravel-afterburner/jetstream:1.0.0 # pin exact, not ^1.0
composer show laravel-afterburner/jetstream # read requires/autoload/description
cat vendor/laravel-afterburner/jetstream/composer.json # real PHP + Laravel constraints
ls vendor/laravel-afterburner/jetstream/ # src/, config/, database/, resources/, routes/
Then enumerate what it actually registers:
php artisan about # providers, drivers, config summary
php artisan list | grep -i afterburner
php artisan route:list --path=teams # confirm it hasn't shadowed Jetstream's routes
Read vendor/laravel-afterburner/jetstream/src/*ServiceProvider.php line by line before running any publish command. That file tells you the three things the README does not: which config keys exist, whether it augments Jetstream (middleware/actions) or replaces published scaffolding, and what it registers conditionally on config('jetstream.features').
Publish, then diff.
php artisan vendor:publish --provider="LaravelAfterburner\Jetstream\AfterburnerServiceProvider" --tag=config
php artisan vendor:publish --provider="LaravelAfterburner\Jetstream\AfterburnerServiceProvider" # then: git status
Run publish on a clean branch so git status shows you exactly which files are package-owned. That diff is your "do not hand-edit" list — commit it as a marker commit.
First use case: the teams + roles + audit loop. On a scratch app, do one vertical slice end to end:
If that loop works, the package is worth evaluating further. If any step requires editing a published file, note it — that's your merge cost.
Config-first, env-second. Every tuning knob should live in config/afterburner.php (or whatever the published file is named) with an env fallback. Resist the urge to hardcode role names or permission strings in Blade — put them in a Permissions enum or a config/permissions.php your team owns, and let the package seed from it. When the package's role model shifts in a 1.1, you change one file, not 40 views.
Authorization: three layers, one source of truth.
// Policy — the canonical check
public function update(User $user, Team $team): bool
{
return $user->hasTeamPermission($team, 'team:update'); // verify method name in vendor
}
$this->authorize('update', $team) or ->middleware('can:update,team').@can('update', $team). Never @if($user->role === 'owner').permissions map per team in HandleInertiaRequests so the front end can hide UI without a second round trip. Do not treat the client-side map as security — it's cosmetic.Team scoping is the #1 source of bugs. Always resolve the team from route-model binding, never from request input:
Route::get('/teams/{team}/projects', ...)->middleware(['auth', 'can:view,team']);
Then scope every downstream query: $team->projects(), not Project::all(). Add a scopeForTeam or a global scope if the package doesn't provide one. Test it explicitly with a second user's team ID in the URL — a 403 is the expected result.
Audit logging: log the decision, not the row. The audit trail is most useful when it captures intent (who changed what, why) rather than a raw model diff. Wrap mutating operations in a transaction and let the package's model events fire inside it:
DB::transaction(function () use ($team, $data) {
$team->update($data); // package observer logs it
activity()->performedOn($team)->log('team.renamed'); // explicit, if the package exposes a helper
});
Rules that pay off later:
auth()->id() into the job, or you get null actors.Extend Jetstream actions, don't fork them. If the package publishes app/Actions/Jetstream/* or hooks Fortify, put your logic in the action class or a listener on the domain event (TeamMemberAdded, UserUpdated), not in a controller. Overriding via a Fortify::authenticateUsing-style callback is safer than editing a published blade.
Testing pattern. Build a small trait so every team-aware test is one line:
protected function actingAsTeamMember(Team $team, array $permissions = []): User
{
$user = User::factory()->create();
$team->users()->attach($user, ['role' => $this->roleWith($permissions)]);
$this->actingAs($user->fresh())->withSession(['current_team_id' => $team->id]);
return $user;
}
Run the package's own tests against your Laravel/PHP matrix before trusting it:
vendor/bin/pest vendor/laravel-afterburner/jetstream/tests # path per repo layout
Day-to-day loop. git pull upstream → composer update laravel-afterburner/jetstream --with-dependencies (only after reviewing the diff against your marker commit) → re-publish to a temp dir and diff -r against your app → merge manually → run the team/role/audit slice test.
The ownership boundary is the whole game. With 0 stars, one release, and notes that are literally a changelog link, assume nothing about BC policy. Mitigate structurally:
"laravel-afterburner/jetstream": "1.0.0" exactly. No caret until you've survived two minors.UPGRADING.md line: "Files under X are package-owned; changes must go through a publish-diff-merge cycle."Silent scaffolding drift. Republishing over customized auth/team views is the highest-probability, highest-impact failure. Before any vendor:publish --force, run publish into a scratch path and diff. If the package overwrites resources/views/teams/*, prefer copying the view into your app's namespace and pointing the component at your copy.
Team context leaks across boundaries. Session-stored "current team," Team::current(), and cached permission maps all go stale:
team_id explicitly.forgetCachedPermissions() (or restart the worker/clear the cache) or the user keeps old rights until TTL.TeamSwitched-style events and refresh shared Inertia props there.Route collisions. This package "works alongside Jetstream and Fortify," which means both may register /user/profile, /teams/{team}, or two-factor-challenge. If route:list shows duplicates, the last-registered provider wins — check bootstrap/providers.php / config/app.php ordering. A php artisan route:list --path=teams -v with middleware columns tells you which one you actually hit.
Debugging recipes.
php artisan tinker → $user->hasTeamPermission($team, 'x'). If false, check the role→permission pivot, not the policy.Gate::before. Add one yourself in AuthServiceProvider and short-circuit it for your admin role — but log a warning if you do.DB::table()->update() or mass ->update() on query builders. Use Eloquent model saves, or log explicitly.DB::listen or Telescope. with('roles.permissions') on the team member listing is usually the fix.Extension points worth knowing. Published config (tune, don't fork), custom roles/permissions seeded from your own enum, Jetstream action classes, Fortify callbacks, model observers for audit, and policies for authorization. Everything else is package-owned — treat it as third-party code you read but don't edit.
On the version number. v1.0.0 means "the API froze," not "battle-tested." Budget an ongoing small maintenance line for republish/re-merge, watch the GitHub releases feed (the notes are a link, so automate it), and re-run the vertical slice test after every bump. If a Laravel major lands and the package lags, your fork point is the escape hatch — that's why you recorded it on day one.
How can I help you explore Laravel packages today?