Laravel AI Rules

Rules for Laravel projects: Eloquent conventions, form requests, queued jobs, Livewire components, and Pest test style. Keeps the AI from defaulting to PHPUnit and old Laravel patterns.

PHPLaravel#php#laravel#livewire#eloquentLast updated 2026-05-05
tune

Want to customize this rules file? Open the generator with this stack pre-loaded.

Open in generatorarrow_forward

Save at .cursor/rules/main.mdc

Laravel

Project context

This is a Laravel 11 project using Livewire 3 for interactive UI and Pest for tests. We follow Laravel conventions; resist the urge to import patterns from other ecosystems that don't fit.

Stack

  • PHP 8.3+
  • Laravel 11+
  • Livewire 3 + Alpine.js for UI interactivity
  • Tailwind CSS 4
  • Pest for tests (not PHPUnit syntax — though Pest runs on PHPUnit)
  • Eloquent for ORM
  • Laravel Pint for code style
  • Composer + npm/bun

Folder structure (Laravel defaults)

app/
  Http/
    Controllers/
    Middleware/
    Requests/             — FormRequest classes
  Livewire/               — Livewire components
  Models/
  Policies/
  Providers/
  Services/               — application services
  Jobs/
  Events/
  Listeners/
  Notifications/
config/
database/
  migrations/
  factories/
  seeders/
resources/
  views/                  — Blade templates
  js/
  css/
routes/
  web.php
  api.php
tests/
  Feature/
  Unit/

Models (Eloquent)

  • One model per table; pluralized table name auto-derived
  • Always set $fillable (mass-assignment whitelist) — never $guarded = []
  • Cast attributes: protected $casts = ['published_at' => 'datetime', 'metadata' => 'array']
  • Define relationships explicitly: belongsTo, hasMany, belongsToMany
  • Scopes: public function scopePublished($query) { $query->whereNotNull('published_at'); }
class Post extends Model
{
    protected $fillable = ['title', 'body', 'author_id', 'published_at'];
    protected $casts = ['published_at' => 'datetime'];

    public function author() { return $this->belongsTo(User::class); }
    public function comments() { return $this->hasMany(Comment::class); }

    public function scopePublished($q) { $q->whereNotNull('published_at'); }
}

Controllers

  • Resource controllers when you have full CRUD: php artisan make:controller PostController --resource
  • Single-action controllers (__invoke) for one-off endpoints
  • Use FormRequest classes for validation, never validate in the controller method body
  • Return Eloquent resources or Inertia/Blade views — never bare arrays for API responses
class StorePostRequest extends FormRequest
{
    public function authorize(): bool { return $this->user()->can('create', Post::class); }

    public function rules(): array {
        return [
            'title' => ['required', 'string', 'max:200'],
            'body'  => ['required', 'string'],
        ];
    }
}

Migrations

  • php artisan make:migration create_posts_table
  • Always set columns nullable / not nullable explicitly — be deliberate
  • Add foreign keys with ->constrained()->cascadeOnDelete() where appropriate
  • Add indexes for any column used in where()
  • Never edit a committed migration; create a new one

Livewire components

  • One component per discrete piece of UI
  • Mount data in mount(); render via render() method that returns a view
  • Use #[Computed] attribute for derived properties
  • Use #[Validate] attribute for property validation
  • Emit events with $this->dispatch('event-name', payload)
class PostList extends Component
{
    #[Computed]
    public function posts() { return Post::published()->latest()->take(20)->get(); }

    public function render() { return view('livewire.post-list'); }
}

Background jobs

  • php artisan make:job ProcessPost
  • Implement ShouldQueue to dispatch async
  • Use tries, backoff, timeout properties for retry behavior
  • Idempotent — jobs may run twice; design for it

Authorization

  • Use Policies (php artisan make:policy PostPolicy --model=Post)
  • Register in AuthServiceProvider
  • Call via $this->authorize('update', $post) in controllers, @can('update', $post) in Blade

Patterns to follow

  • Eager-load to avoid N+1: Post::with('author', 'comments')->get()
  • Use Pint for formatting; don't hand-format
  • Use Str:: helpers (Str::slug, Str::random) over manual string manipulation
  • Use Carbon for dates — Laravel sets it up by default
  • Use Mailables / Notifications for emails — never mail() directly

Patterns to avoid

  • $guarded = [] — always whitelist with $fillable
  • Querying inside Blade templates — pass eager-loaded data from the controller
  • Hardcoded values that belong in config/config('services.foo.key')
  • Editing a committed migration — create a new one
  • DB::raw without parameter binding when params are user input — SQL injection
  • Validating in controllers — use FormRequest

Testing

  • Pest, not PHPUnit class syntax: it('does X', function () { ... });
  • Feature tests hit the routes and assert responses
  • Unit tests for service logic in isolation
  • Use RefreshDatabase trait to reset between tests
  • Use factories liberally: Post::factory()->count(5)->create()
it('lists posts', function () {
    Post::factory()->count(3)->create();
    $response = $this->get('/posts');
    $response->assertOk();
    $response->assertSeeText('Post 1');
});

Tooling

  • php artisan serve — dev server (or php artisan octane:start for prod-style)
  • php artisan migrate — apply migrations
  • php artisan tinker — REPL
  • php artisan test (or vendor/bin/pest) — tests
  • vendor/bin/pint — formatter
  • npm run dev (Vite) — frontend assets

AI behavioral rules

  • Always whitelist with $fillable, never $guarded = []
  • Validate via FormRequest classes — never inline $request->validate(...) in non-trivial controllers
  • Use eager loading (with(...)) wherever a foreign key gets touched in a list view
  • Default to Pest syntax for new tests; only PHPUnit class style if the existing suite uses it
  • Use Laravel's helpers (Str::, Arr::, Carbon::) before reaching for raw PHP
  • Never modify a committed migration
  • Don't bypass mass-assignment protection or skip validations without justifying why
  • Run pest and pint before declaring a task done

Frequently asked

How do I use this Laravel rules file with Cursor?

Pick "Cursor (.cursor/rules/*.mdc)" from the format dropdown above and click Copy. Save it at .cursor/rules/main.mdc in your project root and restart Cursor. The legacy .cursorrules format still works if you're on an older Cursor version — pick that option instead.

Can I use this with Claude Code (CLAUDE.md)?

Yes — pick "Claude Code (CLAUDE.md)" from the format dropdown above and copy. Save the file as CLAUDE.md at your repo root. Claude Code reads it automatically on every session. For monorepos, you can also drop nested CLAUDE.md files in subdirectories — Claude merges them when working in those paths.

Where exactly do I put this file?

It depends on the AI tool. Cursor reads .cursorrules or .cursor/rules/*.mdc at the project root. Claude reads CLAUDE.md at the project root. Copilot reads .github/copilot-instructions.md. The "Save at" path under each format in the dropdown shows the exact location for the format you picked.

Can I customize these Laravel rules for my project?

Yes — that's what the generator is for. Click "Open in generator" above and the wizard loads with this stack's defaults pre-selected. Toggle on or off the conventions you want, then re-export in your AI tool's format.

Will using this rules file slow down my AI tool?

No. Rules files count toward the model's context window but not toward latency in any noticeable way. The file is loaded once per session, not per token. The library files target 250–400 lines, well within every tool's recommended budget.

Should I commit this file to git?

Yes. The rules file is project documentation that benefits every developer using the AI tool. Commit it. The exception is personal-global settings (e.g. ~/.claude/CLAUDE.md) which are user-scoped and stay out of the repo.