Help us improve
Share bugs, ideas, or general feedback.
From laravel-api-tool-kit
Adds a complete CRUD endpoint to Laravel projects (standard or DDD), via ordered steps for model, migration, filter, controller, policy, resources, and more.
npx claudepluginhub ahmedesa/laravel-api-tool-kitHow this command is triggered — by the user, by Claude, or both
Slash command
/laravel-api-tool-kit:new-endpointlaravel-api-tool-kit/workflows/The summary Claude sees in its command listing — used to decide when to auto-load this command
# Workflow: Add a New CRUD Endpoint Follow these steps in order. Do not skip steps or reorder them. --- ## Step 0 — Gather Requirements Before creating any file, confirm: - What is the resource name? (e.g. `Car`) - What columns does it have and what are their types? - Does it need soft deletes? - Does it need file uploads? - Which routes are needed? (all CRUD, or subset?) - Is authentication required? Which guard? ### Detect the project structure first - If you see `Domain/` → **DDD structure** — check `ls app/Domain/` to confirm folder naming conventions used in the project - If yo...
/endpointGenerates complete FastAPI CRUD endpoint for MongoDB using Beanie, with Pydantic schemas, models, service layer, router, and tests. Prompts for resource name, fields, auth, caching.
/blazor-apiGenerates ASP.NET Core API endpoints for an entity using minimal APIs or controllers, including full CRUD, validation, authorization, and OpenAPI docs.
/laravelBuilds, configures, and optimizes Laravel apps with Eloquent models, service layers, queues, events, broadcasting, Sanctum/Passport auth, and Pest tests. Supports API, auth, queue, model gen, optimization, upgrade, audit flags.
Share bugs, ideas, or general feedback.
Follow these steps in order. Do not skip steps or reorder them.
Before creating any file, confirm:
Car)ls app/
Domain/ → DDD structure — check ls app/Domain/ to confirm folder naming conventions used in the projectModels/, Http/ only → Standard Laravel structure| Component | Standard Laravel | DDD |
|---|---|---|
| Model | app/Models/Car.php | app/Domain/Car/Models/Car.php |
| Filter | app/Filters/CarFilters.php | app/Domain/Car/Filters/CarFilters.php |
| Enum | app/Enums/CarStatusEnum.php | app/Domain/Car/Enums/CarStatusEnum.php |
| Action | app/Actions/CreateCarAction.php | app/Domain/Car/Actions/CreateCarAction.php |
| Policy | app/Policies/CarPolicy.php | app/Domain/Car/Policies/CarPolicy.php |
| Form Request | app/Http/Requests/Car/ | app/Http/Requests/Application/Car/ (or Dashboard/Car/) |
| Resource | app/Http/Resources/Car/CarResource.php | app/Http/Resources/Application/Car/CarResource.php |
| Controller | app/Http/Controllers/CarController.php | app/Http/Controllers/API/Application/Car/CarController.php |
Note: Controllers, Requests, and Resources always stay in
app/Http/regardless of structure — only domain logic moves intoapp/Domain/.
Follow rules/models.md for the correct structure.
$fillable$casts for booleans, enums, and arraysSoftDeletes if neededHasUlids trait + $keyType = 'string' + $incrementing = false$default_filters yet — do that after creating the Filter (Step 3)File: app/Models/Car.php — DDD: app/Domain/Car/Models/Car.php
Create a standard Laravel migration:
Use the project's primary key convention from SKILL.md Project Defaults:
// ULID project
Schema::create('cars', function (Blueprint $table) {
$table->ulid('id')->primary();
$table->string('name');
$table->string('color');
$table->boolean('is_active')->default(true);
$table->foreignUlid('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
// Auto-increment project
Schema::create('cars', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('color');
$table->boolean('is_active')->default(true);
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
Follow rules/filters.md for the correct structure.
$allowedFilters with columns clients can filter by equality$allowedSorts with sortable columns (always include created_at)$columnSearch if text search is needed$allowedIncludes for relationships clients can eager-loadFile: app/Filters/CarFilters.php — DDD: app/Domain/Car/Filters/CarFilters.php
Then go back to the Model and bind it:
protected string $default_filters = CarFilters::class;
Follow rules/enums.md for the correct structure. Create one for any fixed-value column (status, type, etc.).
File: app/Enums/CarStatusEnum.php — DDD: app/Domain/Car/Enums/CarStatusEnum.php
Cast it in the Model:
protected $casts = [
'status' => CarStatusEnum::class,
];
Use it in validation:
'status' => ['required', Rule::in(CarStatusEnum::values())],
Follow rules/requests.md for the correct structure.
sometimes prefix for partial updatesFiles:
Standard: app/Http/Requests/Car/CreateCarRequest.php
app/Http/Requests/Car/UpdateCarRequest.php
DDD: app/Http/Requests/Application/Car/CreateCarRequest.php
app/Http/Requests/Application/Car/UpdateCarRequest.php
(use Dashboard/Car/ for admin-facing requests)
Follow rules/resources.md for the correct structure.
$this->whenLoaded('relation') for every relationshipdateTimeFormat() for all timestampstoArray()File: app/Http/Resources/Car/CarResource.php — DDD: app/Http/Resources/Application/Car/CarResource.php
Follow rules/authorization.md. Create a Policy if any endpoint needs ownership or role checks.
Include all applicable methods: viewAny, view, create, update, delete.
File: app/Policies/CarPolicy.php — DDD: app/Domain/Car/Policies/CarPolicy.php
Register in AppServiceProvider or via automatic discovery (Laravel auto-discovers policies by convention).
Follow rules/actions.md for the correct structure. Create one only if the operation:
If it's a simple Model::create($data) — do it directly in the controller. No Action needed.
For external 3rd-party integrations (SMS, payment, etc.), see rules/services.md.
File: app/Actions/CreateCarAction.php — DDD: app/Domain/Car/Actions/CreateCarAction.php
Follow rules/controllers.md for the correct structure. Use the pattern with a constructor-injected Action/Service/Repository if you created one in Step 8.
$this->authorize() on methods that need policy checksFile: app/Http/Controllers/CarController.php — DDD: app/Http/Controllers/API/Application/Car/CarController.php
Since all user-facing strings MUST use trans(), create translation keys before writing messages in the controller.
// lang/en/car.php
return [
'created' => 'Car created successfully.',
'updated' => 'Car updated successfully.',
'deleted' => 'Car deleted successfully.',
];
Register the resource route in routes/api.php:
// Full CRUD
Route::apiResource('cars', CarController::class)->middleware('auth:sanctum');
// Or manual registration for partial CRUD
Route::middleware('auth:sanctum')->group(function () {
Route::get('cars', [CarController::class, 'index']);
Route::post('cars', [CarController::class, 'store'])->middleware('throttle:10,1');
Route::get('cars/{car}', [CarController::class, 'show']);
Route::put('cars/{car}', [CarController::class, 'update']);
Route::delete('cars/{car}', [CarController::class, 'destroy']);
});
Create a factory:
// database/factories/CarFactory.php
public function definition(): array
{
return [
'name' => fake()->word(),
'color' => fake()->safeColorName(),
'is_active' => true,
'user_id' => User::factory(),
];
}
Write a feature test covering index, store, show, update, destroy.
Before marking the feature done, verify (paths per Step 0 path map):
$fillable is populated$casts covers all booleans and enums$allowedFilters and $allowedSorts$request->validated()trans()dateTimeFormat()whenLoaded()declare(strict_types=1) on every new file