·9 min read

Laravel Cursor Rules: AI Guidelines for PHP Teams

Copyable Laravel Cursor rules for Eloquent, form requests, actions, and Pest tests — plus how to share the same AI guidelines with Claude Code and Copilot.

Laravel Cursor rules are markdown files in .cursor/rules/ that tell Cursor's AI how your Laravel codebase actually works: which framework version you target, that validation lives in form requests, that business logic belongs in action classes, and that every test is written in Pest. Without them, the AI generates plausible-looking PHP that mixes Laravel 10 idioms into a Laravel 12 app — and your reviewers pay for it.

Laravel is unusually prone to this because the framework changed shape recently. Laravel 11 removed the HTTP kernel and restructured the application skeleton, Laravel 12 kept the streamlined layout, and Pest replaced PHPUnit as the community's default test runner. Model training data is full of the old world. Rules are how you anchor the AI in the new one. This guide gives you copyable .mdc rules for the whole stack, then shows how to ship the same guidelines to Claude Code, Copilot, and Windsurf without maintaining four copies.

What AI Gets Wrong in Laravel Codebases

Ask an AI assistant to add a feature to a Laravel app with no rules in place and you'll see the same category of mistake over and over: code that was idiomatic three major versions ago.

  • The old skeleton. References to app/Http/Kernel.php and $routeMiddleware arrays. Since Laravel 11, middleware and exception handling are configured in bootstrap/app.php, and the kernel file doesn't exist.
  • Property casts. protected $casts = [...] on models instead of the casts() method Laravel 11 introduced.
  • Inline validation. $request->validate([...]) scattered through controllers instead of dedicated form request classes.
  • Fat controllers. Query logic, notification dispatch, and payment calls stuffed into a controller method instead of a single-purpose action class.
  • N+1 queries. Lazy-loading relationships inside loops because nothing told the AI to eager load.
  • PHPUnit tests. Class-based TestCase files in a codebase that is 100% Pest.
  • env() everywhere. Direct env() calls in application code, which silently return null once you cache config in production.

None of these fail loudly. They compile, they often pass a happy-path test, and they land in code review as debt. Your Laravel AI guidelines should turn each convention into a short, checkable rule scoped to the files it applies to — which is exactly what Cursor's .mdc format is designed for.

Copyable Laravel Cursor Rules: Eloquent, Form Requests, and Actions

