TechEarl

Override Rank Math's Title and Meta Description Programmatically

How to override Rank Math's SEO title and meta description in PHP using the rank_math/frontend/title and rank_math/frontend/description filters, plus the canonical and Open Graph filters, with conditional per-context overrides for rewrite-driven pages.

Ishan Karunaratne⏱️ 9 min readUpdated
Share thisCopied
Override Rank Math's SEO title and meta description programmatically: the rank_math/frontend/title and rank_math/frontend/description filters, plus canonical and Open Graph overrides for dynamic, rewrite-driven WordPress pages.

To override the SEO title and meta description that Rank Math prints, hook the rank_math/frontend/title and rank_math/frontend/description filters and return your own string. This runs after Rank Math has built its value, so whatever you return wins:

php
add_filter( 'rank_math/frontend/title', function ( $title ) {
    return 'My custom title' . ' - ' . get_bloginfo( 'name' );
} );

add_filter( 'rank_math/frontend/description', function ( $description ) {
    return 'My custom meta description, under 160 characters.';
} );

That is the whole mechanism. Rank Math does not give you a "set the title from code" function. It gives you filters on the values it is about to render, and you intercept them. The same pattern covers the canonical URL and every Open Graph tag, which is what makes this approach work for pages that have no edit screen at all: archives, rewrite-driven URLs, and anything generated on the fly.

This is the Rank Math version of a technique that applies to any SEO plugin. If you are on Yoast, the filter names differ but the shape is identical, see override Yoast's title and meta description programmatically. For the plugin-agnostic version that targets the raw wp_head output, see override the WordPress SEO title and meta description.

The full filter set

Rank Math exposes a filter for each piece of metadata it outputs. The four you reach for most:

php
// The <title> and the SEO title.
add_filter( 'rank_math/frontend/title', 'te_rankmath_title' );

// The <meta name="description"> tag.
add_filter( 'rank_math/frontend/description', 'te_rankmath_description' );

// The <link rel="canonical"> tag.
add_filter( 'rank_math/frontend/canonical', 'te_rankmath_canonical' );

// The Open Graph type, e.g. "article" or "website".
add_filter( 'rank_math/opengraph/type', 'te_rankmath_og_type' );

The Open Graph title and description are network-namespaced. Rank Math builds those filter names dynamically as rank_math/opengraph/{network}/{property}, where {network} is facebook or twitter. So the Facebook (Open Graph) title and description are:

php
add_filter( 'rank_math/opengraph/facebook/og_title', 'te_rankmath_og_title' );
add_filter( 'rank_math/opengraph/facebook/og_description', 'te_rankmath_og_description' );

// Twitter Card equivalents use the twitter namespace.
add_filter( 'rank_math/opengraph/twitter/twitter_title', 'te_rankmath_twitter_title' );
add_filter( 'rank_math/opengraph/twitter/twitter_description', 'te_rankmath_twitter_description' );

The network-namespaced structure matters: there is no single og_title filter that covers both Facebook and Twitter. If you only hook rank_math/opengraph/facebook/og_title, the Twitter Card title is untouched and falls back to whatever Rank Math derived. When you are overriding a page's social preview, set both, or set the SEO title and let Rank Math derive the social tags from it (it does this by default when the social fields are empty).

Note that the callbacks above are named with a te_ prefix purely as a namespacing convention. They are functions this example defines, so they get the prefix; the filter names themselves and $title / $description style arguments are fixed by WordPress and Rank Math and are never renamed.

Conditional, per-context overrides

The flat examples replace the value on every page, which is rarely what you want. The real use is conditional: override the title only on a specific page, a taxonomy archive, or a rewrite-driven query var. Conditional tags (is_page(), is_tax(), get_query_var()) gate the override and otherwise return the original value untouched:

php
add_filter( 'rank_math/frontend/title', function ( $title ) {
    // A single static page by slug.
    if ( is_page( 'pricing' ) ) {
        return 'Pricing and Plans - ' . get_bloginfo( 'name' );
    }

    // A custom taxonomy archive.
    if ( is_tax( 'product_brand' ) ) {
        $term = get_queried_object();
        return sprintf( '%s Products - %s', $term->name, get_bloginfo( 'name' ) );
    }

    return $title;
} );

The pattern that justifies doing this in code rather than the Rank Math editor is the rewrite-driven page: a URL like /deals/london/ served by a single template, where the city comes from a custom query var registered with add_rewrite_tag(). There is no post to open in the editor, so there is no SEO box to type into. The filter is the only place the title and description exist:

php
add_filter( 'rank_math/frontend/title', function ( $title ) {
    $city = get_query_var( 'te_deal_city' );
    if ( $city ) {
        $city = ucwords( str_replace( '-', ' ', $city ) );
        return sprintf( 'Deals in %s - %s', $city, get_bloginfo( 'name' ) );
    }
    return $title;
} );

add_filter( 'rank_math/frontend/description', function ( $description ) {
    $city = get_query_var( 'te_deal_city' );
    if ( $city ) {
        $city = ucwords( str_replace( '-', ' ', $city ) );
        return sprintf( 'Browse the latest verified deals in %s, updated daily.', $city );
    }
    return $description;
} );

Set the canonical the same way so each generated URL points at itself rather than inheriting a stale value:

php
add_filter( 'rank_math/frontend/canonical', function ( $canonical ) {
    $city = get_query_var( 'te_deal_city' );
    if ( $city ) {
        return home_url( '/deals/' . sanitize_title( $city ) . '/' );
    }
    return $canonical;
} );

