One SaaS invoicing system. One AI agent. Zero hand-holding. Here’s exactly what it got right, what it got wrong, and what the workflow looks like in 2026.
This is not a tutorial. It’s a report.
I gave Claude Code a real task: build a complete Filament v5 admin panel for a SaaS invoicing system. Full spec — customers, products, invoices, line items, payments, status transitions, authorization rules. I documented every step, every failure, every correction, and every moment where it genuinely surprised me.
Here’s the honest account.
The Setup
The task: Build a Filament v5 admin panel for a SaaS invoicing system:
- Manage customers, products, invoices with line items
- Invoice status: draft → sent → paid → cancelled
- Line items auto-calculate subtotals reactively
- Send invoice emails to customers
- Record and reconcile payments
- Only admins can delete invoices
- Invoices can only be cancelled if not yet sent
Tools used: Claude Code (CLI, Plan Mode), Filament Blueprint via Laravel Boost, fresh Laravel 12 + Filament v5, a tight CLAUDE.md.
My CLAUDE.md conventions:
# Project Conventions
- PHP 8.3, Laravel 12, Filament v5, Livewire v4
- Use Enums for status fields with HasLabel, HasColor, HasIcon
- Always generate Pest smoke tests for each resource
- Use $fillable on all models
- Use Resource::getUrl() not route() for Filament routes
- Never generate View page or Infolist unless asked
- Run tests after every resource generation
Round 1: Without Blueprint
I tried the naive approach first — no Blueprint, just a direct prompt.
What it got right immediately:
- Correct model relationships (Customer hasMany Invoice, Invoice hasMany LineItem)
- Proper migrations with foreign keys
- Correct Filament v5 component names
- Enum-backed status field
What it got wrong:
// Generated — MISSING preload() and createOptionForm()
Select::make('customer_id')
->relationship('customer', 'name')
->searchable()
// Should have been
Select::make('customer_id')
->relationship('customer', 'name')
->searchable()
->preload()
->createOptionForm([
TextInput::make('name')->required(),
TextInput::make('email')->email()->required(),
])
The reactive line item subtotal didn’t update on quantity change. The status guard allowed cancellation of paid invoices — which I’d explicitly excluded.
Time to fix manually: ~45 minutes.
Round 2: With Blueprint
Wiped the files. Switched to Plan Mode with Blueprint loaded.
The 400-line planning output was completely different — it specified:
- Every Resource with exact file paths and artisan commands
- Reactive field config with exact
->live(onBlur: true)and->afterStateUpdated()callbacks - Status machine with explicit guards: cancel only from draft/sent, never from paid
- Authorization:
->hidden(fn() => !auth()->user()->isAdmin())on delete actions - Email action with
->requiresConfirmation()
The reactive line item came out correct on the first try:
TextInput::make('quantity')
->numeric()
->live(onBlur: true)
->afterStateUpdated(function ($state, Set $set, Get $get) {
$set('subtotal', round($state * $get('unit_price'), 2));
}),
TextInput::make('unit_price')
->numeric()
->prefix('$')
->live(onBlur: true)
->afterStateUpdated(function ($state, Set $set, Get $get) {
$set('subtotal', round($get('quantity') * $state, 2));
}),
The cancel action guard was also correct:
Action::make('cancel')
->visible(fn(Invoice $record): bool =>
in_array($record->status, [InvoiceStatus::Draft, InvoiceStatus::Sent])
)
->action(fn(Invoice $record) => $record->cancel()),
The Scorecard
GOT RIGHT — No corrections needed:
Enums with all three Filament interfaces — Because CLAUDE.md specified it, every status Enum implemented HasLabel, HasColor, and HasIcon correctly. The exact return types matched the interface definitions.
Pest smoke tests — Every resource got a smoke test. All passed on first run.
Authorization policy — InvoicePolicy generated correctly, wired to the resource.
Model relationships and lifecycle hooks — calculateTotal() called in the right places.
GOT WRONG — Required manual correction:
Custom Tailwind in Blade — Added a custom widget with bespoke styling. Claude Code didn’t register the Blade folder in theme.css (Filament v5 requirement). Panel looked broken until I fixed it.
route() instead of Resource::getUrl() — One notification action ignored CLAUDE.md. Minor but had to fix it.
Email action assumed method existed — Generated the Action correctly but didn’t generate the underlying sendToCustomer() method or Mailable. It can’t invent business logic — but it also didn’t flag the missing dependency.
The Time Comparison
| Task | Without AI | With Claude Code + Blueprint |
|---|---|---|
| Models + migrations | 2 hours | 8 minutes |
| All 3 Resources (basic) | 4 hours | 22 minutes |
| Reactive line item fields | 1 hour | First try with Blueprint |
| Status transition guards | 45 minutes | First try with Blueprint |
| Enums with interfaces | 30 minutes | Automatic via CLAUDE.md |
| Pest smoke tests | 1.5 hours | Automatic |
| Manual corrections | — | 35 minutes |
| Total | ~9.5 hours | ~1.5 hours |
The Workflow That Actually Works in 2026
1. Invest 30 minutes in CLAUDE.md upfront. Every convention documented saves correction time on every resource generated. The Enum interface rule alone saved 20 minutes per status field.
2. Use Blueprint for complex features. For simple CRUD, skip it — direct generation is fast enough. For reactive fields, state machines, or complex authorization, Blueprint’s planning prevents the errors that cost an hour of debugging.
3. Let the implementing agent run tests after each resource. Small feedback loops catch issues before they compound.
4. Keep a manual review checklist for what AI consistently misses:
- Custom Blade files: register in theme.css
- Email/notification actions: verify the underlying method exists
- Route helpers: audit for route() vs Resource::getUrl()
- Complex authorization: read the generated policy carefully
5. Planning and implementing should be separate sessions. The Blueprint docs are explicit: planning guidelines consume context window. The plan itself contains everything the implementing agent needs.
Is It Worth It?
Yes. Unambiguously.
Not because it’s magic — it makes mistakes, misses dependencies, occasionally ignores CLAUDE.md instructions. The value is that the mistakes are small and fixable, while the 9 hours of boilerplate it saves are real and tedious.
The developers getting the most value from Claude Code in 2026 are not asking it to build entire systems. They’ve learned to direct it precisely — clear specs, tight conventions, short feedback loops — and they know which 20% of the output needs human review before it ships.
The panel is running. The tests are green. One engineer worked like three.
Follow me for daily deep-dives on Laravel, PHP, Vue.js, and AI integrations. Two weeks down — this series continues next week.
