# PhoenixKitOG
OpenGraph template + hierarchical assignment module for [PhoenixKit](https://github.com/BeamLabEU/phoenix_kit).
Build OG preview-card images (`og:image`) from a WYSIWYG canvas editor,
assign templates per-scope (e.g. per post, per group, or a module-wide
default), and render them to PNG on demand — instead of hand-designing a
static image for every page.
## Features
- **Template editor** — SVG canvas with text, image, rect, and stamp
elements; `{{slot}}` variables a consumer wires up, `[[global]]` variables
resolved automatically (site URL, host, name, page URL, locale)
- **Hierarchical assignments** — bind a template to a scope inside a
consumer's hierarchy (e.g. `post → group → default`); first match wins
- **Rendering** — SVG → PNG via the `:resvg` NIF (with CLI fallbacks),
disk-cached by content hash
- **Safe by default** — a kill switch and pass-through-on-error seam mean a
broken template or missing rasterizer never crashes a public page render
- **Zero-config discovery** — implements `PhoenixKit.Module`; the host app
finds it automatically, no wiring required
## Installation
Add `phoenix_kit_og` to your `mix.exs` deps, alongside `phoenix_kit` itself:
```elixir
def deps do
[
{:phoenix_kit, "~> 1.7"},
{:phoenix_kit_og, "~> 0.1"}
]
end
```
Then `mix deps.get`. PhoenixKit's module auto-discovery picks it up on next
boot — no config or router changes needed. Enable it from the PhoenixKit
admin dashboard (OpenGraph tab), which flips the `phoenix_kit_og_enabled`
setting.
Rendering prefers the precompiled `:resvg` NIF (already a dependency); if
that NIF can't load on your host, it falls back to the `resvg` CLI,
`rsvg-convert`, or ImageMagick — install one of those as a system package if
you're on an unusual target.
## Quick start
1. In the admin dashboard, open **OpenGraph → Templates** and design a
canvas (add text/image/rect elements, wire `{{slot}}` placeholders).
2. Open **OpenGraph → Assignments** and bind the template to a scope (e.g. a
specific post, a group, or the module-wide default).
3. A consumer module calls `PhoenixKitOG.refine_og/4` from its own OG-tag
rendering path; when a template resolves, the rendered PNG URL replaces
the consumer's `og:image`.
## Usage — wiring up a consumer module
Any module can plug in by implementing two optional callbacks on its own
`PhoenixKit.Module`:
```elixir
def og_variables do
[
%{name: "post_title", type: :text, label: "Post title", description: "…"},
%{name: "post_featured_image", type: :image, label: "Featured image"}
]
end
def og_resolve(var_name, context)
# context = %{module_key, resource, conn, language, page_url}
```
`og_variables/0` declares what a template author can bind a slot to;
`og_resolve/2` fetches the actual value at render time. Then, wherever the
consumer builds its OG tag map:
```elixir
og = %{title: post.title, description: post.excerpt, image: post.image_url, ...}
og = PhoenixKitOG.refine_og(og, conn, post, language)
```
When the module is disabled, no template resolves, or resolution raises,
`refine_og/4` returns `og` unchanged — the consumer's own OG image keeps
working either way.
`phoenix_kit_publishing` is the reference consumer; see its
`Web.Controller.build_og_data/4` for a complete integration.
## Architecture
See [AGENTS.md](AGENTS.md) for the full architecture: variable syntax and
resolution order, hierarchy resolution semantics, the rendering pipeline and
cache, canvas element JSON shapes, and the editor's JS hook.
## Development
```bash
mix deps.get # Install dependencies
mix test # Run the test suite
mix format # Format code
mix credo --strict # Static analysis
mix dialyzer # Type checking
mix precommit # compile (warnings-as-errors) + deps.unlock --check-unused + quality.ci
```
## Dependencies
| Package | Purpose |
|---------|---------|
| `phoenix_kit` | Module behaviour, Settings API, admin dashboard integration |
| `phoenix_live_view` | Template/assignment editor LiveViews |
| `ecto_sql` | Template and assignment schemas |
| `resvg` | SVG → PNG rasterization (precompiled NIF) |
## License
MIT — see [LICENSE](LICENSE) for details.