AI Skill Report Card

Architecting Laravel SaaS

A-85·Sep 29, 2026·Source: Web
14 / 15

For a new SaaS feature request, follow this pattern immediately:

Bash
php artisan make:model Tenant -mfs php artisan make:controller Api/TenantController --api --model=Tenant php artisan make:request StoreTenantRequest php artisan make:policy TenantPolicy --model=Tenant php artisan make:resource TenantResource

Structure logic across layers — never put business logic in controllers:

PHP
// app/Http/Controllers/Api/TenantController.php public function store(StoreTenantRequest $request, TenantService $service) { $tenant = $service->createTenant($request->validated()); return new TenantResource($tenant); } // app/Services/TenantService.php public function createTenant(array $data): Tenant { return DB::transaction(function () use ($data) { $tenant = Tenant::create($data); $tenant->databases()->create(['name' => 'tenant_' . $tenant->id]); event(new TenantCreated($tenant)); return $tenant; }); }
Recommendation▾
Add a second concrete example showing a 'bad outcome' (e.g., anti-pattern code) contrasted with the fixed version for clearer before/after learning
14 / 15

Progress checklist for building a Laravel SaaS module:

  • Define domain model and relationships (migrations + models)
  • Decide multi-tenancy strategy (single DB with tenant_id, or DB-per-tenant)
  • Create Form Requests for validation (never validate in controller)
  • Create Service/Action classes for business logic
  • Create Policies for authorization (never inline if checks on roles)
  • Create API Resources for consistent JSON output
  • Add Jobs/Events for async work (billing, emails, provisioning)
  • Write Feature tests covering the happy path + edge cases
  • Add rate limiting / throttling on public endpoints
  • Document endpoints (OpenAPI or Postman collection)

1. Multi-Tenancy Strategy

Default recommendation: single database, tenant_id column + global scope, unless client requires strict data isolation (then use stancl/tenancy package with DB-per-tenant).

PHP
class TenantScope implements Scope { public function apply(Builder $builder, Model $model) { if ($tenantId = app('currentTenant')?->id) { $builder->where('tenant_id', $tenantId); } } }

2. Subscription Billing

Use laravel/cashier (Stripe/Paddle) as default. Model plans as a plans table, not hardcoded enums, so pricing can change without deploys.

3. Authentication & Authorization

  • Sanctum for SPA/API tokens.
  • Spatie laravel-permission for roles/permissions.
  • Always gate at Policy level, test policies independently.

4. Layered Architecture (MVC+)

Controller  -> thin, only orchestrates request/response
Request     -> validation
Service      -> business logic, transactional boundaries
Model        -> Eloquent, relationships, scopes only
Resource     -> API shape/transformation
Event/Job    -> async side effects
Recommendation▾
Expand testing guidance with a concrete feature test example instead of just mentioning it in the checklist
16 / 20

Example 1: Input: "Add a feature where creating a company also creates a default admin user and sends a welcome email." Output:

PHP
// TenantService.php public function createTenant(array $data): Tenant { return DB::transaction(function () use ($data) { $tenant = Tenant::create($data); $admin = $tenant->users()->create([ 'name' => $data['admin_name'], 'email' => $data['admin_email'], 'password' => Hash::make(Str::random(16)), ]); $admin->assignRole('admin'); SendWelcomeEmail::dispatch($admin); return $tenant; }); }

Example 2: Input: "Client wants usage-based billing per API call." Output: Recommend Cashier's metered billing with Stripe usage records; log usage via a UsageRecorded event listener that increments a usage_counters table, then a scheduled job reports aggregated usage to Stripe nightly via reportUsageFor().

Recommendation▾
Include a brief note on choosing between stancl/tenancy vs single-DB approach with decision criteria (team size, compliance needs) rather than just a default recommendation
  • Keep controllers under ~15 lines; push logic to Services/Actions.
  • Use Form Requests + authorize() method for every write endpoint.
  • Always wrap multi-step writes in DB::transaction().
  • Use Events for cross-cutting concerns (emails, webhooks, audit logs) instead of coupling in Services.
  • Use API Resources, never return raw Eloquent models as JSON.
  • Cache expensive tenant-level config/lookups with tagged cache (Cache::tags(['tenant:'.$id])).
  • Version your API (/api/v1/...) from day one.
  • Use queues for anything touching third-party APIs (payment, email, webhooks).
  • Write feature tests per tenant scenario, not just per endpoint.
  • Forgetting a global scope on tenant-scoped models → cross-tenant data leaks.
  • Putting billing logic directly in controllers instead of a BillingService.
  • Using env() outside config files — always wrap in config().
  • Skipping policies and checking role === 'admin' inline in controllers.
  • Not queuing webhook handlers (Stripe/Paddle) — causes timeouts under load.
  • Hardcoding plan limits in code instead of a plans/features table.
  • Sharing a single Redis/cache namespace across tenants without prefixing keys.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
14/15
Workflow
14/15
Examples
16/20
Completeness
18/20
Format
15/15
Conciseness
14/15