Skip to content

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 ​

bash
composer create-project laravel/laravel acme
cd acme

2. Install the SDK and a login ​

bash
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/auth

The 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:

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:

bash
php artisan vendor:publish --tag=ui5-config
php artisan vendor:publish --tag=ui5-views

In 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:

php
'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:

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:

bash
php artisan make:migration add_partner_id_to_users_table --table=users
php
public 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:

php
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:

php
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 ​

bash
php artisan migrate
php artisan ui5:intake --name="Acme GmbH" --email=office@acme.test
php artisan ui5:sync

ui5: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
<?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();
    }
}
bash
php artisan db:seed --class=FirstPersonSeeder

This 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 ​

bash
php artisan ui5:help --all
php artisan ui5:nav
php artisan ui5:publish
  • ui5:help --all compiles the help that ships with the apps and builds the search index. The shell loads that index when it starts.
  • ui5:nav compiles the navigation rail into bootstrap/cache/ui5-nav.php.
  • ui5:publish copies the fonts and the fallback avatar to public/sdk, and seeds the CI logos into public/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 ​

bash
php artisan serve

Open 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 ​

bash
grep -o "Missing i18nKey[^']*" bootstrap/cache/ui5-nav.php | sort -u

Empty 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? ​