Modern Cursor rules live in .cursor/rules/*.mdc — markdown files with YAML frontmatter that controls when each rule loads. Use alwaysApply: true for your core stack rule and globs to scope everything else. (For a full breakdown of the format, see our Cursor rules guide.)

Start with a core rule that pins versions and project-wide conventions:

---
description: Laravel stack and project-wide conventions
alwaysApply: true
---

# Laravel core

- Laravel 12, PHP 8.4, Pest for all tests
- declare(strict_types=1) at the top of every PHP file
- Middleware, exceptions, and routing are configured in bootstrap/app.php.
  app/Http/Kernel.php does NOT exist — never reference it.
- Never call env() outside the config/ directory. Read config('services.x')
  instead.
- Formatting: Pint (pint.json is authoritative). Static analysis: Larastan
  at max level.
- Prefer artisan generators over hand-written boilerplate:
  php artisan make:model -mf, make:request, make:policy, make:job

Then scope an Eloquent rule to model and database files:

---
description: Eloquent model and query conventions
globs:
  - "app/Models/**"
  - "database/**"
---

# Eloquent

- Use the casts() method, never the $casts property
- Declare $fillable explicitly on every model. Never use $guarded = [].
- Relationship methods must declare return types (HasMany, BelongsTo, ...)
- Eager load with with()/load(). Model::shouldBeStrict() is enabled in
  AppServiceProvider, so lazy loading throws — never rely on it.
- Reusable filters are query scopes on the model, not where-chains
  repeated across controllers
- Prefer the query builder over DB::raw(). If raw SQL is unavoidable,
  always use bound parameters.

And an HTTP-layer rule that enforces the form request + action pattern:

---
description: Controllers, form requests, and actions
globs:
  - "app/Http/**"
  - "app/Actions/**"
---

# HTTP layer

- Every store/update endpoint validates through a form request class.
  Put authorization in authorize() or a policy — never inline.
- Controllers are thin: resolve the form request, call one action,
  return an API resource. No business logic, no queries.
- Business logic lives in single-purpose action classes in app/Actions,
  each with one public handle() method:

  class PublishPost
  {
      public function handle(User $user, Post $post): Post
      {
          // ...
      }
  }

- API responses return JsonResource classes, never raw models —
  raw models leak attributes when the schema changes.
- Use route model binding instead of manual findOrFail() calls.

A note on sourcing: most Cursor rules PHP developers copy from public gists are framework-agnostic or written for Laravel 9/10. Before pasting anything, check it for $casts, kernel references, and PHPUnit assumptions — outdated rules are worse than no rules, because the AI treats them as ground truth.

Pest Testing Conventions as Rules

Testing is where unruled assistants drift the most. Left alone, the Pest tests AI tools generate are often not Pest at all — they're PHPUnit classes with a .php extension in your tests/Feature folder. Scope a rule to your test directory:

---
description: Pest testing conventions
globs:
  - "tests/**"
---

# Testing

- Pest only: it()/test() functions. Never generate PHPUnit class syntax.
- Feature tests exercise HTTP endpoints via getJson()/postJson().
  Unit tests are reserved for pure logic with no framework dependencies.
- Use the RefreshDatabase trait. Build state with model factories —
  never call seeders inside a test.
- Use datasets for input variations instead of copy-pasting tests.
- Assert both sides: database state with assertDatabaseHas() and
  response shape with assertJsonStructure().
- Keep the arch presets green: arch()->preset()->php() and
  arch()->preset()->laravel() ban dd(), debug leftovers, and env()
  misuse in application code.

With this in place, generated tests come back in your house style:

it('publishes a draft post', function () {
    $post = Post::factory()->draft()->create();

    $this->actingAs($post->author)
        ->postJson("/api/posts/{$post->id}/publish")
        ->assertOk();

    expect($post->refresh()->status)->toBe(PostStatus::Published);
});

The payoff compounds: once the AI writes idiomatic Pest, it also reads your existing tests as examples, and quality snowballs in the right direction.

Cross-Tool Setup: Cursor, Claude Code, and Copilot

Real PHP teams rarely standardize on one assistant. Cursor reads .cursor/rules/, Claude Code reads CLAUDE.md and skills, GitHub Copilot reads .github/copilot-instructions.md, Windsurf reads .windsurf/rules/. Maintaining four hand-synced copies of the same Laravel conventions is how drift starts — someone updates the Cursor rule for casts() and forgets the Copilot file. (We compared every format in AI coding rules across tools.)

The fix is to author once and generate the rest. Here's the workflow with the localskills CLI:

1. Author and verify in one tool

Write the .mdc rules above in .cursor/rules/ and use Cursor for a few days. Tighten anything the AI still gets wrong — rules earn their place by failing in practice first.

2. Package the rules as a skill

A skill is a folder with a root SKILL.md containing your conventions. Move your rule content into it (you can keep per-topic files alongside it in the same package):

npm install -g @localskills/cli
localskills login

3. Publish it to your org

localskills publish

Skills are versioned with rollback, and visibility can be public, private, or unlisted — so internal Laravel conventions stay internal.

4. Install to every tool at once

Each teammate (or your onboarding script) runs one command:

localskills install your-org/laravel-rules --target cursor claude copilot

The CLI writes each tool-native format from the single published skill — .cursor/rules/*.mdc for Cursor, .claude/skills/ for Claude Code, .windsurf/rules/ for Windsurf, and so on. Update the skill, bump the version, reinstall: every tool stays in sync from one source of truth.

Version Pitfalls: The Deprecated .cursorrules Format

Search for Laravel cursorrules examples and most of what you'll find is a single .cursorrules file at the repo root. That format is deprecated. Cursor still reads it for backwards compatibility, but it has real costs:

  • No scoping. A .cursorrules file is all-or-nothing. Your Blade conventions ride along in context while the AI edits a migration, wasting attention (and tokens) on irrelevant instructions.
  • No metadata. .mdc frontmatter gives you description, globs, and alwaysApply; the legacy file has none, so Cursor can't decide when a rule matters.
  • One giant blob. A single 300-line file is harder to review in a PR than six focused rules with obvious diffs.
  • Stale content. Because the format is old, the rules inside these files usually are too — expect $casts properties, kernel middleware arrays, and PHPUnit everywhere.

Migrating is mechanical: split the file by concern (core, Eloquent, HTTP, testing, Blade), add frontmatter with globs, save each as an .mdc file under .cursor/rules/, and delete .cursorrules. If you want worked before/after examples, our .cursorrules examples collection shows the modern equivalents, and there's a dedicated walkthrough for migrating .cursorrules to shared skills if you're taking the team-wide route.

Extending Laravel Cursor Rules into a Skill

Rules top out at "conventions in prose." A skill can carry more, because it's a full folder package — up to 100 MB and 500 files per version — with a root SKILL.md plus any supporting material. A Laravel skill for your team might include:

  • SKILL.md — the conventions above, merged and organized
  • Stub files — a reference action class, form request, and API resource the AI can pattern-match instead of inventing structure
  • Docs — your API error envelope, pagination contract, and naming rules for events and jobs
  • Templates — a Pest feature-test skeleton wired for your factories

Because skills are versioned, a bad rule change is one rollback away, and download analytics show which teams actually installed the update. If your org keeps its skill library mirrored to GitHub, edits made in the repo import back as new skill versions automatically — so the Laravel lead can maintain conventions in a normal PR flow. The full packaging reference lives in the localskills docs.

Start small: the six rules in this post cover the mistakes that cost Laravel teams the most review time. Add a rule only when the AI gets something wrong twice — that's the signal it belongs in the file.


Ready to share one set of Laravel conventions with every AI tool your team uses? Create a free account and publish your rules as a versioned skill.

npm install -g @localskills/cli
localskills login
localskills publish