halilcosdu/laravel-replicate
A Laravel package for duplicating Eloquent models and their relationships via a simple API. Define which relations and columns to replicate, then create deep copies for templates, drafts, or versioned records without manual clone logic.
Install and configure
composer require halilcosdu/laravel-replicate
php artisan vendor:publish --tag="laravel-replicate-config"
Add your token to .env (grab it from replicate.com/account):
REPLICATE_API_TOKEN=r8_xxxxxxxxxxxxxxxxxxxx
v2.0.0 note: this is a major version bump published with only a release-title changelog. Treat it as potentially breaking. Before upgrading, diff
v1.3.0...v2.0.0and check for: minimum PHP/Laravel bumps, renamed namespaces or facade methods, changed config keys, and any HTTP-transport swap (Saloon/Laravel HTTP client). Re-publish the config file rather than copying your old one forward.
Where to look first
config/replicate.php — token, base URL, and any driver/timeout settings exposed in your installed version.vendor/halilcosdu/laravel-replicate/src/ — the facade root and its resource classes (Predictions, Models, Trainings, Deployments, Files). Reading the resource methods is faster than docs for argument shapes.First use case — run a prediction and get the result
use HalilCosdu\Replicate\Facades\Replicate;
$prediction = Replicate::predictions()->create('stability-ai/sdxl', [
'prompt' => 'a corgi wearing a tiny astronaut helmet, studio lighting',
]);
// $prediction['id'], $prediction['status'] === 'starting'
Predictions are asynchronous. For your first integration, poll until terminal to confirm the wiring, then move to webhooks (see below).
$prediction = Replicate::predictions()->get($prediction['id']);
while (in_array($prediction['status'], ['starting', 'processing'])) {
sleep(1);
$prediction = Replicate::predictions()->get($prediction['id']);
}
Wrap the facade in one application service. Nothing else in your codebase should import Replicate. This gives you a single seam to swap models, add logging, and fake in tests.
final class ImageGenerator
{
public function __construct(private PredictionRepository $predictions) {}
public function generate(string $prompt, int $userId): PredictionRecord
{
$response = Replicate::predictions()->create('stability-ai/sdxl', [
'prompt' => $prompt,
'webhook' => route('webhooks.replicate'),
'webhook_events_filter' => ['completed'],
]);
return $this->predictions->create($userId, $response['id'], $response['status']);
}
}
Always persist the prediction. A local predictions table (id, replicate_id, model, version, status, input, output, error, user_id, timestamps) is the single most valuable thing you can add. It gives you idempotency, retry targets, cost attribution, and a paper trail for support.
Prefer webhooks over polling in production. Pass a webhook URL on create, expose a route in routes/api.php (excluded from CSRF), and verify any signature/secret the package or Replicate provides before trusting the payload. Look up your record by replicate_id, not by anything in the body you can't validate.
Route::post('/webhooks/replicate', ReplicateWebhookController::class)
->middleware('throttle:120,1');
Pin model versions, don't ride latest. Resolve the version once and store it alongside the job, so a model owner publishing a new version doesn't silently change your output shape or price.
$version = Cache::remember("replicate:version:sdxl", now()->addDay(), function () {
$model = Replicate::models()->get('stability-ai/sdxl');
return $model['latest_version']['id'];
});
Dispatch long-running work to the queue. A queued job that creates the prediction and returns immediately keeps requests fast; a separate polling job (or the webhook) completes it. Set generous $timeout/$tries on the worker and prefer Horizon for visibility.
Handle files explicitly. Many image/video models expect URLs or uploaded files. Upload through the package's files resource (or your own S3 signed URL) rather than inlining base64 into JSON — payloads get large and are easy to blow up in logs.
Testing. Never hit Replicate in tests. Either point the config at a fake base URL behind Http::fake(), or mock the package's transport (Saloon's MockClient if your installed version uses it), or — cleanest — bind a fake ImageGenerator/PredictionClient in the container and assert your own contract.
starting (cold boot — can be tens of seconds for large models), processing, succeeded, failed, canceled. Handle all of them; treat unknown values as non-terminal and log them.create() blindly. A timeout on the HTTP call may still have started a billable prediction. Generate a client-side idempotency key (or check for an existing in-flight record) before re-submitting, so a retried job doesn't double-charge.predictions()->get() can be slow under load. Set explicit HTTP timeouts in config and a bounded polling budget in your job — otherwise a stuck prediction pins a worker forever.auth middleware. Locally, use a tunnel; in staging, don't point at production. If the payload is delivered but your handler throws, Replicate may redeliver — make the handler idempotent on replicate_id.php artisan config:cache traps. Reading config('replicate.token') at runtime is fine; reading env() outside config files is not. If auth mysteriously stops working after a deploy, this is usually why.^1.3 in composer.json until you've read the diff; run the full test suite against v2 in a branch; re-publish and re-diff config/replicate.php; grep your app for facade method calls that may have been renamed; confirm the package's PHP/Laravel minimums against your matrix before merging. If the release notes stay thin, check the PR #28 diff and the tagged commit directly rather than guessing from the version number.How can I help you explore Laravel packages today?