How to use Windsurf AI in Laravel — the right way (and what to avoid)


In recent months, developer tools powered by AI have exploded in popularity. One of the most interesting tools for PHP developers is Windsurf, a smart VS Code-based IDE that generates, explains, and refactors code based on natural language prompts.
At first glance, it feels magical: "Create a Laravel service class to compress images with ImageMagick" — and boom, a ready-to-use file appears.
But here's the catch: AI-generated code is not always correct, safe, or well-structured.
If you're not careful, you may end up introducing bugs, architectural chaos, or even security risks — all while thinking you're saving time.
The good part: Speed and flow
AI-assisted coding can be a productivity dream. You get:
Quick scaffolding (classes, jobs, tests)
Instant examples
Code completions that feel like pair programming
But like every tool, it must be used wisely — especially in a framework like Laravel, where conventions and architecture matter.
What to watch out for (based on real-world cases)
After digging into how Windsurf and similar tools behave in real Laravel projects, here are typical problems that can occur:
Hallucinated methods or classes: The AI invents code that looks real but doesn’t exist.
Missing validation or edge case handling
Security gaps: e.g.
protected $guarded = []
in models (mass assignment risk)Hardcoded logic where config should be used
Misplaced logic: Business logic ends up in controllers or Blade views (bad MVC)
Unreliable "refactoring": Sometimes the AI changes behavior instead of just structure
Duplicate or overly verbose code
The solution: A clear set of rules for AI
To prevent bad code and guide the AI toward real Laravel standards, I created a .windsurf.json
config file that defines exactly how I want my code to be structured.
This config:
Forces strict refactoring (no behavior changes!)
Requires clean separation (Services, Jobs, Form Requests)
Uses Laravel’s built-in features (Eloquent, Middleware, Validation)
Ensures the code is production-ready — no mocks, no placeholders
Keeps the code maintainable and testable
Works on macOS dev and Linux VPS servers
✅ Full Example: .windsurf.json
(Laravel + AI best practices)
{
"projectType": "laravel",
"frameworkVersion": "Laravel 11",
"phpVersion": "8.2",
"rules": {
"codingStyle": "psr-12",
"architecture": "config-driven-queue-jobs",
"preferRealCode": true,
"useInterfacesForServices": true,
"autoRegisterServices": true,
"splitLargeClasses": true,
"generateTests": true,
"avoidMocks": true,
"commentStyle": "phpdoc",
"strictTyping": true
},
"focusOn": [
"app/Services",
"app/Jobs",
"app/Console",
"config/",
"routes/web.php",
"routes/console.php"
],
"exclude": [
"vendor/",
"node_modules/",
"storage/",
"public/",
".env",
".idea/",
"tests/Browser/",
"bootstrap/cache/"
],
"goals": [
"Refactor without changing behavior.",
"Remove redundancy using services, helpers, collections.",
"Follow Laravel standards: Eloquent, FormRequests, Middleware, Policies.",
"Keep business logic out of views and controllers.",
"Use production-ready code only – no placeholders or mocks.",
"Ensure compatibility between macOS dev and Linux production."
],
"projectConfig": {
"categories": {
"source": "config/categories.php",
"rules": [
"Never hardcode category names — always use config.",
"Categories may define prompts, publishing rules, and media behavior."
]
}
},
"refactoring": {
"rules": [
"Definition: Refactoring means changing internal structure without changing external behavior.",
"When 'refactoring' is requested, the output and logic must remain 100% unchanged.",
"Inputs, outputs, conditions, and side effects must stay identical.",
"Only internal structure, names, or modularity may improve.",
"Each refactor must be validated via tests or manual comparison."
]
},
"environmentHandling": {
"rules": [
"Never modify the .env file directly.",
"Always prompt before changing environment variables."
]
},
"deploymentConsiderations": {
"rules": [
"Code must run identically on macOS (dev) and Linux VPS (production).",
"Avoid system-dependent paths or tools (e.g. brew-only installs)."
]
},
"codingStandards": {
"rules": [
"Follow PSR-12 and enable strict_types.",
"Use descriptive variable and method names.",
"Follow Laravel naming conventions (UserController, ArticleService, etc.)"
]
},
"redundancyElimination": {
"rules": [
"Eliminate duplicate logic via helpers, traits, services.",
"Prefer Laravel collections (map, filter, reduce) over manual loops."
]
},
"fileManagement": {
"rules": [
"Avoid files over 500 lines – split responsibilities.",
"Keep controllers light – delegate to services."
]
},
"documentation": {
"rules": [
"Use PHPDoc for all public methods and classes.",
"Add inline comments for complex or non-obvious logic."
]
},
"laravelBestPractices": {
"rules": [
"Use dependency injection instead of facades.",
"Handle auth and roles via middleware or policies.",
"Use FormRequest for validation – never validate in the controller.",
"Prefer Eloquent with scopes and relationships over raw queries."
]
},
"codeGeneration": {
"rules": [
"No partial code or pseudo-code.",
"Every method must be fully implemented and usable.",
"Edge cases, error handling, and return types must be included."
]
},
"testing": {
"rules": [
"Generate testable, decoupled code.",
"Follow Laravel's test structure (Unit, Feature).",
"Create test classes when appropriate."
]
}
}
Subscribe to my newsletter
Read articles from Martin Schenk directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
