Skip to content

Filament ​

Introduction ​

Filament is one of kitloom's two admin panels. Every module has a -filament package, and the panel is laid out like wp-admin: Posts, Media, Pages, Comments, Appearance, Users, Tools, Settings — with module sections slotted in where WordPress plugins would put them.

Requirements: Filament 5 (Livewire 4), Laravel 12 or 13. The Filament skeleton runs on Laravel 13.

Setting up the panel ​

kitloom works with the panel your application already has. The AdminPanelProvider of the Filament skeleton:

php
use App\Filament\Resources\Users\UserResource;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\AuthenticateSession;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages\Dashboard;
use Filament\Panel;
use Filament\Support\Colors\Color;
use Filament\Widgets\AccountWidget;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
use Kitloom\WpSchema\Filament\Auth\Login;
use Kitloom\WpSchema\Filament\Auth\Register;
use Kitloom\WpSchema\Filament\Users\Profile;

public function panel(Panel $panel): Panel
{
    return $panel
        ->default()
        ->id('admin')
        ->path('admin')
        ->login(Login::class)
        // Sign-up exists only while Settings → General → Membership allows it.
        ->registration(Register::class)
        ->profile(Profile::class, isSimple: false)
        ->navigationItems([UserResource::createNavigationItem(), Profile::navigationItem()])
        ->colors([
            'primary' => Color::Amber,
        ])
        ->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
        ->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
        ->pages([Dashboard::class])
        ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
        // kitloom/wp-schema-filament adds its own widget with links to kitloom.dev.
        ->widgets([AccountWidget::class])
        ->middleware([
            EncryptCookies::class,
            AddQueuedCookiesToResponse::class,
            StartSession::class,
            AuthenticateSession::class,
            ShareErrorsFromSession::class,
            PreventRequestForgery::class,
            SubstituteBindings::class,
            DisableBladeIconComponents::class,
            DispatchServingFilamentEvent::class,
        ])
        ->authMiddleware([
            Authenticate::class,
        ]);
}

This is Filament's default panel with three kitloom pages; UserResource::createNavigationItem() is the skeleton's own Users → Add New User link. PreventRequestForgery is Laravel 13's CSRF middleware; on Laravel 12 the same place holds VerifyCsrfToken.

Login, Register and Profile come from kitloom/wp-schema-filament and are optional:

  • Login links to sign-up only while Anyone can register is on;
  • Register exists only while that option is on, as wp-login.php?action=register does; the new user gets the default role chosen on Settings → General;
  • Profile is Users → Profile, the WordPress profile screen: names, contact info and biography, open to every role that gets into the admin. Module blocks follow below it.

Who may sign in ​

The User model implements Filament\Models\Contracts\FilamentUser. The panel lets in whoever has something to do in it — a permission beyond reading the site (Kitloom\Permissions\AdminAccess). Customers and subscribers stay out, as WooCommerce keeps them out of wp-admin, and use their account on the site:

php
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Kitloom\Permissions\AdminAccess;
use Kitloom\Permissions\Concerns\HasRoles;
use Kitloom\WpSchema\Contracts\HasCustomFields;
use Kitloom\WpSchema\Contracts\HasMetaTable;
use Kitloom\WpSchema\Models\Concerns\AuthenticatesAsWordPressUser;
use Kitloom\WpSchema\Models\Concerns\IsWordPressUser;

class User extends Authenticatable implements FilamentUser, HasCustomFields, HasMetaTable
{
    use AuthenticatesAsWordPressUser, HasFactory, HasRoles, IsWordPressUser, Notifiable;

    public function canAccessPanel(Panel $panel): bool
    {
        return app(AdminAccess::class)->allows($this);
    }
}

What each screen then allows is decided by permissions.

The panel sees every record ​

The site's queries see what a visitor sees: published content. kitloom/wp-schema-filament adds a persistent middleware to every panel that switches the request to Visibility::everything(), so lists, pickers and forms in the admin show drafts, private posts and the trash. What a user may do with each row is still asked of their capabilities.

How modules attach ​

Each -filament package plugs itself in from its service provider with Panel::configureUsing(). The callback runs for every panel as it is built, so a section appears without touching your panel provider:

php
use Filament\Panel;

Panel::configureUsing(function (Panel $panel): void {
    if (! (bool) $this->app->make('config')->get('kitloom-redirects.filament.register', true)) {
        return;
    }

    $panel->plugin(RedirectsPlugin::make());
});

Every package has such a switch, kitloom-<name>.filament.register — see Configuration.

kitloom plugins are idempotent: a plugin may arrive twice — on its own and from your panel provider — and registers its resources and pages once. To decide yourself where a module goes, for example on one of several panels, switch its automatic registration off and add the plugin to the panel you want:

