Custom Models

Place your Eloquent models in app/Models/Custom/ to keep them separate from the core SaasKitFy models. This makes upgrades straightforward since you never modify core files.

Namespace

namespace App\Models\Custom;

Org-Scoped Models

Most custom models should be scoped to an organization. Add an organization_id foreign key with cascading deletes:

<?php

namespace App\Models\Custom;

use App\Models\Organization;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Project extends Model
{
    protected $fillable = [
        'organization_id', 'created_by', 'name',
        'description', 'status', 'due_date',
    ];

    public function organization(): BelongsTo
    {
        return $this->belongsTo(Organization::class);
    }

    public function creator(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by');
    }
}

Migration

Create your migration in database/migrations/ with a numbered prefix. Use constrained()->cascadeOnDelete() on the organization foreign key so that deleting an organization cleans up all related records:

// database/migrations/0005_01_01_000000_create_example_module_tables.php

Schema::create('projects', function (Blueprint $table) {
    $table->id();
    $table->foreignId('organization_id')
        ->constrained()
        ->cascadeOnDelete();
    $table->foreignId('created_by')
        ->constrained('users')
        ->cascadeOnDelete();
    $table->string('name');
    $table->text('description')->nullable();
    $table->string('status')->default('backlog');
    $table->timestamp('due_date')->nullable();
    $table->timestamps();
    $table->index(['organization_id', 'status']);
});

Querying with Org Scope

Always scope queries to the current organization in your controllers:

$org = $request->user()->activeOrganization();

$projects = Project::where('organization_id', $org->id)
    ->orderByDesc('updated_at')
    ->get();

Verifying Ownership

When accessing a single record via route model binding, verify it belongs to the current org:

public function show(Request $request, Project $project): JsonResponse
{
    $org = $request->user()->activeOrganization();
    abort_if($project->organization_id !== $org->id, 403);

    return response()->json($project);
}