Quickstart
From an empty directory to the LeanShell with the SDK's own apps — the Launchpad, Partners and Settings — on a fresh Laravel install, in about fifteen minutes. Every command below is meant to be copied as it is.
You need PHP 8.4, Composer, and an install token for an installation that is entitled to the SDK. The fresh Laravel app uses SQLite, so there is no database server to set up.
Reading the docs is free. Installing the SDK needs a licence: buying it creates a new installation, and a token minted on that installation installs both laravelui5/core and laravelui5/sdk.
1. Create a Laravel app
composer create-project laravel/laravel acme
cd acme2. Install the SDK and a login
composer config repositories.pragmatiqu composer https://packages.pragmatiqu.io
composer config --global http-basic.packages.pragmatiqu.io your-email@example.com YOUR-INSTALL-TOKEN
composer require laravelui5/sdk laravelui5/authThe SDK pulls in Core and OData. laravelui5/auth (MIT, from Packagist) gives you a login screen — the SDK's apps run for signed-in users only.
3. Set up Core
Swap in the UI5-aware CSRF middleware in bootstrap/app.php:
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use LaravelUi5\Core\Http\Middleware\VerifyCsrfToken;
// …
->withMiddleware(function (Middleware $middleware) {
$middleware->web(replace: [
PreventRequestForgery::class => VerifyCsrfToken::class,
]);
})Publish Core's config and views:
php artisan vendor:publish --tag=ui5-config
php artisan vendor:publish --tag=ui5-viewsIn resources/views/ui5/head.blade.php uncomment @includeIfSdk('ui5::head'), and in resources/views/ui5/foot.blade.php uncomment @includeIfSdk('ui5::foot'). These two lines put the shell around your apps.
4. Point Core at the SDK
In config/ui5.php, set these keys:
'registry' => \LaravelUi5\Sdk\Platform\SdkRegistry::class,
'context_factory' => \LaravelUi5\Sdk\Platform\Context\SdkUi5ContextFactory::class,
'middleware' => [
'web',
\LaravelUi5\Core\Http\Middleware\ResolveUi5Context::class,
\LaravelUi5\Core\Http\Middleware\EnsureUi5Authenticated::class,
\LaravelUi5\Sdk\Http\Middleware\CheckAuthMiddleware::class,
],
'odata_middleware' => [
'web',
\LaravelUi5\Core\Http\Middleware\FetchCsrfToken::class,
\LaravelUi5\Core\Http\Middleware\ResolveODataEndpoint::class,
\LaravelUi5\Core\Http\Middleware\EnsureODataAuthenticated::class,
\LaravelUi5\Sdk\Http\Middleware\BindSdkContextForOData::class,
],
'artifact_resolvers' => [
\LaravelUi5\Core\Runtime\PathBasedArtifactResolver::class,
\LaravelUi5\Sdk\Platform\Context\ShellContextArtifactResolver::class,
\LaravelUi5\Sdk\Export\ExportArtifactResolver::class,
],Configuration explains each one.
5. Name the home route
After logout, laravelui5/auth sends you to a route named home. Give Laravel's welcome route that name in routes/web.php:
Route::get('/', function () {
return view('welcome');
})->name('home');After login it sends you to dashboard. The SDK provides that route, and it lands on the Launchpad.
6. Connect users to partners
The SDK authorizes partners, not user accounts. Link each login to its partner:
php artisan make:migration add_partner_id_to_users_table --table=userspublic function up(): void
{
Schema::table('users', function (Blueprint $table) {
// unique — a partner holds at most one login
$table->unsignedInteger('partner_id')->nullable()->unique()->after('id');
$table->foreign('partner_id')->references('id')->on('sdk_partners')->nullOnDelete();
$table->unsignedInteger('login_counter')->default(0);
$table->boolean('is_locked')->default(false);
$table->dateTime('last_login')->nullable();
$table->boolean('must_change_password')->default(false);
});
}Then let app/Models/User.php implement the SDK's contract:
use LaravelUi5\Sdk\Partners\Contracts\HasPartnerInterface;
use LaravelUi5\Sdk\Partners\Traits\HasPartner;
class User extends Authenticatable implements HasPartnerInterface
{
use HasPartner;
// …
}Make partner_id fillable, and cast the four new columns. Add these lines to what the model already has:
protected $fillable = [
// 'name', 'email', 'password',
'partner_id',
];
protected function casts(): array
{
return [
// 'email_verified_at' => 'datetime', 'password' => 'hashed',
'login_counter' => 'integer',
'is_locked' => 'boolean',
'last_login' => 'datetime',
'must_change_password' => 'boolean',
];
}The four status columns stay out of $fillable: a lock flag should never be mass-assignable from a form.
7. Migrate, create the platform owner, sync
php artisan migrate
php artisan ui5:intake --name="Acme GmbH" --email=office@acme.test
php artisan ui5:syncui5:intake creates the organisation that operates this installation. It asks for the name and email if you don't pass them; every other detail stays empty unless you pass it as an option (see The System Actor). On a fresh database it confirms that config(ui5.system_actor_id)=1 resolves to it — there is nothing else to set. ui5:sync then writes the catalog: the SDK's own apps, their abilities and roles, the default settings.
8. Seed yourself
Create database/seeders/FirstPersonSeeder.php:
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use LaravelUi5\Sdk\Partners\Enums\PartnerType;
use LaravelUi5\Sdk\Settings\Enums\SdkRole;
use LaravelUi5\Sdk\Settings\Enums\SystemLevel;
class FirstPersonSeeder extends Seeder
{
public function run(): void
{
// The organisation ui5:intake created.
$orgId = DB::table('sdk_partners')
->where('system_level', SystemLevel::PlatformOwner->value)
->value('id');
// You, as a person employed by it.
DB::table('sdk_partners')->updateOrInsert(
['email' => 'you@acme.test'],
[
'type' => PartnerType::Person->value,
'first_name' => 'Ada',
'name' => 'Ada Admin',
'email' => 'you@acme.test',
'system_level' => SystemLevel::LocalAdmin->value,
],
);
$personId = DB::table('sdk_partners')->where('email', 'you@acme.test')->value('id');
DB::table('sdk_partner_relationships')->updateOrInsert(
[
'partner_id' => $personId,
'related_id' => $orgId,
'relationship_type_id' => DB::table('sdk_partner_relationship_types')
->where('code', 'employed_by')->value('id'),
],
['assigned_by_partner_id' => $orgId, 'is_primary' => true],
);
// The role that opens the Partners and Settings apps.
DB::table('sdk_role_assignments')->updateOrInsert(
[
'partner_id' => $personId,
'role_id' => DB::table('sdk_roles')->where('role', SdkRole::LocalAdmin->value)->value('id'),
],
['assigned_by_partner_id' => $orgId],
);
// Your login.
$user = User::firstOrNew(['email' => 'you@acme.test']);
$user->name = 'Ada Admin';
$user->password = 'password';
$user->partner_id = $personId;
$user->save();
}
}php artisan db:seed --class=FirstPersonSeederThis seeder is the pattern for the real thing. On a real project an implementation consultant writes one per company — everyone who must exist on day one, their roles, their logins — and runs it on the QA system first. Further partners are created later in the Partners app. The First Seed covers what a real one needs.
9. Build help and navigation, publish the shell
php artisan ui5:help --all
php artisan ui5:nav
php artisan ui5:publishui5:help --allcompiles the help that ships with the apps and builds the search index. The shell loads that index when it starts.ui5:navcompiles the navigation rail intobootstrap/cache/ui5-nav.php.ui5:publishcopies the fonts and the fallback avatar topublic/sdk, and seeds the CI logos intopublic/assets/ci/— only if nothing is there, so your own branding is never overwritten. The shell bundle itself needs no publishing; the SDK serves it from the package.
10. Open it
php artisan serveOpen http://localhost:8000/login and sign in as you@acme.test with the password password. You land on the Launchpad. The navigation rail holds the Launchpad, Partners and Settings — the two admin apps are there because the seeder gave you the local_admin role.
Try the shell's keys: Cmd/Ctrl + K searches, Cmd/Ctrl + B toggles the rail, F1 opens the help.
11. Check the labels
grep -o "Missing i18nKey[^']*" bootstrap/cache/ui5-nav.php | sort -uEmpty output means every rail entry has its label. A line means a key is missing: it names the key and the app. Add the key to that app's i18n.properties, run ui5:nav and check again. The rules behind the keys are on Navigation.
What's next?
- Add your own app: build one with the Core quickstart, let its manifest extend
LaravelUi5\Sdk\Platform\AbstractSdkManifestinstead of Core's base, then runui5:syncandui5:nav— it appears in the rail - Gate your app with an ability: Capability Mini-Apps
- Write data: Mutating Actions
- Shape the rail: Navigation
- The keys you set in step 4: Configuration