placidapp/placid-php
Official PHP SDK for the Placid creative automation API. Generate images, videos, and PDFs from templates by merging dynamic data, then retrieve rendered assets through a simple client for Laravel or any PHP app.
composer require placidapp/placid-php guzzlehttp/guzzle
.env as PLACID_API_TOKEN and reference it from config/services.php ('placid' => ['token' => env('PLACID_API_TOKEN')]). Never hard-code the token in a service provider.title, subtitle, hero_image); those strings are your real API contract.$this->app->singleton(\Placid\Placid::class, fn () =>
new \Placid\Placid(config('services.placid.token'))
);
Check vendor/placidapp/placid-php/src/ for the exact class name and constructor signature — the package is thin and the README is the authoritative example.Post model with title, author, and cover_image_url maps almost 1:1 to a template with three layers. Render one card, hit the returned URL in a browser, and you've validated token, template UUID, and layer schema in a single loop.Wrap it in a domain service, not controllers. Create App\Services\PlacidImageService that exposes intent-level methods (renderPostCard(Post $post): string) and hides the layer-payload shape. Controllers and jobs should never assemble layers arrays directly — that payload is template-specific and will drift.
Treat rendering as async by default. Placid's image/video jobs are queued server-side; the create call returns a job/ID, and the final asset URL arrives via response polling or a webhook. Two viable Laravel patterns:
GenerateSocialCard job that calls the SDK with webhook_success_url pointing at a signed route. A PlacidWebhookController receives the payload, matches it to the record via a placid_job_id column, and stores the final URL.retry()/backoff. Fine for a few dozen images a day; painful at scale.Always re-host the output. Don't store Placid's URL as the canonical asset — download it once and push to your own disk:
Storage::disk('s3')->put($path, Http::get($url)->body());
$post->update(['og_image_path' => $path]);
This decouples your app from Placid's CDN and keeps URLs stable if you ever switch providers.
Cache aggressively and key on inputs. A rendered card is deterministic given template UUID + layer payload. Hash that payload (sha1(json_encode($layers))) and store it alongside the asset path; re-render only when the hash changes. This alone cuts most Placid spend.
Fake it in tests. The SDK is Guzzle-based, so Http::fake() won't intercept it — either inject a mock of your PlacidImageService into feature tests, or bind a fake client implementation of an interface you define around the SDK. Do not let real HTTP calls leak into the suite.
Queue + rate-limit. Rendering 500 cards on save will burn your Placid quota and time out. Use a dedicated queue with RateLimited middleware and a per-minute cap that matches your plan.
Http::withToken() wrapper gives you the same result with none of the dependency risk.Title vs title) doesn't error loudly — it silently renders the template's default. Validate your payload keys against a stored copy of the template schema, or add an integration test that asserts the rendered image differs when a layer changes.$response['image_url'] on the create call is the #1 source of "works in curl, fails in prod" bugs.URL::temporarySignedRoute(), and make the handler idempotent (upsert on placid_job_id) — retries are expected.GuzzleHttp\Exception\ClientException (4xx — bad token, unknown UUID, invalid layer) and ServerException/ConnectException (5xx/timeouts). Catch at the service boundary and translate into a domain exception; don't let Guzzle exceptions bubble into your queue's failure logs unlabelled.Placid::-style ergonomics in Laravel, write the facade and service provider yourself — it's five lines and keeps the dependency confined to one binding.Illuminate\Http\Client\PendingRequest. Given the package's age, budgeting for that swap is a realistic long-term plan, not a pessimism.How can I help you explore Laravel packages today?