Appearance
Installation
Requirements
- PHP 8.4 or newer
- Laravel 13 (the packages also accept Laravel 12, most of them 11)
- An admin panel: Filament 5 or Laravel Nova 5 (Nova needs a license)
- A database: SQLite, MySQL or MariaDB — or an existing WordPress database
- Node.js and npm for a Vue theme (the skeletons build with Vite 7); a Blade theme needs no build
Where the packages come from
kitloom packages are not on Packagist: they come from the kitloom Composer repository, https://packages.kitloom.dev, with the access from your purchase (see After your purchase). Every package is released at once, under one version — see the Changelog.
Starting from a skeleton
The fastest way to a working site is a skeleton: a plain Laravel 13 application with the kitloom core and the site route already wired, and the modules and themes you choose while it installs.
| Skeleton | Admin | Package |
|---|---|---|
| Filament skeleton | Filament 5 at /admin | kitloom/filament-skeleton |
| Nova skeleton | Nova 5 at /nova | kitloom/nova-skeleton |
With your access to the kitloom Composer repository saved (see After your purchase), one command creates the project:
bash
composer create-project kitloom/filament-skeleton my-site --repository=https://packages.kitloom.dev --add-repository
cd my-sitecreate-project installs the Composer packages, copies .env.example to .env, generates the key and links public/storage to the uploads. Then it asks what the site keeps:
- Themes, none ticked. A theme keeps the modules it needs and says so: Pantry keeps the shop, Atelier the bookings.
- The active theme, one of those you kept, or none: then the site answers with JSON, for a front end of your own.
- Modules, group by group — Content, SEO, Marketing, Sales, Accounts & security, AI & API, Maintenance. The usual ones are ticked, and those the active theme suggests: comments, widgets, sliders, forms, import and export, search and replace, custom fields, post types, SEO, redirections, two-factor sign-in, the REST API, AI agents and AI Writer, the admin bar, backups and site health. Space ticks and unticks, Enter goes on.
- Whether to fill the site with the active theme's demo — posts, pages, menus and widgets to see it with (
--demoor--no-demoanswer it without asking). - Whether to build the themes now (
npm install,npm run build:themes; Node 20 or later).
What you leave out is removed with composer remove, so your composer.json names exactly what the site runs. The tests are yours too: phpunit.xml runs the tests/Unit and tests/Feature suites, with an example test in each (the home page answers) — kitloom's own tests stay with the packages. APP_THEME in .env and in phpunit.xml names the active theme, or nothing when there is none. Then the migrations run (on SQLite by default; database/database.sqlite is created if missing) and the theme is activated — if it is built: a Vue theme you did not let it build waits for npm run build:themes, and APP_THEME shows it from then on. The core is always there: the WordPress data model, the site and its themes engine, settings, users and roles, media, menus, shortcodes, the Modules screen, the admin menu editor and the visual editor.
Without questions — a script, a CI job — pass the answers instead:
bash
php artisan kitloom:install --no-interaction # the ticked modules, no theme
php artisan kitloom:install --modules=seo,forms --theme=journal --build
php artisan kitloom:install --all --demo # keep everything; the theme APP_THEME names, with its demo(composer create-project … --no-interaction runs it the first way.) --all activates the theme APP_THEME names in .env (Portfolio in the skeletons), or the one --theme names. A module left out comes back later with composer require kitloom/<module> kitloom/<module>-filament (or -nova), and a theme with composer require kitloom/<name>-theme, npm run build:themes and php artisan site:theme <Name> or Appearance → Themes. Fill the site with a first administrator and demo content, and start it:
bash
php artisan db:seed
composer run dev # server, queue worker, logs and Vite togetherThe seeder creates the user admin with the email SEED_ADMIN_EMAIL ([email protected]) and the password SEED_ADMIN_PASSWORD (password) — set both in .env before seeding anything that is not your own machine. It then runs WordPressInstaller, the same code as php artisan wordpress:install: the options WordPress needs, its roles, and the first user as its administrator. On a site still without posts or pages come last a home page, an About page, a post in the News category, a main menu, a redirect and an entry in the 404 log (with Redirections kept) and a comment; the Nova skeleton adds a contact form and a Contacts page with it (with Forms kept). After a theme's demo the seeder adds the administrator only, and the theme's front page, pages and menus stay as they are.
A site without the demo content — or on a server, where composer install --no-dev leaves out the Faker the seeder's factory needs — gets its first administrator from php artisan kitloom:user. It asks for the email and the password, and makes the user an administrator of the site and of a WordPress on the same database (--login, --name, --role and --password for scripts; see Deployment).
Open http://localhost:8000 for the site and /admin (Filament) or /nova (Nova) for the admin panel.
APP_URL before the demo
APP_URL in .env is http://localhost:8000, the address of php artisan serve. Set it to the site's real address before the install fills the site: WordPress's home and siteurl options are written from it, and a theme's demo writes links and image addresses into the content. A site that moves later changes them with Search & Replace, as WordPress would.
Every theme brings demo content of its own, with a <name>:demo command named in extra.kitloom.theme.demo of its composer.json; kitloom:install --demo runs the active theme's. To run one by hand:
bash
php artisan portfolio:demo # Portfolio (Vue): disciplines, case studies, Work, Studio and Contact pages
php artisan marketplace:demo # Marketplace (Blade): the shop's demo products, pages, a contact form, a blog, menus and widgets
php artisan game-reviews:demo # Game Reviews (Vue): genres, platforms, reviews and player votesphp artisan shop:demo (kitloom/shop) adds only a demo shop — categories, products with reviews, orders, the shop, cart and checkout pages — whatever the theme.
npm run build builds the theme APP_THEME names in .env (the shell's APP_THEME wins); with no theme, or a Blade one, Vite has nothing to build and says so, and composer run dev goes on without it. npm run build:themes builds every installed Vue or React theme, each into its own public/build-{theme}. A Vue or React theme that is not built cannot be activated. See Installing Themes.
Adding kitloom to an existing application
1. Require the packages
Add the kitloom repository first — see Installing from a repository. Then the core: the WordPress data model, the settings, the public site, the Modules screen and roles.
bash
composer require kitloom/wp-schema kitloom/settings kitloom/site \
kitloom/modules kitloom/permissionsThen the admin packages of your panel:
bash
composer require filament/filament:"^5.0" \
kitloom/wp-schema-filament kitloom/settings-filament \
kitloom/site-filament kitloom/modules-filament kitloom/permissions-filamentbash
composer require laravel/nova:"^5.0" \
kitloom/wp-schema-nova kitloom/settings-nova \
kitloom/site-nova kitloom/modules-nova kitloom/permissions-novaEvery package registers its own service provider through Laravel's package discovery. There is nothing to add to bootstrap/providers.php.
Roles
An admin section asks the Gate whether the user may open it. Without kitloom/permissions nobody has decided, and kitloom answers no (KITLOOM_UNDECIDED_ABILITIES=deny): the admin stays closed rather than open to every signed-in user. Keep the roles module, or set the variable to allow for a site that runs without roles on purpose.
2. Users are WordPress's
Accounts are rows of WordPress's users table, shared with WordPress: sign-in by email or login name, WordPress's password hashes. The wordpress user provider and hash driver come with kitloom/wp-schema; the application picks them:
php
// config/auth.php (php artisan config:publish auth)
'providers' => [
'users' => [
'driver' => 'wordpress',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
],ini
# .env
HASH_DRIVER=wordpressThe user model reads that table and holds the user's roles:
php
// app/Models/User.php
use Illuminate\Foundation\Auth\User as Authenticatable;
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 HasCustomFields, HasMetaTable
{
use AuthenticatesAsWordPressUser, HasRoles, IsWordPressUser;
}AuthenticatesAsWordPressUser maps what Laravel and the panels call name, email and password to display_name, user_email and user_pass; the key is ID.
3. The database
Run the migrations:
bash
php artisan migratekitloom/wp-schema creates WordPress's core tables — users, posts, comments, terms, their meta, options and links — column for column as WordPress defines them. A table that already exists is left alone. Modules add the tables they need on top.
An application that already has Laravel's own users table gets its accounts moved into WordPress's table by the move_users_to_the_wordpress_table migration; they keep their IDs. Back up the database first. With kitloom/permissions, the first user becomes the administrator unless someone already holds that role. A database with no users yet gets its first administrator from php artisan kitloom:user.
To run on an existing WordPress database instead, see Running on a WordPress database.
4. Route the site
Every address that is not one of your own routes is a page of the site: a post, an archive, the search, a 404. Hand them to the site controller as the fallback route, with the page cache in front:
php
// routes/web.php
use Kitloom\Site\Http\CachePage;
use Kitloom\Site\Http\SiteController;
Route::fallback(SiteController::class)->middleware(CachePage::class);Your own routes still win. The same address answers a browser with the rendered page and a request with Accept: application/json with the page's data. The cache keeps pages for guests while it is switched on in Settings → Cache; SITE_CACHE_TTL is only its default (on with that many seconds, off at 0). kitloom/site also brings POST /login and POST /logout for the theme's own sign-in form, and the REST root /wp-json/. See Architecture.
5. Hook in the Modules screen
To let administrators switch modules off, add one line to bootstrap/app.php, after create():
php
use Kitloom\Modules\Modules;
$app = Application::configure(basePath: dirname(__DIR__))
// ...
->create();
// Modules deactivated on the Modules screen are not loaded.
Modules::bootstrap($app);
return $app;Without this line the Modules screen still lists modules, but says that switching has no effect. See Enabling & Disabling.
6. Your own models
kitloom ships a model for every WordPress table. When the site needs its own behaviour, it extends the package model and lists the class in config/kitloom-wp-schema.php:
php
// app/Models/Post.php
use Kitloom\WpSchema\Models\Post as WordPressPost;
class Post extends WordPressPost
{
//
}php
// config/kitloom-wp-schema.php
return [
'models' => [
'post' => App\Models\Post::class,
'term_taxonomy' => App\Models\TermTaxonomy::class,
'user' => App\Models\User::class,
],
];Models you do not list stay the package's own. Relations inside the packages take the class from this registry, so $category->posts returns your Post. A module that only needs a relation or a scope on a model it does not own uses WpSchema::extend() instead of a subclass — see Data & Models.
7. The admin panel
The admin is open to 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
// app/Models/User.php
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Kitloom\Permissions\AdminAccess;
class User extends Authenticatable implements FilamentUser, HasCustomFields, HasMetaTable
{
public function canAccessPanel(Panel $panel): bool
{
return app(AdminAccess::class)->allows($this);
}
}php
// app/Providers/NovaServiceProvider.php
protected function gate(): void
{
Gate::define('viewNova', fn (User $user): bool => app(AdminAccess::class)->allows($user));
}kitloom sections attach themselves to every Filament panel (Panel::configureUsing()) or to Nova (Nova::serving()). The Filament skeleton also uses kitloom's sign-in, registration and profile pages — Kitloom\WpSchema\Filament\Auth\Login, Register and Kitloom\WpSchema\Filament\Users\Profile — on its panel. See Filament and Nova.
8. A theme
Without a theme every page is returned as JSON — a headless site. To render HTML, install a theme:
bash
composer require kitloom/blade-theme # Blade, no JavaScript build
# or a Vue theme, which brings kitloom/site-inertia:
composer require kitloom/portfolio-themeThe Blade engine is part of kitloom/site. Vue themes need kitloom/site-inertia (Inertia 3 on the server, @inertiajs/vue3 3 in the browser) and a Vite build per theme. Set APP_THEME to the theme's name, or pick it on Appearance → Themes. See Installing Themes.
Running on a WordPress database
Point a connection at the WordPress database and give Laravel its table prefix. The prefix is read by the connection's prefix key; the skeletons' config/database.php reads it from DB_PREFIX for MySQL and MariaDB:
ini
DB_CONNECTION=mysql
DB_DATABASE=wordpress
DB_PREFIX=wp_php
// config/database.php, in the mysql (or mariadb) connection
'prefix' => env('DB_PREFIX', ''),Laravel adds the prefix to every table, so posts is wp_posts, also in kitloom's raw SQL. Run php artisan migrate once: WordPress's tables are already there and are left alone; modules add the tables they need. Back up the database first, as with any migration on a live site — and try it on a copy before: Migrate from WordPress goes through the move step by step.
The other way round works too. A database the site started on gets what WordPress's installer would leave — the siteurl, home, blogname and other options, WordPress's roles, and the first user as its administrator — with:
bash
php artisan wordpress:install --url=https://example.com --title="My site" [email protected]Without options it takes APP_URL, APP_NAME and the first user's email. Nothing already there is changed, so it can run again. After it, WordPress itself runs on the same database.
Installing from a repository
The kitloom packages come from the kitloom Composer repository. With the access from your purchase saved once per machine (see After your purchase), add it to an application that did not start from a skeleton:
bash
composer config repositories.kitloom composer https://packages.kitloom.dev
composer require kitloom/redirects kitloom/redirects-filamentComposer writes ^1.0, the version every kitloom package is released under, and composer update moves them together. The Nova packages also need Nova's own repository, https://nova.laravel.com, with your license.
Packages of your own in development
A module of your own is linked into the application from a path repository while you work on it. kitloom:make-module adds one for packages/ to composer.json:
json
"repositories": [
{ "type": "path", "url": "packages/*", "options": { "symlink": true } }
]Its packages have no version yet, so the application requires them as @dev; the kitloom packages they require still come from the kitloom repository. See Publishing a Module for when they leave the application.
Next steps
- Configuration — environment variables, settings and admin switches.
- Deployment — the site on a server: DigitalOcean step by step, and other hosts.
- Architecture — how a request becomes a page.
- Installing Modules.