Stu Mason
Stu Mason
Guide

Code Quality Standards That Make White-Label Work Actually Work

Stuart Mason7 min read

Here's a truth about white-label development that took me a while to learn: the code you deliver under someone else's name has to be better than the code you'd write for yourself.

Code Quality Standards That Make White-Label Work Actually Work

Here's a truth about white-label development that took me a while to learn: the code you deliver under someone else's name has to be better than the code you'd write for yourself.

When it's your project, you can get away with a slightly messy migration or a controller that's a bit too fat. You know where the bodies are buried. But when you hand code to an agency — code their team needs to maintain, extend, and debug — every shortcut is a landmine someone else will step on.

So I have standards. Not vague "write clean code" standards. Concrete, enforceable, documented standards that I apply to every project regardless of size.

The Foundation: Consistency Above All

The single most important quality standard isn't about any specific practice. It's about consistency. A codebase where every file follows the same conventions — even if those conventions aren't your personal preference — is dramatically easier to work with than a codebase where every file is a different developer's personal style.

This means:

Naming conventions that never vary. Models are singular (Booking, not Bookings). Controllers follow resource naming (BookingController). Actions are verb-noun (ApproveBooking, SendNotification). Form requests match their purpose (StoreBookingRequest, UpdateBookingRequest). No exceptions. Ever.

Directory structure that's predictable. Business logic lives in Actions, organised by domain: app/Actions/Booking/, app/Actions/User/, app/Actions/Payment/. DTOs live in app/DTOs/. Nothing clever. Nothing that requires a map to navigate.

One way to do each thing. If we're using Actions for business logic, we don't also have logic in controllers, models, or random service classes. If we're using DTOs for Inertia props, we don't also pass raw arrays. Pick a pattern and use it everywhere.

Type Hints Everywhere

I'm aggressive about type hints. Return types on every method. Parameter types on every argument. Property types on every class property.

final class ApproveBooking
{
    public function __construct(
        private readonly BookingRepository $bookings,
        private readonly NotificationService $notifications,
    ) {}

    public function execute(Booking $booking, User $approvedBy): BookingData
    {
        $booking->update([
            'status' => BookingStatus::Approved,
            'approved_by' => $approvedBy->id,
            'approved_at' => now(),
        ]);

        $this->notifications->send(
            $booking->user,
            new BookingApprovedNotification($booking),
        );

        return BookingData::fromModel($booking->fresh());
    }
}

No mixed types. No untyped parameters. No @return void PHPDoc on a method that already declares : void. The code documents itself.

Why does this matter for white-label? Because when the agency's developer opens a file six months after I've delivered it, they can immediately see what goes in and what comes out. No guessing. No diving into the implementation to understand the interface.

Automated Formatting with Laravel Pint

Every project gets Laravel Pint. Non-negotiable. It runs on every PR, and the CI pipeline fails if there are formatting issues.

My Pint configuration is close to Laravel's defaults with a few adjustments:

{
    "preset": "laravel",
    "rules": {
        "concat_space": {
            "spacing": "none"
        },
        "ordered_imports": {
            "sort_algorithm": "alpha"
        },
        "single_trait_insert_per_statement": true,
        "final_class": true
    }
}

The specifics matter less than the automation. When I deliver code to an agency, I don't want the first PR review to be "can you fix the formatting?" Pint handles it. Every file is formatted identically. Every import is sorted alphabetically. Every class is final unless there's a reason it shouldn't be.

Running vendor/bin/pint --dirty before every commit catches anything I missed. But honestly, with CI enforcement, it's rare for formatting issues to reach a PR.

Testing with Pest

Tests aren't optional on white-label work. They're a core deliverable.

I use Pest for everything. Feature tests that hit real endpoints, unit tests for complex business logic, and — increasingly — browser tests with Pest 4 for critical user flows.

it('approves a pending booking', function () {
    $owner = User::factory()->create();
    $booking = Booking::factory()
        ->pending()
        ->create();

    $this->actingAs($owner)
        ->postJson("/api/bookings/{$booking->id}/approve")
        ->assertSuccessful()
        ->assertJson([
            'data' => [
                'status' => 'approved',
            ],
        ]);

    expect($booking->fresh())
        ->status->toBe(BookingStatus::Approved)
        ->approved_by->toBe($owner->id)
        ->approved_at->not->toBeNull();
});

