# Generate dynamic images in PHP with an API

> Design one template, then render banners, product images, and certificates from PHP. Works with plain cURL or the bannerify/bannerify SDK, no GD or Imagick.

Canonical: https://bannerify.co/blog/generate-images-php
Last updated: 2026-10-08

By Duc An, 2026-10-08.

Most PHP image code is layout code. With GD or Imagick, every design decision turns into a function call:

```php
$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 to an [image generation API](https://bannerify.co/product/image-generation-api).

## How the API version works

1. Design the banner once in the [editor](https://bannerify.co/product/modern-editor), or duplicate a template from the [gallery](https://bannerify.co/gallery).
2. Name the layers you want to change later, for example `headline`, `price`, `photo`.
3. 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
<?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:

```bash
composer require bannerify/bannerify
```

The SDK returns either a `result` or an `error`, never both, so the failure path is impossible to forget:

```php
<?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](https://bannerify.co/docs/sdk/php/overview).

## 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:

```php
'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:

- `text` replaces the content of a text layer
- `color` overrides the layer color, for example `#FF0000`
- `src` swaps an image layer, using any publicly reachable HTTPS URL
- `qrcode` and `barcode` encode a link, SKU, or tracking number
- `rows` and `columns` fill [charts](https://bannerify.co/product/data-report-api) and [tables](https://bannerify.co/product/table-generation-api)
- `visible` hides a layer for one render only
- `star` sets 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:

```php
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:

```json
{ "url": "https://cdn.bannerify.co/..." }
```

That is the convenient path for [Open Graph images](https://bannerify.co/blog/og-image), email headers, and anywhere a URL is easier to hand around than a file.

## Formats and sizes

- `format` accepts `png` (default), `jpeg`, or `webp`.
- Need a PDF? The same template renders to a PDF with `createPdf`. The [PDF generation API](https://bannerify.co/product/pdf-generation-api) page has the details, and the [invoice PDF guide](https://bannerify.co/blog/dynamic-invoice-pdf-php) has the PHP code.
- 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 `src` for 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. Automate starts at $29 a month for 3,000 generations and unlimited templates. Details on [pricing](https://bannerify.co/pricing).

If you are generating images from PHP today, [start with the free plan](https://app.bannerify.co) 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.

## Related

- [Open Graph tags: og:type, og:title, og:url, and the rest](https://bannerify.co/blog/open-graph-tags)
- [What is an AVIF file? How to open and convert it](https://bannerify.co/blog/avif-file)
- [Canva Bulk Create: how to use it, limits, and fixes](https://bannerify.co/blog/canva-bulk-create)
- [All posts](https://bannerify.co/blog)
