OgImageGen
Generate beautiful Open Graph images for social sharing using SVG templates and resvg (Rust NIF).
Built by the Shiko team.
Examples
| With logo (blue) | Without logo (teal) |
|---|---|
| With logo (coral) | Without logo (dark) |
|---|---|
| Warning theme with logo |
|---|
Features
- SVG → PNG rendering via resvg (no browser, no headless Chrome, no wkhtmltoimage)
- 5 built-in themes with gradient backgrounds (blue, teal, dark, coral, warning)
- Custom themes — register your own at runtime
- Content-based hashing — same content = same hash, perfect for caching
- Batch generation with diff support (only regenerates changed pages)
- Zero external dependencies — no system binaries needed beyond Erlang/Elixir
Installation
Add og_image_gen to your dependencies in mix.exs:
def deps do
[
{:og_image_gen, "~> 0.1.0"}
]
endQuick start
# Generate a PNG binary
{:ok, png} = OgImageGen.render("My Page Title", subtitle: "A great description", theme: :teal)
File.write!("og.png", png)Configuration
config :og_image_gen,
default_theme: :blue,
font_dirs: ["/path/to/fonts"], # e.g. Inter font directory
logo_svg: "<path d=\"...\"/>" # SVG inner content (no <svg> wrapper)Themes
| Theme | Style |
|---|---|
:blue | Deep blue gradient |
:teal | Green-teal gradient |
:dark | Dark gray gradient |
:coral | Red-coral gradient |
:warning | Yellow gradient, dark text |
Custom themes
OgImageGen.register_theme(:brand, %OgImageGen.Theme{
bg1: "#1a0533",
bg2: "#2d0a4e",
bg3: "#4a1280",
text: "#ffffff",
sub_opacity: "0.8"
})
{:ok, png} = OgImageGen.render("My Brand Page", theme: :brand)Batch generation
Process multiple pages with content-based diffing — only new or changed content gets re-rendered:
pages = [
%{"path" => "home", "title" => "My App", "subtitle" => "Welcome", "theme" => "teal"},
%{"path" => "about", "title" => "About Us", "theme" => "dark"}
]
OgImageGen.process_batch(pages, fn path, png_binary ->
# Upload to S3, R2, local disk, wherever
File.write!("priv/static/og/#{path}.png", png_binary)
:ok
end)
#=> %{generated: 2, skipped: 0, failed: 0}Content hashing
Each title/subtitle/theme combination produces a deterministic 8-character hash, useful for cache-busting filenames:
OgImageGen.content_hash("My Title", "subtitle", :blue)
#=> "a1b2c3d4"Path humanization
Convert URL paths to readable titles as a fallback:
OgImageGen.humanize_path("productos/gatos")
#=> "Productos — Gatos"Integration with Phoenix
Use it in a controller to serve OG images on-demand:
def og_image(conn, %{"path" => path} = params) do
title = params["title"] || OgImageGen.humanize_path(path)
theme = String.to_existing_atom(params["theme"] || "blue")
case OgImageGen.render(title, subtitle: params["subtitle"] || "", theme: theme) do
{:ok, png} ->
conn
|> put_resp_header("content-type", "image/png")
|> put_resp_header("cache-control", "public, max-age=604800")
|> send_resp(200, png)
{:error, _} ->
send_resp(conn, 500, "Failed to generate image")
end
endLicense
MIT — see LICENSE.