Open Graph Control
WordPress plugin for full control over Open Graph and social meta tags across 12 platforms (Facebook, X, LinkedIn, iMessage, Threads, Mastodon, Bluesky, WhatsApp, Discord, Pinterest, Telegram, Slack).
by Evžen Leonenko · github.com/teriffy/open-graph-control · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/teriffy/open-graph-control/archive/refs/heads/main.zipA WordPress plugin that emits Open Graph and social meta tags for 12 platforms, with per-platform rules, SEO-plugin conflict handling, Pinterest Rich Pins, output cache and live per-post preview.
Status (v0.4.0 — dynamic OG card generation in progress)
Backend
- 12 platform classes — Facebook, X / Twitter, LinkedIn, iMessage, Threads, Mastodon, Bluesky, WhatsApp, Discord, Pinterest, Telegram, Slack
- 6 resolvers (title, description, image, type, URL, locale) with filterable fallback chains
- Per-archive overrides (v0.3) — OG title / description / image editable on every category, tag, custom taxonomy term, and author edit screen, wired into the resolver chain via a dedicated
archive_overridestep - Dynamic OG card generation (v0.4) — server-side 1200×630 PNG rendering via GD for posts / archives / authors without explicit OG imagery. Auto-generated cards use a fixed layout customizable by filters (logo, site name, title, description, background color). Triggering is opt-in via Settings → Images → Card template; rendering happens on
shutdownhook, never blocking the editor. Inter font (SIL OFL) is bundled. - Dynamic field sources (v0.4+) — map ACF or JetEngine custom fields to the OG title and description chains per post type. When mapped and populated, the field value wins over
post_title/post_excerpt. Configure in Settings → Field sources. - Pinterest Rich Pins JSON-LD (Article / Product / Recipe)
- 7 SEO plugin integrations with clean takeover — Yoast, Rank Math, All in One SEO, SEOPress, Jetpack, The SEO Framework, Slim SEO
- 3 auto-registered image sizes (landscape 1200×630, square 600×600, Pinterest 1000×1500)
- Transient-based output cache with smart invalidation hooks
Admin UI (React)
- Top-level admin menu, 12 settings sections (Overview, Site defaults, Platforms, Post types, Images, Fallback chains, Integrations, Debug/Test, Import/Export, Advanced, Archive overrides, Card template)
- Per-post meta box with Base + X / Twitter + Pinterest + Per-platform tabs, live preview for all 12 platforms, inline validation
- Archive editor (v0.3) on every taxonomy term + author edit screen — OG title / description / image with live character-count hints
- Card template editor (v0.4) in Settings → Images — opt-in toggle, live preview, customizable colors + logo + background, and stats dashboard (generated card count)
- MediaUpload widget for master image + per-platform overrides
- One-time admin notice when a competing SEO plugin is detected (take-over or keep choice)
- Bulk "Regenerate OG image sizes" action for existing attachments
- Reset-to-defaults + import/export JSON
REST API under open-graph-control/v1: /settings, /preview, /conflicts, /post-types, /meta/{id}, /images/regenerate, /settings/reset, /og-card/generate, /og-card/regenerate, /og-card/status, /og-card/purge (v0.4). All manage_options-gated, with rate-limiting on /preview.
WP-CLI: wp ogc tags <post_id>, wp ogc validate <post_id>, wp ogc regenerate, wp ogc cards generate|regenerate|status|purge (v0.4).
Hooks (filters and actions)
| Hook | Type | Signature | Since | Purpose |
|---|---|---|---|---|
ogc_resolve_{title,description,image,type,url,locale}_chain |
Filter | (array $steps): array |
v0.0 | Customize the resolver fallback chain for any field (e.g., add a custom step before site_default) |
ogc_resolve_{title,description,image,type,url,locale}_value |
Filter | (mixed $value, Context $context): mixed |
v0.0 | Final override for any resolved field after the entire chain runs |
ogc_resolve_image_step |
Filter | (string\|null $value, string $step, Context $context): ?string |
v0.4 | Intercept and override the result of a specific resolver step (e.g., post_meta_override, site_default, archive_override) |
ogc_resolve_title_step |
Filter | (string\|null $value, string $step, Context $context): ?string |
v0.4+ | Intercept per-step in the title resolver chain (e.g., provide a value for the acf_title_field or jet_title_field steps) |
ogc_resolve_description_step |
Filter | (string\|null $value, string $step, Context $context): ?string |
v0.4+ | Intercept per-step in the description resolver chain |
ogc_card_should_generate |
Filter | (bool $should, CardKey $key): bool |
v0.4 | Control whether a card should be auto-generated for a given post / archive / author (default: true if image chain returns null and card template is enabled) |
ogc_card_renderer_prefer_imagick |
Filter | (bool $prefer): bool |
v0.4 | Reserved for v0.5: hint the renderer to prefer Imagick over GD if available (currently always false; GD is used exclusively in v0.4) |
ogc_card_generated |
Action | (CardKey $key, string $path): void |
v0.4 | Fired after a card is successfully rendered; $key is the post / archive / author identifier, $path is the local filesystem path to the generated PNG |
Quality gates (CI)
- PHP 8.1–8.4 matrix × PHPUnit (322 tests) + PHPStan level 8 + WPCS
- Code coverage uploaded as artifact
- JS lint (
@wordpress/scriptsESLint + Prettier) + Webpack build - Playwright fixture suite (18 tests: rendering +
@axe-coreWCAG 2 A/AA scan) - Playwright WP suite (12 tests: activation, settings save, metabox override, frontend tags, axe on live wp-admin, responsive layout ≤782px, archive overrides) — opt-in via
OGC_E2E_WP=1 - WordPress/plugin-check-action against the built dist zip on every push
Security
Open Graph Control is built so no user data leaves your server. The plugin does not call any external API, does not phone home, and does not ship telemetry. See SECURITY.md for the full defensive posture and disclosure process.
Layered defenses
- Capability checks on every REST endpoint (
manage_optionsfor site-wide settings,edit_postper post ID for the meta box — no public or subscriber-level write path) - Nonce enforcement via
check_admin_refereron admin-post actions and WP core'sX-WP-Noncefor REST - Output escaping at the edge:
esc_attron every tag attribute,esc_url_rawon every URL,esc_htmlon admin surfaces - JSON-LD payloads encoded with
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT+ second-layerstr_replace('</', '<\/')— no string value can break out of the surrounding `