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 Sanctum Laravel Package

devtical/filament-sanctum

Manage Laravel Sanctum API tokens directly from your Filament panel. Create, list, and revoke personal access tokens, define custom abilities and expiration presets, restrict access with gates, and display the page in the sidebar or user menu.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the package and Sanctum together — they're listed as one require in the README, and the package is useless without Sanctum's table:

composer require devtical/filament-sanctum laravel/sanctum

2. Publish and run Sanctum's migrations — skip this if the app already issues API tokens:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

3. Add HasApiTokens to the model that authenticates into your panel. For most apps that's App\Models\User:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
}

If your panel authenticates a different model (e.g. Admin), the trait goes there — the package renders the page for whatever the panel's authenticated user is.

4. Register the plugin in the panel provider — this is the only wiring step that touches Filament:

use Devtical\Sanctum\SanctumPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(SanctumPlugin::make());
}

5. Publish the config (you will edit it, so don't skip):

php artisan vendor:publish --tag=filament-sanctum-config

6. First use case — end to end in five minutes. Log into /admin, open the user menu (top right), click API Tokens, create a token, and copy the plaintext value from the one-time modal. Verify it works:

curl -H "Authorization: Bearer 1|abc123..." https://app.test/api/user

That round trip is the whole value proposition: a non-technical admin can now mint and revoke their own API credentials without a developer running php artisan tinker.

Where to look first, in order:

  1. config/filament-sanctum.php — everything tunable lives here: navigation, abilities, expiration, authorization.
  2. The panel provider — confirm the plugin is registered on the right panel(s).
  3. vendor/devtical/filament-sanctum/src/ — the surface is small (a page/resource class plus the plugin and a Sanctum URL helper). When the README is silent on a behavior, read the class rather than guessing.
  4. The changelog — the README lags the code on supported versions (see Gotchas).

Implementation Patterns

Pattern: pick navigation placement per panel, deliberately

Default is the user menu, which is the right call for most apps — it signals "these are my credentials." Sidebar placement suggests a management surface, which invites the question "whose tokens am I managing?"

// config/filament-sanctum.php
'navigation' => [
    'slug' => 'sanctum',
    'sidebar_menu' => ['enabled' => true],
    'user_menu'     => ['enabled' => false],
],

If you run multiple panels, remember the config is global — registering the plugin on both an admin panel and a customer-facing portal gives both panels the same slug, abilities list, and gate. If they need to diverge, that's a fork or a service-provider-level config override, not a plugin option.

Pattern: abilities as the contract between panel and API

This is the highest-leverage config block. Define scopes that mirror the middleware on your API routes, and label them in the user's language:

'abilities' => [
    'columns' => 4,
    'allow_expiration' => true,
    'default_expiration_days' => 30,
    'expiration_presets' => [7, 30, 60, 90],
    'list' => [
        'posts:read'   => 'Read posts',
        'posts:write'  => 'Write posts',
        'webhooks:manage' => 'Manage webhooks',
    ],
],

Then enforce them server-side — the package's UI is advisory, your routes are the actual boundary:

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/posts', [PostController::class, 'index'])
        ->middleware('abilities:posts:read');
    Route::post('/posts', [PostController::class, 'store'])
        ->middleware('abilities:posts:write');
});

Keep the ability list and the middleware list in one place (a config or enum) so a scope can't exist in the UI without a corresponding guard. An ability offered in the picker but never checked is a false promise; one checked but not offered is a support ticket.

Pattern: expiration as a policy, not a default

Three settings interact here, and they should agree:

  • allow_expiration / default_expiration_days / expiration_presets — the package UI and the expires_at it writes at creation time.
  • config/sanctum.php → 'expiration' — Sanctum's own enforcement and its own default at creation.

Set the Sanctum expiration to at least as strict as the shortest preset you offer. If Sanctum's expiration is null and the package writes no expires_at, you've shipped immortal tokens with a UI that implied otherwise. For third-party integrations especially, prefer a finite default (30–90 days) plus a visible "last used" column to tell you which tokens are actually alive.

Pattern: gate authorization, then verify scoping

// config/filament-sanctum.php
'authorization' => [
    'enabled' => true,
    'gate' => 'manage-api-tokens',
],
// AppServiceProvider::boot()
Gate::define('manage-api-tokens', fn (User $user) => $user->isAdmin());

