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

# Rust

## Rust

Generate a unique OG image for every page, or any on-demand graphic, from a single Imejis template. The pattern below uses an Axum handler 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 handler that renders the image

This handler 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.

```rust showLineNumbers title="src/main.rs"
use axum::{
    extract::Query,
    http::{header, StatusCode},
    response::IntoResponse,
    routing::get,
    Router,
};
use serde::Deserialize;
use serde_json::json;
use std::collections::HashMap;

#[derive(Deserialize)]
struct OgParams {
    title: Option<String>,
}

async fn og(Query(params): Query<OgParams>) -> impl IntoResponse {
    let title = params.title.unwrap_or_else(|| "Untitled".to_string());
    let design_id = std::env::var("IMEJIS_DESIGN_ID").unwrap();
    let api_key = std::env::var("IMEJIS_API_KEY").unwrap();

    let mut body = HashMap::new();
    body.insert("headline.text", title);

    let res = reqwest::Client::new()
        .post(format!("https://render.imejis.io/v1/{design_id}"))
        .header("dma-api-key", api_key)
        .json(&json!(body))
        .send()
        .await
        .unwrap();

    let bytes = res.bytes().await.unwrap();

    (
        StatusCode::OK,
        [
            (header::CONTENT_TYPE, "image/jpeg"),
            // Renders are deterministic, so cache aggressively at the edge/CDN.
            (
                header::CACHE_CONTROL,
                "public, immutable, no-transform, max-age=31536000",
            ),
        ],
        bytes,
    )
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/og", get(og));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

### 2. Point your markup at it

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

```rust showLineNumbers title="building the meta tag URL"
let og_image = format!(
    "https://your-site.com/og?title={}",
    urlencoding::encode(&post.title)
);
// <meta property="og:image" content="{og_image}" />
// <meta name="twitter:card" content="summary_large_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.
