saloonphp/saloon
Saloon is a Laravel/PHP-friendly HTTP client framework for building typed API connectors and requests. It supports middleware, authentication, retries, caching, testing/mocking, and async features, helping you create clean, reusable integrations without boilerplate.
Installation
composer require saloonphp/saloon saloonphp/saloon-laravel
Publish the Laravel config (if needed):
php artisan vendor:publish --provider="Saloon\Laravel\SaloonServiceProvider"
First Use Case: Fetching Data from an API
Define a Connector (e.g., app/Connectors/StripeConnector.php):
use Saloon\Connector;
use Saloon\Traits\PlaysConnectors;
class StripeConnector extends Connector
{
use PlaysConnectors;
protected string $baseUrl = 'https://api.stripe.com/v1';
protected string $defaultAuth = 'api_key';
protected string $defaultAuthKey = 'sk_test_...';
}
Define a Request (e.g., app/Requests/GetCustomerRequest.php):
use Saloon\Request;
use Saloon\Traits\PlaysRequests;
class GetCustomerRequest extends Request
{
use PlaysRequests;
protected string $endpoint = 'customers/{customer_id}';
protected string $method = 'GET';
protected ?string $customer_id;
}
Send the request in a Laravel controller or service:
$connector = resolve(StripeConnector::class);
$response = $connector->send(new GetCustomerRequest(['customer_id' => 'cus_123']));
$customer = $response->json();
Where to Look First
Connector, Request, ResponseSaloonServiceProvider, config/saloon.phpexamples folder in the repo.withBaseUrl() or override in config:
$connector->withBaseUrl('https://staging-api.stripe.com/v1');
$connector->withAuth('oauth', ['token' => '...']);
$connector->withMiddleware(new \Saloon\Http\Middleware\JsonResponse());
{id}) and bind them:
$request = new GetCustomerRequest(['customer_id' => 'cus_123']);
$query property or use withQuery():
protected array $query = ['limit' => 10];
protected array $headers = ['Accept' => 'application/json'];
resolve() to cast responses to objects:
$response->resolve(CustomerDto::class);
tests/Fixtures/:
// tests/Fixtures/GetCustomerRequest.json
{"id": "cus_123", "name": "John Doe"}
$connector->assertSent(GetCustomerRequest::class, function ($request) {
return $request->customer_id === 'cus_123';
});
MockClient for testing:
$mock = new MockClient();
$mock->shouldReceive('send')
->once()
->andReturn(new Response($fixture));
AppServiceProvider:
$this->app->bind(StripeConnector::class, function ($app) {
return new StripeConnector();
});
$response = $connector->send($request, ['cache' => true, 'cache_key' => 'stripe_customer_123']);
$connector->withMiddleware(new \Saloon\Http\Middleware\EventMiddleware([
'Saloon\Events\RequestSent',
'Saloon\Events\ResponseReceived',
]));
Saloon\Pagination\CursorPagination or OffsetPagination.$connector->withMiddleware(new \Saloon\Http\Middleware\RetryMiddleware());
$connector->withPlugin(new \Saloon\Plugins\RateLimitPlugin());
Authentication Issues:
defaultAuthKey or defaultAuth in the connector.protected string $defaultAuth = 'api_key'; and protected string $defaultAuthKey = '...'; are set.$connector->debug() to inspect the request payload.Endpoint URL Construction:
users/{user_id}) may cause issues with URL encoding.$endpoint.Response Handling:
json() will always work, even for non-JSON responses.if ($response->isJson()) {
$data = $response->json();
} else {
$data = $response->body();
}
response() to get the raw Psr\Http\Message\ResponseInterface for custom handling.Mocking Quirks:
GetCustomerRequest.json).context in fixtures for dynamic data:
{"id": "{{ customer_id }}", "name": "John Doe"}
Middleware Order:
$connector->withMiddleware(new MiddlewareA());
$connector->withMiddleware(new MiddlewareB()); // Runs before MiddlewareA
Deprecated Methods:
sendAndRetry() (v3.6.4+).send() with retry middleware instead.CVE-2026-33942 (v4.0.0):
AccessTokenAuthenticator.Absolute URL Overrides (v4.0.0):
base_url_overrides in config:
'base_url_overrides' => true,
$connector->debug(); // Logs request/response details
$request = $connector->getRequest($requestClass);
dd($request->toPsr());
dd() or dump():
$response->dump(); // Pretty-prints response body
$response->headers->all(); // Returns all headers as an array
use Saloon\Authenticators\Authenticator;
class CustomAuthenticator extends Authenticator
{
public function authenticate
How can I help you explore Laravel packages today?