it('rejects approval of an already approved booking', function () {
    $booking = Booking::factory()
        ->approved()
        ->create();

    $this->actingAs(User::factory()->create())
        ->postJson("/api/bookings/{$booking->id}/approve")
        ->assertUnprocessable();
});

Every feature gets happy path tests, failure path tests, and edge case tests. Factories have states that match business logic (pending(), approved(), cancelled()). Assertions are specific — assertUnprocessable() not assertStatus(422).

For white-label, tests serve a second purpose: they're documentation. When the agency's developer needs to understand how a feature works, the tests show them. "It approves a pending booking." "It rejects approval of an already approved booking." That's clearer than any README.

The CLAUDE.md Standards System

On my own projects, and increasingly on agency projects, I use what I call the Claudavel approach — a CLAUDE.md file in the project root that documents every coding standard, convention, and pattern.

This isn't just for AI assistants (though it helps there too). It's a single source of truth that any developer can read and immediately understand:

  • What directory structure to follow
  • How to create new features (which generator commands to use)
  • How to run tests
  • What patterns are used (Actions, DTOs, Form Requests)
  • What naming conventions apply
  • How to run formatting

The standards directory goes deeper:

docs/standards/
├── README.md        # Quick reference checklist
├── general.md       # Core principles, type hints, imports
├── backend.md       # Laravel patterns: models, controllers, Actions, DTOs
├── frontend.md      # Inertia + React + Tailwind patterns
└── testing.md       # Pest testing patterns

When I deliver a white-label project, these standards are part of the delivery. The agency doesn't just get code — they get the guide for maintaining it. Their developers can read the standards, understand the conventions, and contribute code that matches what's already there.

Specific Standards That Prevent Problems

Over the years, I've learned which standards prevent the most common issues:

No business logic in controllers. Controllers receive requests, validate them, call an Action, and return a response. Nothing else. This makes refactoring trivial and testing straightforward.

final class BookingController extends Controller
{
    public function store(
        StoreBookingRequest $request,
        CreateBooking $createBooking,
    ): RedirectResponse {
        $createBooking->execute(
            BookingData::fromRequest($request),
        );

        return redirect()
            ->route('bookings.index')
            ->with('success', 'Booking created.');
    }
}

DTOs for Inertia props. Never pass raw Eloquent models to the frontend. Always use a DTO with explicitly defined properties. This prevents accidental data leakage and creates a clear contract between backend and frontend.

final readonly class BookingData
{
    public function __construct(
        public int $id,
        public string $reference,
        public string $status,
        public string $guestName,
        public CarbonImmutable $checkIn,
        public CarbonImmutable $checkOut,
        public int $totalPence,
    ) {}

    public static function fromModel(Booking $booking): self
    {
        return new self(
            id: $booking->id,
            reference: $booking->reference,
            status: $booking->status->value,
            guestName: $booking->guest_name,
            checkIn: $booking->check_in,
            checkOut: $booking->check_out,
            totalPence: $booking->total_pence,
        );
    }
}

Migrations that are complete. Every column has a type. Every foreign key has a constraint. Every index has a reason. No nullable() unless the field is genuinely optional. Comments on non-obvious columns.

Enum classes for status fields. Never store statuses as plain strings. Always use a backed enum. This prevents typos, enables IDE autocomplete, and makes it impossible to set an invalid status.

The Quality Guarantee

When I deliver a white-label project, the agency gets:

  1. Formatted code — Pint-compliant, zero formatting issues
  2. Typed code — return types, parameter types, property types everywhere
  3. Tested code — Pest tests covering happy paths, failures, and edge cases
  4. Documented code — CLAUDE.md, standards directory, clear PR descriptions
  5. Consistent code — every file follows the same conventions, no surprises
  6. Maintainable code — Actions, DTOs, Form Requests — patterns that any Laravel developer can follow

That's not just a quality standard. It's a promise. When an agency puts their name on code I've written, it has to reflect well on them. Not adequately. Well.

The bar for white-label is higher than the bar for your own projects, and it should be. Someone else's reputation is on the line.


I've been embedded in agency teams for over a decade. If you're looking for senior development capacity you can trust, let's talk.

Get the Friday email

What I shipped this week, what I learned, one useful thing.

No spam. Unsubscribe anytime. Privacy policy.