Behind the Scenes: Advanced Themer 13 — Hooking the SEO Page to Your Pages and Posts
This one’s for the WordPress devs. When I rebuilt Advanced Themer’s SEO Options page for version 13, I recoded every field from the legacy v12 plugin into proper registered settings — meta description, keywords, title prefixes and suffixes, the works. The form saved. The options persisted. Everything looked done.
It wasn’t. Every single at13_seo_* option was write-only. The admin page saved them, and nothing on the front end ever read them. No wp_head output, no title filters, nothing. A settings page that faithfully stores your input and then does absolutely nothing with it. (In fairness to v13 — the legacy plugin had the same problem in more than one place. The v12 CSS page rendered its whole Quick Styles form without ever calling register_setting(), so WordPress silently refused to save it at all.)
Here’s how the consumer side got built.
The Per-Page SEO Meta Box
The SEO Options page had a “Per-Page SEO Box” checkbox that was literally a flag pointing at a future build. That build is now real: when the flag is on, Pages and Posts get a meta box with three fields — SEO Title override, Meta Description, and Meta Keywords.
add_action('add_meta_boxes', function () {
if (!get_option('at13_seo_pp_section_enabled')) return;
foreach (['page', 'post'] as $type) {
add_meta_box(
'at13_seo_meta_box',
__('SEO — Advanced Themer 13', 'advanced-themer-13'),
'at13_render_seo_meta_box',
$type,
'normal',
'default'
);
}
});
The save handler is standard hardening: nonce check, autosave bail, capability check. One detail worth stealing — empty fields delete the post meta instead of storing empty strings, so the postmeta table doesn’t fill up with blank rows for every page you never customized:
foreach (at13_seo_meta_fields() as $key => $sanitize) {
$value = isset($_POST[$key]) ? call_user_func($sanitize, wp_unslash($_POST[$key])) : '';
if ($value === '') {
delete_post_meta($post_id, $key);
} else {
update_post_meta($post_id, $key, $value);
}
}
The field list itself lives in one small function mapping meta key to sanitize callback, so the render and save sides can never drift out of sync.
Precedence: Per-Page Beats Global
Every front-end output follows the same chain: per-page meta box value → global option (if its enable checkbox is on) → nothing. One helper enforces the gate:
function at13_seo_get_page_meta($key) {
if (!get_option('at13_seo_pp_section_enabled')) return '';
if (!is_singular(['post', 'page'])) return '';
return get_post_meta(get_queried_object_id(), $key, true);
}
Turn the meta box off and every per-page value stops applying instantly — the data stays in postmeta, but nothing reads it. One kill switch, no orphaned behavior.
Titles: document_title_parts, Not wp_title
Title overrides go through the modern document_title_parts filter. The per-page SEO title replaces the core title part, then the prefix/suffix chains stack on top — article prefix/suffix on single posts, category prefix/suffix on category archives, and the global prefix/suffix outermost so it applies everywhere:
add_filter('document_title_parts', function ($parts) {
$custom = at13_seo_get_page_meta('_at13_seo_title');
if ($custom) $parts['title'] = $custom;
$title = isset($parts['title']) ? $parts['title'] : '';
if (is_singular('post')) {
if (get_option('at13_seo_article_prefix_enabled') && ($p = get_option('at13_seo_article_prefix'))) $title = $p . ' ' . $title;
if (get_option('at13_seo_article_suffix_enabled') && ($s = get_option('at13_seo_article_suffix'))) $title = $title . ' ' . $s;
}
// ... category + global chains follow the same pattern
$parts['title'] = trim($title);
return $parts;
});
Because it’s document_title_parts and not a full pre_get_document_title hijack, the separator and site-name parts that the theme (or another plugin) manages stay intact.
Meta Tags at Priority 1
Description and keywords print on wp_head at priority 1, so they land high in <head> before the pile of styles and scripts. Values get wp_strip_all_tags() plus esc_attr() on the way out — options are sanitized at save time, but output escaping is not optional just because input was clean.
The Fun One: Keyword Deep Links
The SEO page lets you define up to three keyword/link/alt sets. On the front end, each keyword’s first plain-text occurrence in post content gets auto-linked. The regex is where it gets interesting:
$pattern = '/\b(' . preg_quote($keyword, '/') . ')\b(?![^<]*>)(?![^<>]*<\/a>)/i';
$content = preg_replace($pattern, $replace, $content, 1);
Two negative lookaheads do the safety work: the first skips matches sitting inside an HTML tag (so a keyword in an alt="" attribute never gets mangled), the second skips text that’s already inside a link (no nested anchors). The 1 limit on preg_replace keeps it to one link per keyword — auto-linking every occurrence is how content starts reading like 2006 spam. The filter also checks in_the_loop() && is_main_query() so it doesn’t fire on excerpts, widgets, or secondary queries.
The Rest of the Wiring
Menu prefix/suffix bookend nav menus as plain list items via wp_nav_menu_items. The Author Title option swaps WordPress’s default “Author:” archive label through get_the_archive_title_prefix — a filter a lot of devs don’t know exists. Footer HTML outputs on wp_footer, run through wp_kses_post().
And one field deliberately stayed unwired: Deep Link Keywords, a lone text input with no link target and no clear consumer even in the legacy code. Shipping a behavior I’d be guessing at is worse than shipping a field that visibly does nothing — it’s parked until it has a real spec.
The Takeaway
A settings page isn’t a feature. It’s half of one. The other half is the hooks that actually consume what it saves — and it’s shockingly easy, especially when porting legacy code, to build the form, watch it save, and call it done. If you’re auditing an old plugin, grep for get_option() on every option the admin registers. The ones that only ever appear inside the admin form itself? Those are your write-only settings, and your users have been checking boxes that do nothing.
Advanced Themer 13’s SEO page now does what it always looked like it did. Tested on live pages and posts — per-page overrides, title chains, meta tags, keyword links, all firing.