php
// config/kitloom-redirects.php
return [
    'filament' => ['register' => false],
];
php
use Kitloom\Redirects\Filament\RedirectsPlugin;

return $panel
    // ...
    ->plugin(RedirectsPlugin::make());

Sections follow WordPress order: Posts, Media, Pages, Comments, Appearance, Users, Tools, Settings, then the plugin-like sections Contact, SEO and Custom Fields. The order is held by Kitloom\WpSchema\Filament\MenuOrder, and a module puts its own section after an existing one from its Filament provider:

php
use Kitloom\WpSchema\Filament\MenuOrder;

$this->app->afterResolving(MenuOrder::class, fn (MenuOrder $order) => $order->after('Reviews', 'Comments'));

Sections nobody placed come after all of these. Empty sections are not shown.

A module's own section ​

Each module has one section, named after the module, laid out the same way:

  1. its screens — resources and pages;
  2. Settings — the module's settings page, in the module's own section (never under the shared Settings);
  3. How it works — last: the README of the module's domain package as an admin page.

The last item is Kitloom\WpSchema\Filament\Navigation\ModuleHelpItem. It links to /admin/modules/{vendor}/{name}, served by kitloom/modules-filament, and hides itself when that package is not installed, the module has no README or KITLOOM_MODULES_MENU_HELP is false (see Enabling & Disabling):

php
use Kitloom\WpSchema\Filament\Navigation\ModuleHelpItem;

$panel->navigationItems([
    ModuleHelpItem::make('acme/reviews', ReviewsPlugin::GROUP, fn (): bool => ReviewResource::canViewAny()),
]);

The third argument says who sees the item — whoever sees the rest of the section.

Letting administrators rearrange the menu ​

The navigation Filament builds from the resources and pages is a starting point, not the final menu. The Admin Menu module (kitloom/admin-menu with kitloom/admin-menu-filament) adds Appearance → Admin Menu (/admin/admin-menu), where an administrator arranges it without touching code:

  • reorder sections, and the items inside them, by dragging;
  • move an item into another section;
  • rename a section or an item;
  • set an icon on an item (a Heroicon name);
  • hide a section or an item the site does not use — the page still opens by its address;
  • restrict a section or an item to permissions, so an editor's menu holds only what an editor does;
  • Reset to what the packages build.

The edits apply on top of that menu, in every panel. Items of a newly installed module go after the placed ones until someone places them. The Nova screen adds sections and links of your own as well; see Admin Menu.

bash
composer require kitloom/admin-menu kitloom/admin-menu-filament
php artisan migrate

The edits are stored in the menu_overrides table, which the Nova screen shares (a row per entry, per admin). The screen is for those who may manage_options. kitloom-admin-menu.filament.register leaves the screen out while the saved menu still applies; kitloom-admin-menu.filament.navigation_group (Appearance) and navigation_sort (5) move it.

Form sections ​

Modules add blocks to other resources' forms — SEO on posts, custom fields on posts, terms and users, the role on users — through Kitloom\WpSchema\Filament\Forms\FormSection implementations tagged FormSections::TAG. A section says which resources it appliesTo(), gives a title() and its components(); the fields write their values themselves, usually to meta. kitloom's own resources show them automatically.

Your own resources can show them too — the skeleton's UserForm does:

php
use Kitloom\WpSchema\Filament\Forms\FormSections;

public static function configure(Schema $schema): Schema
{
    return $schema->components([
        // your fields…
        ...app(FormSections::class)->sectionsFor(UserResource::class),
    ]);
}

Each block comes as a collapsible section; a block without fields for that resource is left out. See Extension Points › Blocks on the post form.

Settings pages ​

kitloom/settings-filament provides Kitloom\Settings\Filament\SettingsPage: a Filament page whose field names are rows of the options table (wp_options on a WordPress database). A subclass only declares fields; a row that does not exist yet shows the field's default and is created on the first save. Values are written through SettingWriter, which checks a declared setting's type and stores arrays as JSON.

A module's settings page lives in the module's own section, labelled Settings:

php
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Kitloom\Settings\Filament\SettingsPage;

class ReviewsSettings extends SettingsPage
{
    protected static ?string $slug = 'options-reviews';

    protected static ?int $navigationSort = 90;

    public static function getNavigationGroup(): ?string
    {
        return __('Reviews');
    }

    public static function getNavigationLabel(): string
    {
        return __('Settings');
    }

    protected function settingsFields(): array
    {
        return [
            Toggle::make('reviews_moderation')->label(__('Hold new reviews for moderation'))->default(true),
            TextInput::make('reviews_per_page')->integer()->default(10),
        ];
    }
}

