benbjurstrom/replicate-php
A Laravel-friendly PHP client for Replicate that makes it easy to run machine learning models, create and manage predictions, and handle webhooks through a fluent, type-safe API.
1. Install and configure
composer require benbjurstrom/replicate-php
php artisan vendor:publish --tag="replicate-config" # verify tag in the service provider
Add the token to .env:
REPLICATE_API_TOKEN=r8_xxxxxxxxxxxxxxxxxxxxxxxx
The package uses Laravel auto-discovery, so the service provider (and possibly a Replicate facade) is registered automatically. The token is exposed through config('replicate.api_token') (confirm the exact key in config/replicate.php).
2. Resolve the client
Prefer constructor injection over the facade so the client is swappable in tests:
use BenBjurstrom\Replicate\Replicate;
class GenerateImage
{
public function __construct(private Replicate $replicate) {}
public function __invoke(string $prompt): string
{
$prediction = $this->replicate->predictions()->create(
version: config('services.replicate.sdxl_version'),
input: ['prompt' => $prompt],
);
return $prediction->id;
}
}
3. Where to look first
src/Replicate.php — the public entry point; lists the available resources (predictions(), models(), …).src/Requests/ — one class per endpoint; this is where the real request/response shape lives.src/Data/ (or equivalent DTO directory) — Prediction, Model, Version objects and their properties.config/replicate.php — token and any webhook settings.tests/ — the fastest way to see exact method signatures and fixture payloads.4. First real use case: text-to-image, then read the output
$prediction = $replicate->predictions()->create(
version: '5c7d5dc6...', // pin a version ID, not a model name
input: ['prompt' => 'a llama in the style of Monet'],
);
// Async — poll until done (fine for a CLI/spike, not for a web request)
do {
sleep(2);
$prediction = $replicate->predictions()->get($prediction->id);
} while (in_array($prediction->status, ['starting', 'processing']));
$url = $prediction->output[0] ?? null;
Async lifecycle: create → persist → webhook → update. The dominant production pattern with Replicate is: create the prediction, store its id and your own domain record, return immediately, and let a webhook complete the loop. Do not hold a PHP-FPM request open while a diffusion model runs.
// Controller / job
$prediction = $replicate->predictions()->create(
version: $versionId,
input: $input,
webhook: route('replicate.webhook'),
webhookEventsFilter: ['completed'], // verify parameter name in src/Requests
);
Generation::create([
'user_id' => $user->id,
'prediction_id' => $prediction->id,
'status' => $prediction->status,
]);
Webhook handler: return fast, dispatch a job.
Route::post('/webhooks/replicate', ReplicateWebhookController::class)
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
Persist the raw payload, dedupe on payload['id'] (webhooks can be retried), then dispatch. Respond 200 even for unknown IDs so Replicate doesn't retry forever.
Polling fallback. Keep a scheduled command that sweeps records stuck in starting/processing older than N minutes and calls predictions()->get($id). This covers dropped webhooks and local development.
Model + version resolution. For convenience you can resolve the latest version:
$model = $replicate->models()->get('stability-ai/sdxl');
$version = $model->latest_version->id;
But treat latest_version as a discovery tool, not production config — resolve once, then store the concrete version ID in config or the database.
Wrap each model in a small class. Input schemas differ wildly per model (prompt, image, mask, num_inference_steps, …). A SdxlGenerator, WhisperTranscriber, etc., that owns the version ID and the input array makes call sites clean and gives you one place to update when a model changes.
Outputs are URLs, not files. Most models return one or more HTTPS URLs (often S3 with an expiry). Copy them to your own disk immediately — Storage::disk('s3')->put(...) or a queued download — rather than storing the Replicate URL in the database.
Inputs must be reachable by Replicate. For image-to-image, img2img masks, or training, pass a publicly accessible URL or a data URI. Storage::url() on a private disk will fail; use a temporary signed URL.
Testing. Avoid hitting the real API. If the package is built on Laravel's HTTP client, Http::fake([...]) with a captured prediction payload works well; if it uses Saloon, use the corresponding fake/mock connector. Assert on your own persisted status and ID, not on generated content.
create() returns while status is starting and output is null. Any code that dereferences output immediately will break intermittently.output shape is model-specific. Sometimes a string, sometimes an array of URLs, sometimes an object. Normalize defensively (is_array($output) ? $output : [$output]) and validate before use.latest_version.predictions()->create() generally takes a version hash, not owner/model. Use models()->get() first if you only have a model slug.--timeout above it. Prefer webhooks so no worker is blocked.php artisan config:cache freezes the token value; rotate it and re-cache. Never read env() outside config files.id, status).prediction_id and make your handler safe to run twice.401 (bad token), 422 (invalid input for the model), and 429 (rate limit). Catch the package's request exception at the service boundary and translate to a domain error rather than leaking HTTP details to controllers.src/Requests/ before assuming; for anything absent, call the Replicate REST API directly with Http::withToken(...) and keep the DTO conversion in your own code.src/Requests/ and matching its DTO. Fork or wrap rather than editing vendor/.How can I help you explore Laravel packages today?