# OgImageGen
Generate beautiful Open Graph images for social sharing using SVG templates and
[resvg](https://github.com/nicolo-ribaudo/resvg-elixir) (Rust NIF).
Built by the [Shiko](https://shiko.vet) 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`:
```elixir
def deps do
[
{:og_image_gen, "~> 0.1.0"}
]
end
```
## Quick start
```elixir
# Generate a PNG binary
{:ok, png} = OgImageGen.render("My Page Title", subtitle: "A great description", theme: :teal)
File.write!("og.png", png)
```
## Configuration
```elixir
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
```elixir
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:
```elixir
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:
```elixir
OgImageGen.content_hash("My Title", "subtitle", :blue)
#=> "a1b2c3d4"
```
## Path humanization
Convert URL paths to readable titles as a fallback:
```elixir
OgImageGen.humanize_path("productos/gatos")
#=> "Productos — Gatos"
```
## Integration with Phoenix
Use it in a controller to serve OG images on-demand:
```elixir
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
end
```
## License
MIT — see [LICENSE](LICENSE).