---
title: "Symfony"
description: "Generate dynamic OG images and on-demand graphics in Symfony with the Imejis.io API. A secure controller proxy that keeps your API key server-side and returns a cached image."
url: "https://www.imejis.io/apis/symfony"
published: "2026-07-27"
---

# Symfony

## Symfony

Generate a unique OG image for every page, or any on-demand graphic, from a single Imejis template. The pattern below uses a Symfony controller action as a thin proxy: your API key stays on the server, and the browser (and social crawlers) only ever see your own URL.

Set two environment variables first: `IMEJIS_API_KEY` (from your dashboard) and `IMEJIS_DESIGN_ID` (any design's ID).

```bash title=".env"
IMEJIS_API_KEY=your_api_key
IMEJIS_DESIGN_ID=your_design_id
```

### 1. A controller that renders the image

This action takes a `title` query param, POSTs it to Imejis as a `component.property` override with the `dma-api-key` header, and returns the finished image bytes. Because the key is sent server-side, it never reaches the client.

```php showLineNumbers title="src/Controller/OgController.php"
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class OgController extends AbstractController
{
    #[Route('/og', name: 'og', methods: ['GET'])]
    public function index(Request $request, HttpClientInterface $client): Response
    {
        $title = $request->query->get('title', 'Untitled');
        $designId = $_ENV['IMEJIS_DESIGN_ID'];
        $apiKey = $_ENV['IMEJIS_API_KEY'];

        $upstream = $client->request(
            'POST',
            "https://render.imejis.io/v1/{$designId}",
            [
                'headers' => ['dma-api-key' => $apiKey],
                'json' => ['headline.text' => $title],
            ]
        );

        $response = new Response($upstream->getContent());
        $response->headers->set('Content-Type', 'image/jpeg');
        // Renders are deterministic, so cache aggressively at the edge/CDN.
        $response->headers->set(
            'Cache-Control',
            'public, immutable, no-transform, max-age=31536000'
        );

        return $response;
    }
}
```

### 2. Point your template at it

Reference the route from your Twig template or meta tags, passing each page's real data. Social platforms fetch `/og?title=…` from your own domain, no key exposed.

```twig showLineNumbers title="templates/post/show.html.twig"
{% set og_image = 'https://your-site.com/og?title=' ~ post.title|url_encode %}
<meta property="og:image" content="{{ og_image }}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="{{ og_image }}" />
```

**Override any field.** Each dynamic component exposes keys like `headline.text`, `title.color`, or `image.image`. Send only the ones you want to change. A design's overridable fields are listed in its Share / API panel.
