Reader Stacks

How to Create a Custom Facade in Laravel

A facade is a static-looking proxy to an object resolved from the service container — creating your own is three small pieces: the underlying class, the facade class, and (optionally) a binding.

A Laravel facade — like Cache::get() or Auth::user() — looks like a static method call, but it's actually a proxy that resolves an object from the service container and forwards the call to it. Creating a custom facade for your own class follows the same three-piece pattern.

The underlying class

namespace App\Services;

class OrderCalculator
{
    public function calculateTotal(array $items): float
    {
        return collect($items)->sum(fn ($item) => $item['price'] * $item['quantity']);
    }
}

The facade class

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class OrderCalculator extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return \App\Services\OrderCalculator::class;
    }
}

getFacadeAccessor() is the one method every facade must implement — it returns the container binding key (here, just the class name itself) that the facade resolves and forwards calls to.

Using the facade

use App\Facades\OrderCalculator;

$total = OrderCalculator::calculateTotal($items);

This works even without an explicit service container binding, since Laravel's container can resolve App\Services\OrderCalculator::class automatically via reflection (assuming its constructor has no unresolvable dependencies) — a binding becomes necessary once the class needs specific configuration or should be bound as a singleton.

Binding it explicitly, for a singleton or configured instance

// in a service provider's register() method
public function register()
{
    $this->app->singleton(\App\Services\OrderCalculator::class, function ($app) {
        return new \App\Services\OrderCalculator();
    });
}

Registering an alias (optional, for a shorter name)

// config/app.php
'aliases' => Facade::defaultAliases()->merge([
    'OrderCalculator' => App\Facades\OrderCalculator::class,
])->toArray(),

This lets you use the bare OrderCalculator::calculateTotal() without the full use App\Facades\OrderCalculator; import in every file — a convenience, not a requirement, since importing the facade class directly (as in the earlier example) works identically without touching this config.

Why facades are convenient, and their real trade-off

Facades give a clean, memorable, static-looking syntax without sacrificing testability (Laravel facades support Facade::shouldReceive() mocking in tests, unlike a genuinely static method call) — but overusing custom facades for every service in an application can obscure the actual dependency graph, since a facade call doesn't show up as a constructor dependency the way normal dependency injection does. Reaching for a facade is most justified for something used pervasively across many unrelated parts of the app, similar to how Laravel's own built-in facades (Cache, Auth, Log) are used.