Claude Code with Laravel: PHP Artisan Workflows
Why Laravel projects need deliberate Claude Code configuration
Claude Code knows Laravel. It knows Artisan, Eloquent, Blade, service providers, Facades, Form Requests, Jobs, and the entire lifecycle from request through middleware to response. This framework-level knowledge is not the same as project-level knowledge.
A production Laravel application has conventions that are not obvious from the code alone: which Eloquent scopes are standard across all models, how service classes are structured, whether routes are grouped by API version or domain, how Jobs handle failures and retry logic, and whether you use Pest or PHPUnit for testing.
Without a project-specific CLAUDE.md, Claude generates Eloquent queries without your scope conventions, writes php artisan migrate as a side effect of model changes, creates thin controller methods with logic that belongs in service classes, and generates PHPUnit tests in a project that uses Pest. Each correction takes time. Multiplied across a sprint, it adds up.
The fix is the same as with any framework: a CLAUDE.md that converts your project's implicit conventions into explicit rules Claude reads before every session. If you have not set Claude Code up at all, the Claude Code setup guide covers installation and authentication first.
The Laravel CLAUDE.md
The CLAUDE.md for a Laravel project needs to answer: what version of Laravel and PHP are in use, how is the project structured, how is the database managed, how are controllers and services organized, how are tests run, and what are the hard rules?
# Laravel project rules
## Versions
- PHP: 8.3
- Laravel: 11.x
- Package manager: Composer 2.x
## Project structure
- Controllers: app/Http/Controllers/, thin, HTTP only, no business logic
- Form Requests: app/Http/Requests/ for all validation
- Services: app/Services/, all business logic, injected via constructor
- Models: app/Models/, Eloquent models, scopes, relationships
- Resources: app/Http/Resources/, API resource transformers (Laravel API Resources)
- Jobs: app/Jobs/, queued and synchronous jobs
- Events and Listeners: app/Events/, app/Listeners/
- Policies: app/Policies/, one policy per model
## Database
- Engine: MySQL 8.0 (local: MySQL via Docker, port 3306)
- Eloquent ORM only. No raw DB:: queries except in migrations with comment.
- Always eager-load relationships used in views or resources (no lazy loading)
- Scopes: define on the model as query scopes, not in controllers
- Connection config: .env (DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD)
## Artisan and migrations
- Never run `php artisan migrate` unless explicitly instructed
- Never create a migration without stating intent first
- Migration names must be descriptive: `php artisan make:migration add_stripe_id_to_users_table`
- Check pending migrations before writing model code: `php artisan migrate:status`
- Rollback only when explicitly asked: `php artisan migrate:rollback`
## Tests
- Framework: Pest PHP (not PHPUnit directly, use Pest test syntax)
- Run: `php artisan test` from project root
- Fast pass: `php artisan test --filter=ClassName` for a single test class
- Database: use RefreshDatabase trait in tests that touch the database
- Factories: Laravel Factories (database/factories/), one per model
- No fixtures. Factories and seeders only.
## Queue and jobs
- Driver: Redis (QUEUE_CONNECTION=redis in .env)
- Run queue worker locally: `php artisan queue:work --tries=3`
- Failed jobs table: `jobs` table, inspect with `php artisan queue:failed`
- Jobs should be idempotent: running twice must be safe
## Hard rules
- No business logic in controllers. Controllers call services and return responses.
- No DB calls in Blade templates. Pass all data from the controller.
- No direct Facade use in service classes. Use constructor injection.
- All user input validated in Form Request classes, not controllers.
- Policies required for every model action that involves authorization.
- No print_r() or var_dump() in production code. Use Log::info() / Log::error().
Four sections in this CLAUDE.md prevent the most costly Claude Code mistakes with Laravel.
The Artisan and migrations section stops Claude from applying migrations as a side effect of model work. Laravel developers working in teams know the friction of undocumented migrations appearing in a branch. The rule to state migration intent before running anything gives you a review window every time.
The Pest declaration matters because Claude's training contains significantly more PHPUnit than Pest code. Without the explicit declaration, Claude generates PHPUnit-style test classes with class ExampleTest extends TestCase and public function test_something() methods, rather than Pest's it() and test() function syntax. One line in CLAUDE.md locks Claude to Pest throughout.
The no Facades in services rule prevents a common Laravel coupling mistake. Facades are convenient in controllers where the dependency is implicit, but in service classes they make testing harder and dependencies invisible. Specifying constructor injection for services means Claude generates testable service classes from the start.
The jobs idempotency rule prevents one of the hardest Laravel bugs to debug: a job that is processed twice due to a visibility timeout and creates duplicate records because the job logic was not designed to be safe to re-run.
Artisan command patterns that work with Claude Code
Artisan is the interface through which Claude does most of its project management work in Laravel. Understanding which Artisan commands Claude uses and for what, and setting the correct permissions around them, is the most important operational decision in a Laravel Claude Code setup.
The generate commands Claude uses most frequently:
# Model with factory, migration, policy, and resource
php artisan make:model Item --factory --migration --policy --resource
# Controller with invokable single-action class
php artisan make:controller ProcessPaymentController --invokable
# Form Request for validation
php artisan make:request StoreItemRequest
# Service class (not built in, use make:class or a custom stub)
php artisan make:class Services/ItemService
# Job
php artisan make:job ProcessItemExport
# Event and listener
php artisan make:event ItemCreated
php artisan make:listener SendItemNotification --event=ItemCreated
Tell Claude in CLAUDE.md which make:model flags are standard for your project. If every model in your project has a factory, migration, and policy, specify: "When creating a new model, always run php artisan make:model ModelName --factory --migration --policy. Never create these separately." This prevents Claude from creating models without factories and then needing to come back to add them.
The -R flag on make:model creates the Resource class at the same time. If your project uses Laravel API Resources for all JSON responses, add that flag to the standard model creation command.
For a broader look at how Claude Code handles database work across frameworks, the Claude Code database guide covers query optimization, MCP database server setup, and schema patterns that apply to any relational project.
Eloquent workflow patterns
Eloquent's magic methods and dynamic properties are where Claude Code's output quality depends most on the conventions you specify. Two patterns in particular need explicit guidance.
Eager loading everywhere relationships are used. Claude Code will generate Eloquent queries without with() when it is generating code quickly. The N+1 query problem in Laravel is subtle: the page loads, the data is correct, but the query log shows one query per model instance. Add the rule to CLAUDE.md:
## Eloquent eager loading rules
- In controllers, always call with() for relationships used in the view or resource:
$orders = Order::with(['user', 'lineItems.product'])->where(...)->get();
- Never access a relationship attribute without verifying it is eager-loaded
- Use withCount() instead of count() on a loaded collection
- Use whereHas() for filtering by relationship existence
Query scopes for reusable filtering. Claude Code will put query logic in controllers when it does not know your scope conventions. Specify the pattern:
## Eloquent scope conventions
// On the model:
public function scopeActive(Builder $query): void
{
$query->where('status', 'active');
}
public function scopeForUser(Builder $query, User $user): void
{
$query->where('user_id', $user->id);
}
// Usage in controller (chaining):
$items = Item::active()->forUser($user)->with('category')->paginate(20);
// Scope names are camelCase without the "scope" prefix
// Global scopes go in the model's booted() method
With these patterns in CLAUDE.md, Claude Code generates Eloquent queries that use your defined scopes rather than reinlining the filtering conditions in every controller method.
The select() discipline problem. Claude Code will generate Model::all() or Model::where(...)->get() without a select() clause. For large tables this loads every column including ones not used by the view. Add to CLAUDE.md:
- Never use Model::all(). Always chain ->get() on a query with conditions.
- Use select() to specify columns when only a subset is needed in list views.
- For existence checks, use ->exists() not ->count() > 0.
Pest PHP testing setup
Pest's expressive syntax pairs well with Claude Code's tendency toward readable test output. The configuration that produces the most consistent Claude Code test generation:
## Pest testing conventions
### Test structure
tests/
Feature/ , full HTTP request tests with database
Unit/ , isolated class tests, no database
Pest.php , global helpers and dataset definitions
### Feature test pattern
use App\Models\User;
use App\Models\Item;
it('creates an item for authenticated users', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/items', [
'name' => 'Test item',
'price' => 19.99,
]);
$response->assertStatus(201)
->assertJsonFragment(['name' => 'Test item']);
expect(Item::count())->toBe(1);
});
### Always use RefreshDatabase in Feature tests
uses(RefreshDatabase::class)->in('Feature');
### Unit test pattern (no database)
it('calculates the discounted price correctly', function () {
$service = new PricingService();
expect($service->discounted(100.00, 20))->toBe(80.00);
});
Add to CLAUDE.md:
## After every code change
1. Run `php artisan test --filter={ChangedClass}` for fast feedback
2. If focused tests pass, run `php artisan test` for the full suite
3. Do not mark a task complete if any test fails
4. For new features: write the Pest test before or alongside the implementation
The --filter flag is important for development speed. A full Laravel test suite can take a minute or more. Running only the tests relevant to the changed class gives Claude fast feedback without waiting for unrelated feature tests.
For the broader Claude Code testing workflow including TDD loops and coverage tracking, the Claude Code testing guide covers patterns that apply across any PHP or framework project.
Service class architecture
Laravel's service classes are where business logic lives when you follow the thin controller principle. Claude Code generates service classes well when the pattern is explicit. The problem is that "service class" means different things to different teams. Specify exactly:
Add to CLAUDE.md:
## Service class rules
### Structure
// app/Services/ItemService.php
class ItemService
{
public function __construct(
private readonly ItemRepository $items,
private readonly NotificationService $notifications,
) {}
public function create(array $data, User $user): Item
{
$item = $this->items->create([...$data, 'user_id' => $user->id]);
$this->notifications->itemCreated($item);
return $item;
}
}
### Binding services
// Register in a ServiceProvider, not in config:
$this->app->bind(ItemService::class, function ($app) {
return new ItemService(
$app->make(ItemRepository::class),
$app->make(NotificationService::class),
);
});
### Rules
- Services are injected via constructor in controllers, never resolved with app()
- Services do not know about HTTP: no Request objects, no response helpers
- Services return domain objects or throw domain exceptions
- Service method names are verbs: create, update, cancel, export
The "no Request objects in services" rule is particularly important. Claude will sometimes pass the full Illuminate\Http\Request object into a service method when it is generating controller and service together. This couples your service to the HTTP layer, making it untestable without an HTTP context. Services should receive plain PHP types or validated data arrays.
Claude Code permission hooks for Laravel
Laravel has several Artisan commands that are destructive or irreversible. Claude Code can be configured to block these behind explicit permission while allowing the safe ones freely.
In .claude/settings.local.json:
{
"permissions": {
"allow": [
"Bash(php artisan make:*)",
"Bash(php artisan migrate:status*)",
"Bash(php artisan migrate:diff*)",
"Bash(php artisan queue:work*)",
"Bash(php artisan test*)",
"Bash(php artisan route:list*)",
"Bash(php artisan config:show*)",
"Bash(composer require*)",
"Bash(composer install*)"
],
"deny": [
"Bash(php artisan migrate*)",
"Bash(php artisan migrate:rollback*)",
"Bash(php artisan migrate:fresh*)",
"Bash(php artisan migrate:reset*)",
"Bash(php artisan db:seed*)",
"Bash(php artisan cache:clear*)",
"Bash(php artisan config:clear*)"
]
}
}
The make:* allow is important. You want Claude to scaffold files freely. The migration restrictions ensure that schema changes require explicit approval. The migrate:fresh and migrate:reset restrictions are particularly important in development: these commands drop and recreate all tables, and Claude will run them to start from a clean state if not blocked.
The cache:clear and config:clear restrictions prevent Claude from clearing caches as a debugging step without telling you, which can affect other development sessions sharing the same environment.
For the full picture of what Claude Code hooks can enforce and what other commands are worth gating, the Claude Code hooks guide covers the complete permission system.
Queue and job patterns
Laravel queues are where Claude Code needs the most guardrails. Jobs that look correct in code can create production incidents if they are not idempotent, if they swallow exceptions silently, or if they do not handle failure states cleanly.
Add a jobs section to your CLAUDE.md:
## Queue job conventions
### Job class structure
class ProcessItemExport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $backoff = 60; // seconds between retries
public function __construct(
private readonly int $itemId, // Pass IDs, not model instances
) {}
public function handle(ItemExportService $service): void
{
$item = Item::find($this->itemId);
if (!$item) {
// Item deleted before job ran, not an error, just exit
return;
}
$service->export($item);
}
public function failed(\Throwable $exception): void
{
Log::error('ItemExport job failed', [
'item_id' => $this->itemId,
'error' => $exception->getMessage(),
]);
// Optionally notify, but do not rethrow
}
}
### Rules
- Pass IDs, not model instances (models don't serialize safely across queue restarts)
- Always implement failed() method
- Always check if the model still exists before processing
- Jobs must be safe to run twice (idempotent)
- Use ShouldBeUnique for jobs that must not have duplicates in the queue
The "pass IDs, not model instances" rule is the most important job convention for Claude to follow. Claude will generate job constructors that accept Item $item because that is the most readable pattern. But serialized Eloquent models in the queue can cause stale data issues when the job processes after the model has been updated. Passing the ID and fetching fresh is always safer.
For patterns around Claude Code and custom workflows that go beyond single commands, the Claude Code custom agents guide covers how to build multi-step automation that can wrap Artisan workflows.
What Laravel developers get wrong first
Three mistakes appear consistently when Laravel developers start using Claude Code in production codebases.
Not specifying the PHP version. Claude will generate code using PHP 8.0 syntax (no intersection types, no fibers, older null-safe operator patterns) unless you specify 8.3 in CLAUDE.md. The generated code works, but it does not use the features available to you. Worse, in a team where static analysis runs against a PHP version target, Claude may generate code that fails the checks. One line prevents this.
Letting Claude generate controllers with form validation inline. Without the Form Request rule in CLAUDE.md, Claude will add validation directly to controller methods using $request->validate(). This works, but it couples validation logic to the controller and prevents reuse. Form Request classes are the Laravel convention for a reason: they are testable in isolation, reusable across routes, and readable. The CLAUDE.md rule enforces this from the first generated controller.
Not telling Claude which front-end stack is in use. Laravel projects range from full Blade with Livewire, to Inertia.js with React or Vue, to pure API backends. Claude will generate Blade views by default. If your project uses Inertia, Claude will generate the wrong response type in controllers (return view('items.index') instead of return Inertia::render('Items/Index', [...])) unless the stack is specified in CLAUDE.md. Add a single line: "Front end: Inertia.js with Vue 3 / React 18 / Blade, pick one and specify it."
Getting more from your Laravel workflow
The CLAUDE.md configuration in this guide produces a Laravel workflow where migrations never run without intent, Eloquent queries are eager-loaded by default, Pest tests run automatically after every change, and job classes are idempotent and failure-safe.
The principle is consistent with every framework guide: the more precisely you define your project's conventions, the less time you spend correcting Claude's output. A Laravel project with this configuration produces controller-service-resource pipelines that match your architecture, Pest tests that follow your factory conventions, and Artisan commands that generate the right scaffold for each resource type.
Start with the CLAUDE.md template above and focus on three sections first: the Artisan migration rules, the Pest test declaration, and the service class architecture. Those three changes cut the correction rate fastest. Add the Eloquent eager loading conventions and job rules as you encounter those areas.
For the broader workflow patterns that apply to any Claude Code backend project, the Claude Code best practices guide covers the underlying principles that make these framework-specific rules work. Claudify includes a Laravel-specific CLAUDE.md starter template with pre-configured Artisan permissions, Pest conventions, and Eloquent scope patterns as part of the Claude Code workflow kit.
More like this
Ready to upgrade your Claude Code setup?
Get Claudify