Keep every callback returning the original argument on the path where the condition does not match. A filter that returns an empty string (or nothing) when its branch is skipped will blank out the title or description on every other page on the site. This is the single most common way these snippets go wrong.

Reading Rank Math's stored meta

Sometimes you do not want to replace the value, you want to read what an editor typed into the Rank Math box for a post and reuse it elsewhere (a custom card, a JSON feed, an email). Rank Math stores the SEO title and description in post meta under rank_math_title and rank_math_description:

php
$id    = get_the_ID();
$title = get_post_meta( $id, 'rank_math_title', true );
$desc  = get_post_meta( $id, 'rank_math_description', true );

Two things to know about the stored values. First, if the field was left empty in the editor and Rank Math is generating the value from its global title template, the meta key is never written to the database, so get_post_meta() returns an empty string. Always fall back:

php
function te_rankmath_seo_title( $id ) {
    $stored = get_post_meta( $id, 'rank_math_title', true );
    return $stored !== '' ? $stored : get_the_title( $id );
}

Second, the stored title can contain Rank Math's template variables (tokens like %title% or %sep%) rather than literal text, because that is what the editor saves when you use a variable. If you need the rendered string, hook the frontend filter instead of reading meta directly, since the filter fires after Rank Math has expanded those tokens. Read the raw meta only when you genuinely want the unexpanded template.

Disabling Rank Math's output for one page

If a template renders its own <title>, canonical, and Open Graph tags (a fully custom landing page, say), you do not want Rank Math's tags fighting yours in the <head>. Returning an empty string from rank_math/frontend/title suppresses the SEO title; the same works for the description and canonical filters:

php
add_filter( 'rank_math/frontend/title', function ( $title ) {
    if ( is_page_template( 'template-custom-head.php' ) ) {
        return '';
    }
    return $title;
} );

For the whole Open Graph and Twitter block, Rank Math also exposes rank_math/frontend/remove_credit_notice and a set of disable filters; the cleaner approach for a single template is to suppress the specific tags you are replacing rather than tearing out the whole head. If you are stripping a lot of core and plugin head output for a bespoke template, the broader sweep is covered in clean up wp_head in WordPress.

Ship it as a must-use plugin

Like any "change how the head renders" code, this belongs in a plugin rather than functions.php, so a theme switch cannot silently take your overrides with it. A must-use plugin loads automatically and is the right home:

php
<?php
/**
 * Plugin Name: TE Rank Math Meta
 * Plugin URI:  https://techearl.com/wordpress-override-rank-math-title-meta
 * Description: Programmatic overrides for Rank Math's frontend title, description, canonical, and Open Graph tags.
 * Version:     1.0.0
 * Author:      Ishan Karunaratne
 * Author URI:  https://techearl.com
 * License:     GPL-2.0-or-later
 * Text Domain: te-rankmath-meta
 */

defined( 'ABSPATH' ) || exit;

add_filter( 'rank_math/frontend/title', 'te_rankmath_title' );
add_filter( 'rank_math/frontend/description', 'te_rankmath_description' );
add_filter( 'rank_math/frontend/canonical', 'te_rankmath_canonical' );

function te_rankmath_title( $title ) {
    $city = get_query_var( 'te_deal_city' );
    if ( $city ) {
        $city = ucwords( str_replace( '-', ' ', $city ) );
        return sprintf( 'Deals in %s - %s', $city, get_bloginfo( 'name' ) );
    }
    return $title;
}

function te_rankmath_description( $description ) {
    $city = get_query_var( 'te_deal_city' );
    if ( $city ) {
        $city = ucwords( str_replace( '-', ' ', $city ) );
        return sprintf( 'Browse the latest verified deals in %s, updated daily.', $city );
    }
    return $description;
}

function te_rankmath_canonical( $canonical ) {
    $city = get_query_var( 'te_deal_city' );
    if ( $city ) {
        return home_url( '/deals/' . sanitize_title( $city ) . '/' );
    }
    return $canonical;
}

Drop that at wp-content/mu-plugins/te-rankmath-meta.php. One small gotcha: if Rank Math is not active, these filters simply never fire, so the file is a harmless no-op rather than an error. You do not need to guard on function_exists() or class checks for filter callbacks like these.

Verify it worked

Filters that touch the <head> are easy to get subtly wrong (an override that fires on the wrong context, or blanks every other page), so check the rendered HTML directly. From the command line:

bash
curl -s https://example.com/deals/london/ | grep -iE '<title>|name="description"|rel="canonical"|og:title|og:description'

That one line surfaces the <title>, the meta description, the canonical link, and the Open Graph title and description in one shot. Walk a couple of URLs: the page you meant to override should show your custom strings, and an unrelated page (the home page, a normal post) should still show Rank Math's defaults, untouched. If the unrelated page is also showing your override or a blank tag, your conditional branch is wrong or a callback is failing to return $title on the no-match path.

If the tags do not change at all, the usual suspects are a page cache serving stale HTML (purge it), Rank Math being inactive on that environment, or a typo in the network-namespaced Open Graph filter name (rank_math/opengraph/facebook/og_title, not rank_math/frontend/og_title).

See also

Sources

Authoritative references this article was fact-checked against.

TagsWordPressRank MathSEOPHPFilters

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Update ACF Fields Programmatically

update_field() is the canonical way to write ACF data programmatically: the function signature, how to write to Repeater and Flexible Content fields, when to use field keys instead of names, options pages and user meta, and the gotchas that bite.