For a Laravel SaaS using Eloquent models, form requests, queued jobs, and Cashier for Stripe subscriptions. It assumes Blade or Inertia on the front end and Pest for tests. Use it when Laravel conventions and artisan tooling should be followed rather than improvised.
# AGENTS.md
This is a subscription SaaS built with Laravel, Eloquent, and Stripe via Cashier.
## Project
Stack: PHP 8.3, Laravel 11, MySQL, Redis queues, Laravel Cashier, Inertia with Vue 3, Vite, Pest, Pint, Larastan.
Layout:
- `routes/web.php`, `routes/api.php`, `routes/console.php`
- `app/Http/Controllers/` thin controllers, one action group per resource
- `app/Http/Requests/` form request validation
- `app/Http/Middleware/`
- `app/Actions/` single purpose invokable classes holding business logic
- `app/Models/` Eloquent models, relationships, casts, scopes
- `app/Policies/` authorization
- `app/Jobs/` queued work, `app/Events/` and `app/Listeners/`
- `database/migrations/`, `database/factories/`, `database/seeders/`
- `resources/js/Pages/` Inertia pages, `resources/js/Components/`
- `config/` framework and package configuration
Entry points: `routes/web.php`, `app/Providers/AppServiceProvider.php`, `config/cashier.php`.
## Commands
```bash
composer install
npm install
php artisan serve # dev server on :8000
npm run dev # vite dev server
npm run build # production assets
php artisan migrate # apply migrations
php artisan migrate:fresh --seed # local reset only
php artisan make:model Invoice -mfR # model, migration, factory, request, controller
php artisan queue:work # process jobs
php artisan schedule:work # run the scheduler locally
php artisan test # full suite
php artisan test --filter=SubscriptionTest
./vendor/bin/pint # format to PSR-12
./vendor/bin/phpstan analyse # static analysis
php artisan optimize:clear # clear config, route, view caches
```
Generate classes with artisan rather than creating files by hand, so namespaces and stubs stay correct.
## Code style
- Controllers are thin. Validation goes in a form request, authorization in a policy, business logic in an action.
- Never call `request()` outside a controller or middleware. Pass data down explicitly.
- No queries in Blade or in an Inertia page component. Load relations eagerly with `with()` in the controller.
- Guard against N+1 by keeping `Model::preventLazyLoading()` enabled in non production environments.
- Use route model binding rather than manual `find()` plus 404 handling.
- Mass assignment: define `$fillable` explicitly, never `$guarded = []`.
- Money is stored in integer minor units, formatted only at the view layer.
- Anything slower than a database write goes into a queued job, not an inline call.
- Follow PSR-12 through Pint. Typed properties, typed arguments, and return types everywhere.
- Config values are read through `config()`, never `env()` outside `config/`.
## Boundaries
Do not touch without explicit instruction:
- Existing files in `database/migrations/`. Migrations are append only once applied.
- `.env` and any credential. New keys go in `.env.example` and a `config/` file.
- `composer.lock` and `package-lock.json`. Use `composer require` and `npm install <pkg>`.
- Cashier tables, subscription records, and Stripe price identifiers.
- `.github/workflows/`, deployment scripts, and server provisioning files.
Needs human review: policies and gates, anything in `app/Jobs/` that touches billing, webhook handling, and any migration that drops or renames a column.
Do not run `migrate:fresh` against anything but the local database.
## Testing
- `php artisan test` runs Pest. Use `--filter` to run one test while iterating.
- Tests use `RefreshDatabase` against a dedicated test connection. Never point tests at the development database.
- Required tests: every route (happy path plus authorization failure), every policy, every job, and all billing state transitions.
- Build data with factories in `database/factories/`. Seeders are for local development only.
- Fake external services with `Http::fake()`, `Queue::fake()`, `Mail::fake()`. Do not hit Stripe in tests, use Cashier's test helpers.
- A bug fix ships with a regression test.
## Git workflow
- Branch from `main`: `feat/short-description`, `fix/short-description`.
- Conventional commits: `feat(billing): add annual plan upgrade path`.
- Migrations ship in the same commit as the code that depends on them.
- Run Pint before committing so formatting never appears in a review diff.
- PR description lists migrations, queue changes, new config keys, and any artisan command needed at deploy.
- Never commit to `main`.
Laravel puts a feature across controllers, requests, actions, models, and views, and an agent needs to know which of those the project actually uses. Naming the directories and the front end approach avoids code landing in both Blade and Inertia. Entry points make routing and service registration findable.
Artisan is the interface to nearly everything, and generating files with it produces correctly namespaced code. Listing migrate, queue, test, and cache commands lets the agent verify work end to end. Composer versus npm split needs to be explicit since both exist here.
Laravel style is mostly about honoring conventions: validation in form requests, authorization in policies, and no queries in Blade. These are concrete and reviewable. PSR-12 and Pint handle formatting so the section can stay on architecture.
Migrations, environment files, and billing configuration are the high risk surfaces. Cashier tables and subscription state should never be edited ad hoc. Naming the human review list keeps money and access decisions with a person.
Laravel makes feature tests cheap, so the expectation should be that endpoints and jobs are covered rather than mocked into meaninglessness. Stating the database strategy prevents an agent from running tests against development data. Factories are the right default over seeders.
Migrations must accompany the code that needs them or a deploy fails halfway. Commit conventions keep history scannable across a team. PR notes should list the artisan commands a deploy requires.
Claude Code looks for CLAUDE.md. Most other agents and editors read AGENTS.md. Rather than maintaining both and letting them drift, keep one real file and symlink the other:
ln -s AGENTS.md CLAUDE.md
git add AGENTS.md CLAUDE.md Git stores the symlink, so it survives cloning on macOS and Linux. On Windows it needs developer mode or a stub file that references the real one. The full comparison is in CLAUDE.md vs AGENTS.md.
Next.js SaaS
App Router SaaS with Supabase auth, Postgres, and Stripe billing.
Turborepo Monorepo
Multi-package workspace with shared UI, config, and typed contracts.
Python / FastAPI
Async FastAPI service with SQLAlchemy, Alembic, and pytest.
Django SaaS
Django app with per-app structure, Celery jobs, and Stripe billing.