The gate answers "can this user reach the page." It does not answer "which rows do they see." For multi-tenant or mixed-privilege panels, do the second half explicitly:

  • If each user should only manage their own tokens, the gate is not the control you want — it's a page-level allow/deny. Confirm against the source how the listing is scoped before enabling in production, and test with two real users: user A must not see user B's token names, and more importantly must not be able to revoke them.
  • For support/admin flows where staff should see everyone's tokens, keep the gate narrow (isAdmin()), and log revocation actions.

Pattern: pair with Sanctum::actingAs in tests

You don't need to click through the panel to test your API. Once abilities are wired:

Sanctum::actingAs($user, ['posts:read']);
$this->getJson('/api/posts')->assertOk();

Sanctum::actingAs($user, []);
$this->postJson('/api/posts')->assertForbidden();

Reserve panel-level feature tests (Filament's livewire()/callAction() helpers) for the create-and-revoke flow itself — the package ships its own tests for that, so your coverage should focus on your gate and your scoping.

Pattern: don't use the panel for machine tokens

Service accounts, CI, and long-lived integrations shouldn't be minted through a human's user menu — the token would be attached to that person, and it dies when their account is deactivated. Issue those via a console command or an admin-only Filament custom page, and use this package for what it's good at: self-service credentials for humans.


Gotchas and Tips

The plaintext token is shown exactly once. Sanctum stores only a SHA-256 hash — there is no "reveal" and no recovery, only revoke-and-recreate. If your support docs don't already say "copy it now," they need to.

Revocation is destructive and non-recoverable. Combined with the fact that the page reads the existing personal_access_tokens table, this means every token ever issued in your app appears in the panel the first time the plugin is enabled, revocable by anyone who can reach it. That's usually what you want, but audit first: check personal_access_tokens for tokens belonging to service accounts or legacy integrations, and make sure the gate is on before you deploy rather than after.

A misconfigured gate is a security incident, not a bug. The package doesn't decide visibility for you. Shipping with authorization.enabled = false on a panel that non-admins can log into means any panel user can list and revoke tokens. Treat "gate defined + verified with a non-admin user + listing confirmed scoped" as part of the definition of done, not a follow-up ticket.

Pin the version and record the tested matrix. The docs disagree with themselves: v1.1.0's changelog claims Filament 5 / Laravel 13 support, v1.0.0 claims Filament 3/4 + Sanctum v4, and the README still describes the older flow. Pre-1.0 pins won't work on a Filament 4/5 panel. Pin an exact version in composer.json and note the verified combination in your repo — this package's README is not a reliable version oracle.

Require v1.1.0 or later if you're not on a root-path panel. The duplicate-path bug (/admin/admin/sanctum) in the user menu was fixed in v1.1.0, as was an abilities-column rendering bug that repeated "None" / "5 abilities". Both are exactly the kind of thing that costs an afternoon of debugging if you assume your config is wrong.

last_used_at is lazy. Sanctum updates it on authenticated requests, so a token that hasn't been used since the timestamp was introduced may read as never-used. Don't delete tokens purely on a "never used" sort without checking with the integrator.

Clear caches after every config change. The config is read at panel boot. php artisan config:cache (or php artisan optimize:clear in dev) between edits, or you will spend time debugging a change that simply hasn't been loaded.

Publishing translations freezes them. --tag=filament-sanctum-translations copies EN/ID into lang/vendor/...; after that, package updates won't reach your files. Only publish if you're actually editing (the package ships EN and ID only), and add your own locale by copying an existing file.

Wildcard abilities. If the abilities list is empty or a token is created with ['*'], that token bypasses abilities: middleware entirely. In the UI, make sure at least one ability is selected by default, or you've quietly created an admin-level credential with a friendly name.

Config is the extension point; beyond it, read the source or fork. There's no documented hook for customizing the page's table columns or adding fields to the create form. The code surface is small (page/resource + plugin + a Sanctum::getUrl() helper), so a subclass or a fork is a day's work at most — but budget for it rather than assuming a config key exists. Zero dependents and a single maintainer means you should be comfortable owning that fork if you need it.

Debugging checklist when the page doesn't appear: plugin registered on this panel → HasApiTokens on this panel's user model → gate returns true for the current user → config cache cleared → version pinned to one that supports your Filament major. That ordering resolves nearly every "I installed it and nothing happened" report.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource