A module is a folder with a module.json and a Laravel service provider. It can add pages to the business panel, the admin panel and the public site, store its own data, react to bookings and run scheduled tasks, all without editing Slotara.
This page is the contract between Slotara and modules. Only what's listed here is stable. Anything else in Slotara is internal and may change in any release. Changing an item on this list is a breaking change: it bumps Slotara's major version, and modules declare which versions they support in module.json ("requires": {"product": "^1.8"}).
On a development copy of Slotara:
Turn on developer mode in .env (loads unsigned modules from modules-dev/):
LICENTRA_MODULES_DEV=true
Get the example module: Admin โ System โ Modules โ More โ Download example module, then Upload module with the zip (or unzip it into modules-dev/). Or start from scratch:
php artisan module:make invoices # creates modules-dev/slotara-invoices/
Create its tables and publish its CSS/images:
php artisan module:migrate slotara-invoices
Give it to a plan (Subscription Plans โ Modules included) and open its pages.
{warning} Developer mode loads code that isn't checked. Never turn it on for a live site.
The example module, slotara-hello:
slotara-hello/
โโโ module.json identity, versions, what it touches
โโโ src/
โ โโโ ModuleServiceProvider.php entry point: routes, views, hooks, tasks, panel pages
โ โโโ Models/Greeting.php its own model
โ โโโ Filament/
โ โโโ TenantPlugin.php pages for the business panel (/manage)
โ โโโ Tenant/Greetings.php a page with a form
โ โโโ AdminPlugin.php pages for the platform admin (/admin)
โ โโโ Admin/Overview.php
โโโ routes/web.php public pages, under /slotara-hello/...
โโโ resources/views/ view('slotara-hello::public')
โโโ lang/en/messages.php __('slotara-hello::messages.title')
โโโ database/migrations/ its own tables
โโโ public/hello.css published to /modules/slotara-hello/hello.css
The provider:
class ModuleServiceProvider extends \Pacific\Licentra\Modules\Laravel\ModuleServiceProvider
{
public function bootModule(): void
{
$this->loadModuleResources(); // views + translations
$this->loadModuleRoutes('routes/web.php', ['web']); // under /slotara-hello, gated per plan
$this->observe(SlotReservation::class, 'created', function (SlotReservation $booking) {
Greeting::create(['tenant_id' => $booking->tenant_id, 'message' => "Thanks, {$booking->name}!"]);
});
$this->schedule(fn ($schedule) => $schedule->call(fn () => Greeting::prune())->daily());
}
public function filamentPlugins(string $panel): array
{
return match ($panel) {
'tenant' => [TenantPlugin::make()],
'admin' => [AdminPlugin::make()],
default => [],
};
}
}
{
"manifest": 1,
"slug": "slotara-invoices",
"url": "invoices",
"name": "Invoices",
"description": "Shown on the Modules page.",
"version": "1.0.0",
"product": "slotara",
"requires": { "product": "^1.8", "licentra": "^1.6" },
"namespace": "Modules\SlotaraInvoices\",
"provider": "Modules\SlotaraInvoices\ModuleServiceProvider",
"envato_item_id": null,
"plan_feature": false,
"capabilities": {
"tables": ["slotara_invoices"],
"outbound": [],
"routes": ["GET /invoices/view/{token}"],
"schedule": false
}
}
| Key | Notes |
|---|---|
slug |
slotara-{name}, lowercase. The same slug is used everywhere: folder, route names, view namespace, license. |
url |
Optional. The address your pages use, e.g. invoices gives /invoices/... and /manage/invoices/..., so a white-labelled platform never shows "slotara" in its addresses. Without it, pages use the slug. If Slotara already uses that address, the module is switched off. Needs "licentra": "^1.6". |
version |
x.y.z. An older version can never replace a newer one. |
requires.product |
Slotara versions you support (^1.8, >=1.8 <2.0). On other versions the module is switched off, not broken. |
namespace, provider |
PSR-4 root for src/; the provider must be inside it. |
plan_feature |
true: sold per plan (a checkbox on plans and businesses). false: every business gets it. |
capabilities |
What the module adds, shown to the admin before install. Be accurate; it isn't enforced. |
Business panel and admin panel (Filament). Add pages and resources in a Filament plugin returned from filamentPlugins(). Every module page and resource must:
use Pacific\Licentra\Modules\Laravel\GatedByModule;
class Greetings extends \Filament\Pages\Page
{
use GatedByModule; // hidden unless the module runs and the plan includes it
protected static string $module = 'slotara-hello';
protected static ?string $slug = 'slotara-hello/greetings'; // must start with your url (or slug)
}
A page whose slug doesn't start with the module's url (or its slug when there's no url) gets the module switched off, so a module can never take over a Slotara page.
Public pages. Put routes in routes/web.php and load them with loadModuleRoutes(). Everything is placed under /{url}/ (your url, else the slug) and named {slug}.*:
// routes/web.php โ GET /slotara-hello/{business}, route name "slotara-hello.public"
Route::get('/{tenant:slug}', fn (Tenant $tenant) => view('slotara-hello::public', compact('tenant')))
->name('public');
With a {tenant} parameter the plan check uses that business. Add 'auth' to the middleware list for logged-in pages.
React to Slotara only through these helpers. If your callback throws, Slotara's own action (saving the booking, rendering the page) still completes; the error is logged and charged to your module.
| Helper | Example |
|---|---|
observe($model, $events, $fn) |
$this->observe(SlotReservation::class, 'created', fn ($booking) => โฆ) |
listen($events, $fn) |
$this->listen(SomeEvent::class, fn ($event) => โฆ) |
composer($views, $fn) |
$this->composer('pages.for-businesses', fn ($view) => $view->with(โฆ)) |
schedule($fn) |
$this->schedule(fn ($s) => $s->call(fn () => โฆ)->daily()) |
A module is switched off when it throws while starting, causes 3 PHP crashes in 10 minutes, causes 3 errors on Slotara's pages (code hooked in some other way), or causes 10 errors in 10 minutes on its own pages, hooks or tasks. 404s, validation errors and redirects to login don't count.
App\Models\Traits\BelongsToTenant: in the business panel, queries only return the current business's rows and tenant_id is filled in on create.Model::withoutGlobalScope('tenant') and filter by tenant_id yourself.App\Support\TenantContext::current(), or auth()->user()->tenant.slotara_invoices_โฆ) and link them to tenants.id. Never add columns to Slotara's tables.| Area | Stable API |
|---|---|
| Provider | Extend Pacific\Licentra\Modules\Laravel\ModuleServiceProvider: register(), bootModule(), filamentPlugins($panel), loadModuleRoutes(), loadModuleResources(), observe(), listen(), composer(), schedule(), modulePath() |
| Filament | Panel ids admin and tenant; GatedByModule trait; page/resource slugs {url} or {url}/โฆ |
| Business | App\Support\TenantContext, App\Models\Traits\BelongsToTenant |
| Plan gating | plan_feature in module.json; app(\Pacific\Licentra\Modules\Laravel\TenantGate::class)->allows($slug, request()) |
| Models | App\Models\Tenant, User, Service, Provider, SlotReservation (a booking), SubscriptionPlan: read, relate, and observe created / updated / deleted |
| Settings | Read Slotara settings via App\Models\Setting only |
| Packages | Only what Slotara already requires (Laravel 12, Filament 5, Livewire 3). Modules can't ship a vendor/ folder. |
tenant_id).capabilities accurate.public/; only CSS, JS, images, fonts and JSON are published (never PHP).Run your module's tests through Slotara's test runner, loading the module from a scratch folder so your real modules-dev/ is never touched. The Invoices add-on's tests/InvoicesModuleTest.php shows the setup:
php artisan test ../slotara-invoices/tests/InvoicesModuleTest.php
In the Licentra admin, add a product: slug slotara-{name}, Add-on for: Slotara, pricing Envato (with its CodeCanyon item ID) or Free.
Bump version in module.json, then from the Slotara folder:
vendor/bin/licentra-release module modules-dev/slotara-{name} --upload
This signs the module with your release key, uploads it, and also writes slotara-{name}-x.y.z.licentra-module.zip, a signed package admins can install with Upload module.
Publish the release in the Licentra admin. Every licensed Slotara shows it under Available add-ons (or as an update).