Skip to main content

guides/getting_started.md

# Getting started

TL;DR: define a content module mapping keys to titles and descriptions, point OGMate at it, mount a plug to serve `/images/og/<key>.png`, drop a meta tag in your layout. Images are built at compile time and served straight from memory.

OG images are those preview cards you see when sharing links on social media. They don't help SEO but they make your links look intentional. OGMate handles the build pipeline, you provide the content.

This walks through the full setup, framework-agnostic. Works inside a Phoenix endpoint, but Phoenix isn't required.

## 1. Add the dependency

```elixir
def deps do
  [
    {:og_mate, "~> 0.1.0"}
  ]
end
```

## 2. Content module

Map URL slugs to `{title, description}`. Return `:error` for anything unknown, those fall through to the default image at runtime.

```elixir
defmodule MyApp.OGContent do
  @static_content %{
    "home" => {"MyApp", "Welcome to MyApp."},
    "about" => {"About", "What MyApp does."}
  }

  def content_for(key) when is_map_key(@static_content, key),
    do: Map.fetch!(@static_content, key)

  def content_for("posts/" <> id) do
    case MyApp.Blog.find_by_id(id) do
      nil -> :error
      post -> {post.title, post.description}
    end
  end

  def content_for(_), do: :error
end
```

Static map for fixed pages, pattern-matched clauses for dynamic content. The catch-all `:error` clause at the bottom handles anything the rest didn't.

## 3. The OGMate module

```elixir
defmodule MyApp.OGImage do
  use OGMate,
    all_keys:
      ["home", "about"] ++ Enum.map(MyApp.Blog.all_posts(), &"posts/#{&1.id}"),
    content_for: MyApp.OGContent,
    theme: [
      background: "#0a0a0a",
      foreground: "#ffffff",
      font: "Inter",
      secondary: "#a3a3a3",
      logo: "priv/static/images/logo.png",
      site_name: "myapp.com"
    ],
    default: {"MyApp", "Welcome to MyApp."},
    dev_mode: Application.compile_env(:my_app, :og_image_dev_mode, false)

  def path_for(key) when is_binary(key), do: "/images/og/#{key}.png"
end
```

`all_keys` is computed at compile time. `MyApp.Blog.all_posts()` is a normal call to an already-compiled module, the result gets inlined as a literal list. New posts means a recompile, but Phoenix code reloading handles that for you in dev.

`path_for/1` keeps URL building in one place so the plug and your templates stay in sync.

## 4. The plug

A small plug intercepts `/images/og/<key>.png` and serves the bytes.

```elixir
defmodule MyApp.Plugs.OGImage do
  @behaviour Plug
  import Plug.Conn

  @impl Plug
  def init(opts), do: opts

  @impl Plug
  def call(%Plug.Conn{request_path: "/images/og/" <> rest} = conn, _) do
    key = String.replace_suffix(rest, ".png", "")
    {:ok, bytes} = MyApp.OGImage.image_for(key)

    conn
    |> put_resp_content_type("image/png")
    |> put_resp_header("cache-control", "public, max-age=31536000")
    |> send_resp(200, bytes)
    |> halt()
  end

  def call(conn, _), do: conn
end
```

That `cache-control` is a year, which is fine because the URLs are content-keyed. If the content changes, the key changes.

## 5. Mount the plug

In Phoenix, drop it into the endpoint:

```elixir
# lib/my_app_web/endpoint.ex
plug MyApp.Plugs.OGImage
plug Plug.Static, ...
```

Before `Plug.Static` so OG requests are caught first. Outside Phoenix, add it to your `Plug.Router`.

## 6. Reference in your HTML

```html
<meta property="og:image" content={MyApp.OGImage.path_for(@og_key)} />
```

Set `@og_key` per route in your controller. `"home"` for the homepage, `"posts/#{post.id}"` for a post page.

## 7. Dev mode

Building everything at compile time is great for production. But rendering all your OG images on every save while you're iterating gets old fast. Set `dev_mode: true` and OGMate skips that at compile time, rendering lazily on each `image_for/1` call instead. New content shows up immediately, no recompile.

```elixir
# config/dev.exs
config :my_app, og_image_dev_mode: true
```

```elixir
# config/prod.exs
config :my_app, og_image_dev_mode: false
```

The `Application.compile_env(:my_app, :og_image_dev_mode, false)` call in step 3 reads this at compile time.

## See also

- `OGMate`: main module reference
- `OGMate.Theme`: theme field reference
- `OGMate.Renderer`: default renderer (1200×630 PNG layout)