The page asks the Gate about itself (Gate::allows(static::class)), so declare a permission that covers it; a page nobody declared needs manage_options. On the site the values are read with the Settings facade — Settings::get('reviews_per_page', 10) — through the same cached store every module uses.

Custom post types ​

Post types and taxonomies defined on Custom Fields → Post Types and Custom Fields → Taxonomies (kitloom/post-types-filament, the Post Types module) get admin sections at once, without generated classes: each type is a Filament resource configuration at /admin/types/{type}, each taxonomy one at /admin/taxonomies/{taxonomy}. A type's section takes its menu name and sits after Comments; a taxonomy goes into the section of its first post type. As in WordPress, a type without show_ui gets no section.

A new type shows up from the next request. A type that already has a resource — the site's own or another package's, extending Kitloom\WpSchema\Filament\Resources\PostTypeResource — is edited by that resource instead.

Revisions ​

A record's edit page has Revisions (N) while there is a saved version to go back to — for every type that keeps revisions, and every edit page built on EditPostRecord. It opens a panel: pick a version (when, by whom, "AI agent" for an agent's), see line by line what restoring it would change — title, content, excerpt, custom fields, SEO meta — and Restore This Revision. The restore is saved as the newest version, so it can be undone the same way. See AI Agents → Safety.

Revisions

Visual editor ​

Post and page text, the product description and WYSIWYG custom fields take their editor from the Kitloom\WpSchema\Filament\Contracts\HtmlEditor contract. By default it is a plain text area: the html as stored. kitloom/visual-editor-filament binds TinyMCE 6 instead:

bash
composer require kitloom/visual-editor-filament

The editor keeps WordPress markup — block comments, shortcodes, attributes — and a text nobody edited goes back byte for byte. With kitloom/media it has Add Media, which opens the media library window when kitloom/media-filament is installed. config/kitloom-visual-editor.php sets the height, the site's styles inside the editor and TinyMCE options; VISUAL_EDITOR=false brings the text area back. The editor's script is published by php artisan filament:assets, which filament:upgrade runs after composer install.

The package stays on TinyMCE 6, the last MIT-licensed version. To use another editor, bind HtmlEditor in your application's provider — see Extension Points › The HTML editor.

Media library and attachment picker ​

kitloom/media-filament adds Media → Library and Add New Media File, and Settings → Media (image sizes, month folders). A library entry is the WordPress attachment itself, so files uploaded by WordPress or by the Nova admin are there too.

Every field that picks files — a post's Featured Image, image, file and gallery custom fields, a category thumbnail, a slide, the image widget — is the Kitloom\WpSchema\Filament\Contracts\AttachmentPicker. Without the media admin it is a searchable list of attachment titles; kitloom/media-filament binds the library window with search, uploads and previews, and post lists then open each row with the featured image. kitloom-media.filament.picker set to false keeps the plain list; kitloom-media.filament.register turns the section off.

The featured image is stored as WordPress keeps it: the attachment id in _thumbnail_id.

Two-factor sign-in ​

The Two-Factor Authentication module (kitloom/two-factor with kitloom/two-factor-filament) runs on Filament's own multi-factor authentication; nothing is added to the panel provider. After the password the panel asks for a code from an authenticator app, a code mailed to the user or a recovery code. Users set it up on Users → Profile; whoever may edit another user can reset their factors on that user's screen. Two-Factor Authentication → Settings (/admin/options-two-factor) picks the roles that must use it and the methods on offer. The factors are user meta, so the users table gets no columns.

What's where ​

SectionPackage
Posts (All Posts, Add New Post, Categories, Tags), Pages, Commentskitloom/wp-schema-filament
Settings → General, Writing, Permalinks; Appearance → Widgetskitloom/wp-schema-filament
Users → Profile; the kitloom dashboard widgetskitloom/wp-schema-filament
Users → All Usersthe application's own UserResource (in the skeleton)
Users → Roles; the role on the user form, the role column and tabskitloom/permissions-filament
Media → Library, Add New Media File; Settings → Media; the library as attachment pickerkitloom/media-filament
Appearance → Menus, Menu Locationskitloom/navigation-filament
Appearance → Themes; Settings → Reading, Privacy, Cachekitloom/site-filament
Tools → Shortcodeskitloom/shortcodes-filament
Modules → Installed Modules; the How it works pageskitloom/modules-filament
Appearance → Admin Menukitloom/admin-menu-filament
Custom Fields → Post Types, Taxonomies; a section per typekitloom/post-types-filament
The visual editorkitloom/visual-editor-filament
Module sectionssee the catalog

kitloom — modules and themes for Laravel.
Pricing · Services · License · Refunds · Privacy · Community · [email protected]