OG Pilot Ruby
Important
An active OG Pilot subscription is required to use this gem.
A small Ruby client for generating OG Pilot Open Graph images via signed JWTs.
Installation
Add to your Gemfile:
gem "og_pilot_ruby"Then install:
bundle installConfiguration
The default initializer reads from OG_PILOT_API_KEY and OG_PILOT_DOMAIN automatically.
Override as needed:
OgPilotRuby.configure do |config|
config.api_key = ENV.fetch("OG_PILOT_API_KEY")
config.domain = ENV.fetch("OG_PILOT_DOMAIN")
# config.strip_extensions = true
# config.cache_store = Rails.cache
# config.cache_ttl = 86_400
endFor Rails, drop the snippet above into config/initializers/og_pilot_ruby.rb.
You can also generate it with:
bin/rails og_pilot_ruby:installUsage
Generate an image URL (the client sends a signed POST request, then follows the redirect returned by OG Pilot). In Rails, you can skip the require:
require "og_pilot_ruby"
image_url = OgPilotRuby.create_image(
template: "blog_post",
title: "How to Build Amazing OG Images",
description: "A complete guide to social media previews",
bg_color: "#1a1a1a",
text_color: "#ffffff",
author_name: "Jane Smith",
publish_date: "2024-01-15",
iat: Time.now.to_i
)If you omit iat, OG Pilot will cache the image indefinitely. Provide an iat to
refresh the cache daily.
When config.cache_store is set, the client can also cache generated responses
locally:
- With
iat: cache forcache_ttlseconds - Without
iat: cache for7 * cache_ttlseconds
Fail-safe behavior
create_image is non-blocking. If any error occurs (request, configuration,
validation, parsing, etc.), the gem does not raise to your app and logs an
error-level message instead.
- URL mode (
json: false, default): returnsnil - JSON mode (
json: true): returns{ "image_url" => nil }
Template helpers
create_image defaults to the page template when template is omitted.
Supported templates: page, blog_post, podcast, product, event, book, company, portfolio, github.
Use these helpers to force a specific template:
OgPilotRuby.create_blog_post_image(...)OgPilotRuby.create_podcast_image(...)OgPilotRuby.create_product_image(...)OgPilotRuby.create_event_image(...)OgPilotRuby.create_book_image(...)OgPilotRuby.create_company_image(...)OgPilotRuby.create_portfolio_image(...)- For
github, useOgPilotRuby.create_image(template: "github", ...)(no dedicated helper yet).
Example:
image_url = OgPilotRuby.create_blog_post_image(
title: "How to Build Amazing OG Images",
author_name: "Jane Smith",
publish_date: "2024-01-15"
)OG Image Examples
All sample payloads set explicit bg_color, text_color, and logo/avatar URLs to avoid default branding fallbacks.
For templates that support custom images, this set includes both with_custom_image and without_custom_image variants. (github currently has no custom image slot.)
Avatar-style fields (for example author_avatar_url) use DiceBear Avataaars, e.g. https://api.dicebear.com/7.x/avataaars/svg?seed=JaneSmith.
Sample Gallery
Parameters Used
[
{
"id": "page_with_custom_image",
"template": "page",
"variant": "with_custom_image",
"params": {
"title": "Acme Robotics Product Updates",
"description": "See what shipped this week across the web app.",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.notion.so",
"image_url": "https://images.unsplash.com/photo-1558655146-d09347e92766?w=1400&q=80",
"bg_color": "#0B1220",
"text_color": "#F8FAFC",
"template": "page"
},
"image_url": "https://img.ogpilot.com/1f6oY498I6SiNfqGDjwdHLNpwmeU264t2OL0k7tY8Mw/plain/s3://og-pilot-development/eoo5v45d766hf22j4r2al60ktali"
},
{
"id": "page_without_custom_image",
"template": "page",
"variant": "without_custom_image",
"params": {
"title": "Notion AI Workspace for Product Teams",
"description": "Docs, specs, and roadmaps in one living workspace.",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.notion.so",
"bg_color": "#111827",
"text_color": "#E5E7EB",
"template": "page"
},
"image_url": "https://img.ogpilot.com/9MZdTcTRyOoRqpTLll__EvDimmgojZESfZWokDqXeZM/plain/s3://og-pilot-development/wfa9es2wuvp6btjiriekk53swp6n"
},
{
"id": "blog_post_with_custom_image",
"template": "blog_post",
"variant": "with_custom_image",
"params": {
"title": "How Stripe Reduced Checkout Abandonment by 18%",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fstripe.com",
"image_url": "https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=1400&q=80",
"author_name": "Maya Patel",
"author_avatar_url": "https://api.dicebear.com/7.x/avataaars/svg?seed=MayaPatel",
"publish_date": "2026-02-24",
"bg_color": "#0F172A",
"text_color": "#F8FAFC",
"template": "blog_post"
},
"image_url": "https://img.ogpilot.com/RBBQZnBrAKcVmFjJg6UtNqX8P6nRRQdGLrlJNWYif7I/plain/s3://og-pilot-development/je7pj816exul9umhyszqpnbxelmd"
},
{
"id": "blog_post_without_custom_image",
"template": "blog_post",
"variant": "without_custom_image",
"params": {
"title": "Inside Vercel's Edge Rendering Playbook",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fvercel.com",
"author_name": "Daniel Kim",
"author_avatar_url": "https://api.dicebear.com/7.x/avataaars/svg?seed=DanielKim",
"publish_date": "2026-02-18",
"bg_color": "#111827",
"text_color": "#E5E7EB",
"template": "blog_post"
},
"image_url": "https://img.ogpilot.com/yP1B7OrLOy9Iu9JDSNk9Veys3ESCuCSBM9il2wq13V4/plain/s3://og-pilot-development/6aei8frvun6kvqojoor1hqack31y"
},
{
"id": "podcast_with_custom_image",
"template": "podcast",
"variant": "with_custom_image",
"params": {
"title": "Indie Hackers Podcast: Pricing Experiments That Worked",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.spotify.com",
"image_url": "https://images.unsplash.com/photo-1478737270239-2f02b77fc618?w=1400&q=80",
"episode_date": "2026-02-21",
"bg_color": "#18181B",
"text_color": "#FAFAFA",
"template": "podcast"
},
"image_url": "https://img.ogpilot.com/rzOOt7PWJ44OEwpKnntMLZaPvtl76DA3yRlj6B6N-Cc/plain/s3://og-pilot-development/fmkeblwertneuy4p82foeg5rfltl"
},
{
"id": "podcast_without_custom_image",
"template": "podcast",
"variant": "without_custom_image",
"params": {
"title": "Shopify Masters: Building a 7-Figure DTC Brand",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.shopify.com",
"episode_date": "2026-02-19",
"bg_color": "#0B1120",
"text_color": "#E2E8F0",
"template": "podcast"
},
"image_url": "https://img.ogpilot.com/5UWWFG2J5bNRDOhDkN96ZG_g0XI9ULGDFgQkuVTjCYQ/plain/s3://og-pilot-development/yyhmo7lamj1n99i2n6dyttwungmq"
},
{
"id": "product_with_custom_image",
"template": "product",
"variant": "with_custom_image",
"params": {
"title": "Allbirds Tree Dasher 3",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.allbirds.com",
"image_url": "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=1400&q=80",
"unique_selling_point": "Free shipping + 30-day returns",
"bg_color": "#F8FAFC",
"text_color": "#0F172A",
"template": "product"
},
"image_url": "https://img.ogpilot.com/mzmHDMjyAX4VlpJanMT3zpmIJgSuClYI5eofhFpSJNQ/plain/s3://og-pilot-development/8ls2legb316l9vak40nu388uzy2t"
},
{
"id": "product_without_custom_image",
"template": "product",
"variant": "without_custom_image",
"params": {
"title": "Bose QuietComfort Ultra",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.bose.com",
"unique_selling_point": "Save $70 this weekend",
"bg_color": "#111827",
"text_color": "#F9FAFB",
"template": "product"
},
"image_url": "https://img.ogpilot.com/kK6d7xU3EWT5WKC6jKCw1rhJDmv9bwvRn2S-nShV4NA/plain/s3://og-pilot-development/52ns2l1ll7hjhfg0p3wn5c9pqtr5"
},
{
"id": "event_with_custom_image",
"template": "event",
"variant": "with_custom_image",
"params": {
"title": "Launch Week Berlin 2026",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.eventbrite.com",
"image_url": "https://images.unsplash.com/photo-1540575467063-178a50c2df87?w=1400&q=80",
"event_date": "2026-06-12/2026-06-14",
"event_location": "Station Berlin",
"bg_color": "#1E1B4B",
"text_color": "#F5F3FF",
"template": "event"
},
"image_url": "https://img.ogpilot.com/A7nxHkYs4xN4c1PhH2KQSWoB4ALwBdpP0HPiAss9_70/plain/s3://og-pilot-development/vjkdf6cx82dvdxmhiwtvrvkl976d"
},
{
"id": "event_without_custom_image",
"template": "event",
"variant": "without_custom_image",
"params": {
"title": "RubyConf Chicago 2026",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Frubyconf.org",
"event_date": "2026-11-17",
"event_location": "Chicago, IL",
"bg_color": "#312E81",
"text_color": "#EEF2FF",
"template": "event"
},
"image_url": "https://img.ogpilot.com/elpfu28vJ57XCGx3npKhCwXqqJnPoqwCm8Aj5SLkWsM/plain/s3://og-pilot-development/vpte818nvtegc60ta98q7pmw91c8"
},
{
"id": "book_with_custom_image",
"template": "book",
"variant": "with_custom_image",
"params": {
"title": "Designing APIs for Humans",
"description": "A practical handbook for product-minded engineers.",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.oreilly.com",
"image_url": "https://images.unsplash.com/photo-1512820790803-83ca734da794?w=1400&q=80",
"book_author": "Alex Turner",
"book_series_number": "2",
"book_genre": "Technology",
"bg_color": "#172554",
"text_color": "#EFF6FF",
"template": "book"
},
"image_url": "https://img.ogpilot.com/7pidSkvU_l0RzY9xBOLSa2x-jDWWvx14Gtv-KMDCGLw/plain/s3://og-pilot-development/cwnhb631ls2olk0yzkukmr5dnf7e"
},
{
"id": "book_without_custom_image",
"template": "book",
"variant": "without_custom_image",
"params": {
"title": "The Product Operating System",
"description": "How modern teams ship with confidence.",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.penguinrandomhouse.com",
"book_author": "Sofia Ramirez",
"book_series_number": "1",
"book_genre": "Business",
"bg_color": "#1E293B",
"text_color": "#F8FAFC",
"template": "book"
},
"image_url": "https://img.ogpilot.com/FpMbBN15SLgaK9FEX07xXcT5dQIWyZkFdHaZ9i5wx3U/plain/s3://og-pilot-development/0rzisfn2qswdzsz641rl3s29ngu9"
},
{
"id": "portfolio_with_custom_image",
"template": "portfolio",
"variant": "with_custom_image",
"params": {
"title": "Maya Chen • Product Designer",
"description": "Fintech UX, design systems, and prototyping.",
"logo_url": "https://api.dicebear.com/7.x/avataaars/svg?seed=MayaChen&size=64",
"image_url": "https://images.unsplash.com/photo-1557672172-298e090bd0f1?w=1400&q=80",
"bg_color": "#1F2937",
"text_color": "#F9FAFB",
"template": "portfolio"
},
"image_url": "https://img.ogpilot.com/gauiw2MMcNLLSFnQ07Hq3flv4S0L-89lnnjUOO0VgYU/plain/s3://og-pilot-development/qok04llq0ff3d2lherudwlhqxslm"
},
{
"id": "portfolio_without_custom_image",
"template": "portfolio",
"variant": "without_custom_image",
"params": {
"title": "Arjun Rao • Frontend Engineer",
"description": "React performance, accessibility, and DX.",
"logo_url": "https://api.dicebear.com/7.x/avataaars/svg?seed=ArjunRao&size=64",
"bg_color": "#0F172A",
"text_color": "#E2E8F0",
"template": "portfolio"
},
"image_url": "https://img.ogpilot.com/jVck9SDPlei0gaHq44_itLoVzn2wINrCP3XCTQF3SYs/plain/s3://og-pilot-development/jxa1s7dtibaeqamh0fsymwz7uqrx"
},
{
"id": "company_with_custom_image",
"template": "company",
"variant": "with_custom_image",
"params": {
"title": "Notion",
"description": "4 job openings",
"logo_url": "https://www.google.com/s2/favicons?sz=128&domain_url=https%3A%2F%2Fwww.notion.so",
"bg_color": "#111827",
"text_color": "#F9FAFB",
"template": "company"
},
"image_url": "https://img.ogpilot.com/rji7hNgoxRM1KF2GofIO9gqXJQNg5CqlPWbhbGpR4FA/plain/s3://og-pilot-development/2xl6zi3zgjm74izr46efduzhmbrr"
},
{
"id": "company_without_custom_image",
"template": "company",
"variant": "without_custom_image",
"params": {
"title": "Linear",
"description": "12 job openings",
"company_logo_url": "https://www.google.com/s2/favicons?sz=256&domain_url=https%3A%2F%2Flinear.app",
"bg_color": "#0B1220",
"text_color": "#E5E7EB",
"template": "company",
"iss": "gitdigest.ai"
},
"image_url": "https://img.ogpilot.com/PgGl9a6xPmG0Tn7qKmZZUczrv43cNLxzyISsbHG8_oE/plain/s3://og-pilot-development/6gmm6jg5r8ya27r3tr215edfd972"
},
{
"id": "github_activity",
"template": "github",
"variant": "default",
"params": {
"title": "rails/rails",
"description": "Recent merged PRs, reviews, and commit activity.",
"accent_color": "#22C55E",
"bg_color": "#0B1220",
"text_color": "#E5E7EB",
"contributions": [
{
"date": "2025-10-28",
"count": 6
},
{
"date": "2025-11-01",
"count": 3
},
{
"date": "2025-11-05",
"count": 9
},
{
"date": "2025-11-08",
"count": 12
},
{
"date": "2025-11-12",
"count": 7
},
{
"date": "2025-11-16",
"count": 11
},
{
"date": "2025-11-20",
"count": 5
},
{
"date": "2025-11-24",
"count": 14
},
{
"date": "2025-11-28",
"count": 8
},
{
"date": "2025-12-02",
"count": 4
},
{
"date": "2025-12-06",
"count": 10
},
{
"date": "2025-12-10",
"count": 15
},
{
"date": "2025-12-14",
"count": 6
},
{
"date": "2025-12-18",
"count": 9
},
{
"date": "2025-12-22",
"count": 13
},
{
"date": "2025-12-26",
"count": 4
},
{
"date": "2025-12-30",
"count": 7
},
{
"date": "2026-01-03",
"count": 11
},
{
"date": "2026-01-07",
"count": 16
},
{
"date": "2026-01-11",
"count": 12
},
{
"date": "2026-01-15",
"count": 6
},
{
"date": "2026-01-19",
"count": 8
},
{
"date": "2026-01-23",
"count": 14
},
{
"date": "2026-01-27",
"count": 9
},
{
"date": "2026-01-31",
"count": 5
},
{
"date": "2026-02-04",
"count": 13
},
{
"date": "2026-02-08",
"count": 17
},
{
"date": "2026-02-12",
"count": 10
},
{
"date": "2026-02-16",
"count": 7
},
{
"date": "2026-02-20",
"count": 12
},
{
"date": "2026-02-24",
"count": 9
}
],
"template": "github"
},
"image_url": "https://img.ogpilot.com/0dxm83dTyNCg5Dq_GZFT4SqsRyTWUO31d0HQwjIq0-A/plain/s3://og-pilot-development/jlhwskjsx08x56attaljw3ce0p65"
}
]Parameters
The client sends POST /api/v1/images requests. All parameters are embedded in the signed JWT payload; the only query param is token.
The gem handles iss (domain) and sub (API key prefix) automatically.
Core parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
template |
No | "page" |
Template name |
title |
Yes | — | Primary title text |
description |
No | — | Subtitle or supporting text |
logo_url |
No | — | Logo image URL |
image_url |
No | — | Hero image URL |
bg_color |
No | — | Background color (hex format) |
text_color |
No | — | Text color (hex format) |
iat |
No | — | Issued-at timestamp for daily cache busting |
path |
No | auto-set | Request path for image rendering context. When provided, it overrides auto-resolution (see Path handling) |
Configuration options
| Option | Default | Description |
|---|---|---|
api_key |
ENV["OG_PILOT_API_KEY"] |
Your OG Pilot API key |
domain |
ENV["OG_PILOT_DOMAIN"] |
Your domain registered with OG Pilot |
base_url |
https://ogpilot.com |
OG Pilot API base URL |
open_timeout |
5 |
Connection timeout in seconds |
read_timeout |
10 |
Read timeout in seconds |
strip_extensions |
true |
When true, file extensions are stripped from resolved paths (see Strip extensions) |
strip_query_parameters |
false |
When true, query strings are removed from resolved paths before signing (see Strip query parameters) |
cache_store |
nil |
Optional cache backend with read/write (for example Rails.cache) |
cache_ttl |
86400 |
Cache TTL in seconds when cache_store is enabled |
Ruby options
| Option | Default | Description |
|---|---|---|
json |
false |
When true, sends Accept: application/json and parses the JSON response. On failure, returns { "image_url" => nil }
|
headers |
— | Additional HTTP headers to include with the request |
default |
false |
Forces path to / when true, unless a manual path is provided (see Path handling) |
Template-specific parameters
| Template | Parameters |
|---|---|
page |
title, description
|
blog_post |
title, author_name, author_avatar_url, publish_date (ISO 8601) |
podcast |
title, episode_date (ISO 8601) |
product |
title, unique_selling_point
|
event |
title, event_date, event_location
|
book |
title, description, book_author, book_series_number, book_description, book_genre
|
portfolio |
title |
company |
title, company_logo_url, description (note: image_url is ignored) |
github |
title, description, contributions (array of { date, count }), accent_color
|
Path handling
The path parameter enhances OG Pilot analytics by tracking which OG images perform better across different pages on your site. Without it, all analytics would be aggregated under the / path, making it difficult to understand how individual pages or content types are performing. By automatically capturing the request path, you get granular insights into click-through rates and engagement for each OG image.
The client automatically injects a path parameter on every request:
| Option | Behavior |
|---|---|
default: false |
Uses the current request path when available. In Rails, prefers request.fullpath (via the per-request store), then falls back to Rack/CGI env vars (REQUEST_URI, PATH_INFO). If no path can be determined, uses /. |
default: true |
Forces the path parameter to /, regardless of the current request (unless path is provided explicitly). |
path: "/..." |
Uses the provided path verbatim (normalized to start with /), overriding auto-resolution. |
Example:
image_url = OgPilotRuby.create_image(
template: "blog_post",
title: "How to Build Amazing OG Images",
default: true
)Manual override:
image_url = OgPilotRuby.create_image(
template: "page",
title: "Hello OG Pilot",
path: "/pricing?plan=pro"
)Fetch JSON metadata instead:
payload = {
template: "page",
title: "Hello OG Pilot"
}
data = OgPilotRuby.create_image(**payload, json: true)Local caching
You can optionally enable client-side caching to avoid repeated API calls for identical payloads:
OgPilotRuby.configure do |config|
config.cache_store = Rails.cache
config.cache_ttl = 86_400 # 1 day
endCache key inputs include the normalized payload, iat, output mode (json
vs URL), and configured domain.
TTL behavior:
- If
iatis present: cached forcache_ttl - If
iatis omitted: cached for7 * cache_ttl
Strip extensions
When strip_extensions is enabled, the client removes file extensions from the
last segment of every resolved path. This ensures that /docs, /docs.md,
/docs.php, and /docs.html all resolve to "/docs", so analytics are
consolidated under a single path regardless of the URL extension.
Multiple extensions are also stripped (/archive.tar.gz becomes /archive).
Dotfiles like /.hidden are left unchanged. Query strings are preserved.
OgPilotRuby.configure do |config|
config.strip_extensions = true
end
# All of these resolve to path "/docs":
OgPilotRuby.create_image(title: "Docs", path: "/docs")
OgPilotRuby.create_image(title: "Docs", path: "/docs.md")
OgPilotRuby.create_image(title: "Docs", path: "/docs.php")
# Nested paths work too: /blog/my-post.html → /blog/my-post
# Query strings are preserved: /docs.md?ref=main → /docs?ref=main
# Dotfiles are unchanged: /.hidden stays /.hiddenStrip query parameters
When strip_query_parameters is enabled, the client drops the query string from
every resolved path before it signs the payload, so /docs?ref=main and
/docs?ref=next are normalized to "/docs". When both strip_extensions and
strip_query_parameters are enabled, the extension is removed first and the
query string is removed afterward, which means /archive.tar.gz?ref=campaign
becomes "/archive".
OgPilotRuby.configure do |config|
config.strip_query_parameters = true
config.strip_extensions = true
end
# These resolve to "/docs":
OgPilotRuby.create_image(title: "Docs", path: "/docs")
OgPilotRuby.create_image(title: "Docs", path: "/docs?ref=main")
# Both of these resolve to "/archive":
OgPilotRuby.create_image(title: "Archive", path: "/archive.tar.gz")
OgPilotRuby.create_image(title: "Archive", path: "/archive.tar.gz?ref=campaign")Development
Run tests with:
bundle exec rake testReleases
This repo uses gem-release to bump versions and tag releases:
bundle exec gem bump --version patch
bundle exec gem bump --version minor
bundle exec gem bump --version major
bundle exec gem bump --version 1.2.3Then publish:
bundle exec rake release