Appearance
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:
Loginlinks to sign-up only while Anyone can register is on;Registerexists only while that option is on, aswp-login.php?action=registerdoes; the new user gets the default role chosen on Settings → General;Profileis 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());Menu order
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:
- its screens — resources and pages;
- Settings — the module's settings page, in the module's own section (never under the shared Settings);
- 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 migrateThe 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.
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-filamentThe 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
| Section | Package |
|---|---|
| Posts (All Posts, Add New Post, Categories, Tags), Pages, Comments | kitloom/wp-schema-filament |
| Settings → General, Writing, Permalinks; Appearance → Widgets | kitloom/wp-schema-filament |
| Users → Profile; the kitloom dashboard widgets | kitloom/wp-schema-filament |
| Users → All Users | the application's own UserResource (in the skeleton) |
| Users → Roles; the role on the user form, the role column and tabs | kitloom/permissions-filament |
| Media → Library, Add New Media File; Settings → Media; the library as attachment picker | kitloom/media-filament |
| Appearance → Menus, Menu Locations | kitloom/navigation-filament |
| Appearance → Themes; Settings → Reading, Privacy, Cache | kitloom/site-filament |
| Tools → Shortcodes | kitloom/shortcodes-filament |
| Modules → Installed Modules; the How it works pages | kitloom/modules-filament |
| Appearance → Admin Menu | kitloom/admin-menu-filament |
| Custom Fields → Post Types, Taxonomies; a section per type | kitloom/post-types-filament |
| The visual editor | kitloom/visual-editor-filament |
| Module sections | see the catalog |