Your First Resource: A Customer Directory
This walkthrough takes a new Laravel application from a database table to an authenticated SeviaFlow screen. At the end, you will have a customer list with search, a status filter, and create, show and edit forms. The example uses the UI adapter; API access is an optional later step.
Before you start
- A Laravel 12 application on PHP 8.2+, or Laravel 13 on PHP 8.3+.
- A configured database and a working Laravel
Usermodel/authentication setup.
For the package constraints and installation commands, see Requirements & Installation.
How the pieces fit together
- The migration and model define and represent your application data. SeviaFlow does not create your business tables for you.
- The Resource connects that Eloquent model to a table, form, and optional API contract.
- The resource map assigns a short, stable slug such as
customersto the Resource class. - The adapter provides a surface: UI renders web pages, while the optional API package registers REST routes. Core supplies shared Resource behavior.
Keep business rules, tenant scoping, database design and Laravel authorization in your host application. A Resource describes how SeviaFlow should expose the model.
1. Install Core and UI
For the operator screens, install UI; Composer also installs its Core dependency. Publish its configuration, package migrations and assets, then run:
composer require seviaflow/ui:^0.1@dev
php artisan vendor:publish --tag=seviaflow-config
php artisan vendor:publish --tag=seviaflow-migrations
php artisan vendor:publish --tag=seviaflow-ui-config
php artisan vendor:publish --tag=seviaflow-ui-migrations
php artisan vendor:publish --tag=seviaflow-ui-assets
php artisan migrate
Publishing makes package configuration, migrations and frontend assets available to your host application. Run migrations after publishing. Review Requirements & Installation before choosing a database or upgrading an existing SeviaFlow setup.
2. Create the customer table and model
Ask Artisan to create both the Eloquent model and its migration:
php artisan make:model Customer -m
In the new migration, define the columns that the form and table will use:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('customers', function (Blueprint $table): void {
$table->id();
$table->string('name');
$table->string('email');
$table->string('status')->default('prospect');
$table->text('notes')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('customers');
}
};
In app/Models/Customer.php, allow the fields that SeviaFlow's form will save:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
protected $fillable = ['name', 'email', 'status', 'notes'];
}
The names must agree: the migration creates these columns, the model allows them to be assigned, and the Resource below refers to them.
3. Give an operator access to the panel
The UI's default middleware requires a signed-in user. SeviaFlow also checks a separate panel gate before admitting that user to the admin UI. Core's permissions.admin_gate defaults to null, which fails closed. Configure the gate before expecting the panel to open.
This example uses an is_admin flag on your existing users table. Create a migration:
php artisan make:migration add_is_admin_to_users_table --table=users
Replace the generated migration body with this code so rolling the migration back removes the column too:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table): void {
$table->boolean('is_admin')->default(false);
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table): void {
$table->dropColumn('is_admin');
});
}
};
In config/seviaflow.php, import the user model and Authenticatable contract near the top of the file, then add this callback inside the existing permissions array. Keep the other generated permission settings:
use App\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
// Inside the existing 'permissions' array:
'admin_gate' => static fn (?Authenticatable $user): bool =>
$user instanceof User && (bool) $user->is_admin,
The callback explicitly rejects guests and users whose flag is false. SeviaFlow's login authenticates with Laravel's configured guard, then checks this gate before opening the panel. The UI does not create your first application user: use your existing signup flow or a seeder, then mark one trusted local account as an admin.
After finishing the migrations below, you can mark an existing local user in Tinker:
php artisan tinker
$user = App\Models\User::where('email', 'you@example.com')->firstOrFail();
$user->is_admin = true;
$user->save();
Replace the email with an account you control. Do not make every user an admin in a deployed application. If your app already has a trusted admin check, use that check in the callback instead of adding this example column. More detail is in Authorization.
4. Define the Resource
Create app/SeviaFlow/Resources/CustomerResource.php. This complete example connects the model to a searchable table, a status filter and a form:
<?php
declare(strict_types=1);
namespace App\SeviaFlow\Resources;
use App\Models\Customer;
use SeviaFlow\Core\Resources\Columns\StatusColumn;
use SeviaFlow\Core\Resources\Columns\TextColumn;
use SeviaFlow\Core\Resources\Fields\EmailField;
use SeviaFlow\Core\Resources\Fields\SelectField;
use SeviaFlow\Core\Resources\Fields\TextareaField;
use SeviaFlow\Core\Resources\Fields\TextField;
use SeviaFlow\Core\Resources\Filters\SelectFilter;
use SeviaFlow\Core\Resources\Form;
use SeviaFlow\Core\Resources\Resource;
use SeviaFlow\Core\Resources\Table;
final class CustomerResource extends Resource
{
protected static string $model = Customer::class;
protected static ?string $navigationLabel = 'Customers';
protected static ?string $navigationGroup = 'Example app';
protected static ?string $navigationIcon = 'users';
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')->label('Name')->searchable()->sortable(),
TextColumn::make('email')->label('Email')->searchable(),
StatusColumn::make('status')->label('Status')->options(static::statusOptions()),
])
->filters([
SelectFilter::make('status')->label('Status')->options(static::statusOptions()),
]);
}
public static function form(Form $form): Form
{
return $form->fields([
TextField::make('name')->label('Name')->required(),
EmailField::make('email')->label('Email')->required(),
SelectField::make('status')->label('Status')->options(static::statusOptions())->required(),
TextareaField::make('notes')->label('Notes')->rows(4),
]);
}
/**
* @return array<string, string>
*/
private static function statusOptions(): array
{
return [
'prospect' => 'Prospect',
'active' => 'Active',
'paused' => 'Paused',
];
}
}
$model is required. The table columns and form fields use the same database attributes you created in step 2. The statusOptions() method keeps the labels and stored values consistent between the table, filter and form.
The source file used by this documentation is also available in the website repository at examples/CustomerResource.php. The separate Playground project uses a nested folder: app/SeviaFlow/Resources/CustomerResource/CustomerResource.php.
5. Register the Resource
Open the published config/seviaflow.php and add the class to its resources map. Keep the other config entries already in the file:
'resources' => [
'customers' => App\SeviaFlow\Resources\CustomerResource::class,
],
The key customers is the slug. SeviaFlow uses it to resolve the Resource and to build the URLs; it is not inferred from the PHP class name. See Resource Registration for discovery, route prefixes and the API route map.
6. Migrate, sign in and verify the result
Run the migrations now; this includes both the SeviaFlow package tables and your customer/admin columns. Clear cached config after editing it:
php artisan migrate
php artisan config:clear
php artisan route:list --path=admin
Check that the route list includes the UI login and resource routes. Resource routes contain the placeholder {resource}; the registered slug is resolved when a request comes in. Visit /admin/login, sign in with the existing user marked as admin, and open /admin/resources/customers.
- The empty list is expected before you add data. Choose Create and enter a name, email, status and optional notes.
- Save the record. It should appear in the customer list; search by name or email.
- Use the status filter to narrow the list, then open the record to view or edit it.
- Check the
customerstable in your database if the record does not persist.
7. Add API access only when you need it
The API adapter is optional. Installing it registers API routes, but a Resource is not exposed until its api() method opts in. Add use SeviaFlow\Core\Resources\ApiResource; to the Resource and define an explicit field contract, for example:
public static function api(ApiResource $api): ApiResource
{
return $api
->enabled()
->readable(['id', 'name', 'email', 'status'])
->creatable(['name', 'email', 'status', 'notes'])
->updatable(['name', 'email', 'status', 'notes'])
->requiredOnCreate(['name', 'email', 'status'])
->useFormFieldRules();
}
Before enabling this, configure the API adapter's default api + auth:sanctum middleware and add Laravel policies or enabled SeviaFlow role permissions for the operations you need. API authentication and Resource authorization are separate from the UI panel gate. Without a model policy, Core allows an operation after its optional role-permission check passes; role permissions are disabled by default. Follow the tested token setup in API: Introduction & Installation and the model-policy guidance in Authorization.
When something does not work
| Symptom | What to check |
|---|---|
| Composer cannot resolve the package. | Check the package name and version constraint against the beta installation instructions, and confirm that your PHP and Laravel versions meet the supported requirements. |
/admin/resources/customers is not found. | Confirm UI is installed, the slug map contains customers, the class autoloads, and cached routes/config were cleared. |
| Login works but the panel denies access. | Confirm permissions.admin_gate is configured and returns true for this user. A missing callback denies panel access. |
| The form loads but saving reports a database error. | Run php artisan migrate and confirm the table has every field used by the Resource. |
| Styles are missing. | Republish UI assets with php artisan vendor:publish --tag=seviaflow-ui-assets --force. |
What to read next
- Resource Registration explains explicit slugs and adapter routes.
- Resource Reference describes the available Resource methods.
- Authorization explains panel access, policies, role permissions and row scoping.
- API: Introduction & Installation walks through API authentication and requests.
Get Started