Most PHP image code is layout code. With GD or Imagick, every design decision turns into a function call:
$image = imagecreatetruecolor(1200, 630);
$blue = imagecolorallocate($image, 37, 99, 235);
imagefilledrectangle($image, 0, 0, 1200, 630, $blue);
imagettftext($image, 48, 0, 80, 300, $white, 'Inter-Bold.ttf', 'Summer sale: 30% off');
It works, until someone wants the headline in two lines, a shadow behind the price, or a QR code in the corner. Now the design lives inside your PHP, and every tweak is a code change and a deploy.
There is a third option between “draw it yourself” and “run a headless browser fleet”: keep the layout in a template, and let PHP send only the data.
How the API version works
- Design the banner once in the editor, or duplicate a template from the gallery.
- Name the layers you want to change later, for example
headline,price,photo. - Send the template ID plus your values to the API. The response is the finished image.
Fonts, spacing, gradients, shadows, tables, and QR codes stay in the template. A designer can change all of it without touching your PHP, and your PHP keeps doing what it is good at: moving data around.
What you need
- An API key. Create one in Dashboard → API Keys and keep it on the server. Treat it like a database password.
- A template ID. Open the template you designed and copy its ID. It looks like
tpl_kd93mz. - Layer names. The name you give a layer in the editor is the name you use in the request, matched exactly.
Option 1: plain cURL
No dependencies, works on any PHP host, including the ones where installing extensions is a fight.
<?php
$payload = [
'apiKey' => getenv('BANNERIFY_API_KEY'),
'templateId' => 'tpl_kd93mz',
'format' => 'png',
'modifications' => [
['name' => 'headline', 'text' => 'Summer sale: 30% off'],
['name' => 'price', 'text' => '$149.00'],
['name' => 'photo', 'src' => 'https://example.com/sneaker.jpg'],
],
];
$ch = curl_init('https://api.bannerify.co/v1/templates/createImage');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 30,
]);
$image = curl_exec($ch);
if ($image === false) {
throw new RuntimeException('Transport error: ' . curl_error($ch));
}
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("Bannerify returned {$status}: " . substr($image, 0, 200));
}
file_put_contents(__DIR__ . '/banner.png', $image);
Two details that trip people up:
- The response body is the image itself, not JSON with base64 inside. Write it straight to a file or stream it to the browser.
- Errors are JSON with a 4xx status. That is why the code above checks the status code before saving anything.
Option 2: the official PHP SDK
If you would rather not hand-roll requests, install the SDK:
composer require bannerify/bannerify
The SDK returns either a result or an error, never both, so the failure path is impossible to forget:
<?php
use Bannerify\Bannerify\BannerifyClient;
$client = new BannerifyClient(getenv('BANNERIFY_API_KEY'));
$result = $client->createImage('tpl_kd93mz', [
'modifications' => [
['name' => 'headline', 'text' => $product['name']],
['name' => 'price', 'text' => '$' . number_format($product['price'], 2)],
['name' => 'photo', 'src' => $product['image_url']],
],
]);
if (isset($result['error'])) {
error_log('Render failed: ' . $result['error']['message']);
return null;
}
$path = __DIR__ . "/banners/{$product['sku']}.png";
file_put_contents($path, $result['result']);
Full method list and options are in the PHP SDK docs.
Sending real data
You do not have to build the modification array by hand. The API also accepts an object shorthand where the key is the layer name:
'modifications' => [
'headline' => $product['name'],
'price' => '$' . number_format($product['price'], 2),
'photo' => ['src' => $product['image_url']],
'qr' => ['qrcode' => $product['checkout_url']],
],
Plain values become text. Nested arrays keep their own fields, which is what you need for images, QR codes, barcodes, charts, and tables.
Per-layer fields you can send:
textreplaces the content of a text layercoloroverrides the layer color, for example#FF0000srcswaps an image layer, using any publicly reachable HTTPS URLqrcodeandbarcodeencode a link, SKU, or tracking numberrowsandcolumnsfill charts and tablesvisiblehides a layer for one render onlystarsets a rating value
One field per layer, sent by name. If you need the same data on two layers, send it twice.
Batching and caching
Generating a thousand images is a loop, not a special case:
foreach ($products as $product) {
$payload = buildPayload($product);
$cacheKey = hash('xxh128', json_encode($payload));
$path = __DIR__ . "/cache/{$cacheKey}.png";
if (file_exists($path)) {
continue;
}
file_put_contents($path, renderBanner($payload));
}
Keep the loop bounded (a few concurrent requests at a time), and key the cache on the payload so a repeated request never pays twice. Rendering is synchronous, so you get the file in the same request instead of polling a job queue.
If you do not want to store files at all, use createStoredImage instead. It uploads the render and returns a hosted URL:
{ "url": "https://cdn.bannerify.co/..." }
That is the convenient path for Open Graph images, email headers, and anywhere a URL is easier to hand around than a file.
Formats and sizes
formatacceptspng(default),jpeg, orwebp.- The canvas size comes from the template, not from the request. Keep one template per size you need: 1200x630 for link previews, 1080x1080 for a feed, 1080x1920 for stories.
- Sending
srcfor a photo? The renderer fetches that URL, so it has to be public. A local file or a private bucket will fail.
Common mistakes
- Putting the API key in browser JavaScript or a mobile app. It belongs on the server.
- Rebuilding the image when nothing changed. Cache on the payload hash.
- Rendering thousands of images inside one web request. It works for tens, use a queue for thousands.
- Assuming the response is JSON. It is the file itself, unless you use the stored endpoint.
What it costs
The free plan includes 100 generations a month (10 a day), one template, and 1GB of stored images, which is plenty to wire this into a real app first. PRO is $29 a month for 10,000 generations and unlimited templates. Details on pricing.
If you are generating images from PHP today, start with the free plan and move one template off GD. The first render that comes back with the layout intact is usually the moment people stop writing positioning code.