Rust icon

Rust

RustRust

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).

.env
IMEJIS_API_KEY=your_api_key
IMEJIS_DESIGN_ID=your_design_id

1 a handler that renders the image1. 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.

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 it2. 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.

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.