Functions, shims and meta keys

This page covers the PHP that themes and plugins can call: the anansi_is_active() check, the breadcrumb functions and shortcodes Anansi provides in place of other SEO plugins’ ones, a few read-only class methods, and the meta keys Anansi stores with their value formats. Anansi has one global function of its own; everything else is a static method on an Anansi_* class. Always guard calls with function_exists() or class_exists(), so your code keeps working when Anansi is deactivated.

anansi_is_active()

anansi_is_active(): bool

Defined in includes/functions.php, loaded as soon as the plugin loads. Returns true when Anansi prints the page’s SEO output: the mode is live and no other full SEO plugin (Rank Math, Yoast SEO, All in One SEO, SEOPress, The SEO Framework) is active.

When it returns true, your theme or plugin should stop printing its own title, meta description, robots, canonical, Open Graph, Twitter and JSON-LD tags. It returns false in preview mode and on signed preview requests, so the site keeps its current output until you switch Anansi to live.

function mytheme_seo_active() {
    return function_exists( 'anansi_is_active' ) && anansi_is_active();
}

add_action( 'wp_head', function () {
    if ( mytheme_seo_active() ) {
        return; // Anansi prints the description.
    }
    echo '<meta name="description" content="' . esc_attr( mytheme_description() ) . '" />' . "\n";
}, 5 );

It is cheap to call: it reads the anansi_mode option (autoloaded) and checks a few constants.

See theme-integration.md for the full pattern.

Anansi has no breadcrumb function under its own name. Instead it defines the functions and shortcodes that themes already call for Rank Math, Yoast SEO, All in One SEO and SEOPress, so a theme keeps its breadcrumbs after the switch.

When they load

They are registered on template_redirect (priority 0) on front-end page requests, only when:

  • Anansi is in charge (live mode, no other SEO plugin active), and
  • the function or shortcode does not exist yet.

They are never defined in admin, AJAX, cron, REST or WP-CLI requests. Those are the requests where the old plugin could be activated again, and a second definition of the same function name would be a fatal error.

Because they load on template_redirect, call them from templates, not from functions.php at load time. Keep your function_exists() checks.

Functions

FunctionSignatureOutput
rank_math_the_breadcrumbs()( array $args = array() ): voidEchoes the trail.
rank_math_get_breadcrumbs()( array $args = array() ): stringReturns the trail.
yoast_breadcrumb()( string $before = '', string $after = '', bool $display = true ): string\|voidWraps the trail in $before / $after; echoes, or returns when $display is false.
aioseo_breadcrumbs()( bool $display = true ): string\|voidEchoes, or returns when $display is false.
seopress_display_breadcrumbs()( bool $display = true ): string\|voidEchoes, or returns when $display is false.

$args for the Rank Math functions:

KeyDefault
wrap_before<nav aria-label="breadcrumbs" class="rank-math-breadcrumb"><p>
wrap_after</p></nav>
before''
after''

yoast_breadcrumb() wraps the trail in <span><span> … </span></span> instead of the <nav>.

Shortcodes

[rank_math_breadcrumb], [wpseo_breadcrumb], [aioseo_breadcrumbs] and [seopress_breadcrumbs] all print the same trail with the Rank Math markup. They take no attributes.

Markup

All of them use Rank Math’s markup, so existing CSS keeps working:

<nav aria-label="breadcrumbs" class="rank-math-breadcrumb"><p>
  <a href="https://example.com">Home</a><span class="separator"> &raquo; </span>
  <a href="https://example.com/news/">News</a><span class="separator"> &raquo; </span>
  <span class="last">Post title</span>
</p></nav>

The separator, the optional prefix label (<span class="label">) and the home crumb come from General → Breadcrumbs. They print nothing when breadcrumbs are off. Turn them on in settings, or with add_theme_support( 'anansi-breadcrumbs' ) (Anansi also accepts rank-math-breadcrumbs).

Change the trail with anansi/breadcrumb/items and the final HTML with anansi/breadcrumb/html.

Reading the trail yourself

Anansi_Breadcrumbs::enabled(): bool
Anansi_Breadcrumbs::crumbs(): array

crumbs() returns the filtered trail for the current request. Each crumb is array( 0 => label, 1 => url, 'hide_in_schema' => bool ). Build your own markup from it if the Rank Math markup does not fit your theme:

if ( class_exists( 'Anansi_Breadcrumbs' ) && Anansi_Breadcrumbs::enabled() ) {
    $crumbs = Anansi_Breadcrumbs::crumbs();
    echo '<ol class="crumbs">';
    foreach ( $crumbs as $crumb ) {
        echo '<li>' . ( $crumb[1] ? '<a href="' . esc_url( $crumb[1] ) . '">' . esc_html( $crumb[0] ) . '</a>' : esc_html( $crumb[0] ) ) . '</li>';
    }
    echo '</ol>';
}

Other shortcode and block

NameWhat it prints
[anansi_preferred_source]A link to Google’s “preferred sources” page for your site. Attributes: label, style (link or button), domain, align.
Block anansi/preferred-sourceThe same link as a block (attributes label, style).

Anansi also renders Rank Math’s rank-math/faq-block and rank-math/howto-block with Rank Math’s markup while Rank Math is not active, so their content stays on the page.

Meta keys

Anansi stores its per-object SEO data in _anansi_{key} meta. The leading underscore keeps the keys out of the Custom Fields box. The editor module registers them with register_meta(): they are sanitised on save, need edit_post / edit_term / edit_user to write, and show in the REST API in the edit context only.

Post meta

KeyFormatMeaning
_anansi_titlestringSEO title template; may use variables such as %title% %sep% %sitename%. '' = cleared, use the post type template.
_anansi_descriptionstringMeta description template. '' = cleared.
_anansi_focus_keywordstringFocus keywords, comma-separated. The first is the main one.
_anansi_robotsstring[]Any of index, noindex, nofollow, noarchive, nosnippet, noimageindex. noindex removes index.
_anansi_advanced_robotsarraymax-snippet (string number), max-video-preview (string number), max-image-preview (none, standard, large).
_anansi_canonical_urlstring (URL)Custom canonical URL.
_anansi_breadcrumb_titlestringLabel used for this post in breadcrumbs.
_anansi_facebook_titlestringOpen Graph title.
_anansi_facebook_descriptionstringOpen Graph description.
_anansi_facebook_image_idintAttachment ID for Open Graph.
_anansi_twitter_use_facebookon | offReuse the Open Graph values for Twitter.
_anansi_twitter_card_typesummary_large_image | summary_cardCard type for this post.
_anansi_twitter_titlestringTwitter title.
_anansi_twitter_descriptionstringTwitter description.
_anansi_twitter_image_idintAttachment ID for Twitter.
_anansi_pillar_contenton | offCornerstone content.
_anansi_news_robotsnoindex | indexnoindex keeps the post out of the news sitemap.
_anansi_lock_modified_dateon | offKeep the modified date when the post is saved.
_anansi_rich_snippetoff | articleSchema type choice for the post.
_anansi_snippet_article_typeArticle | BlogPosting | NewsArticleArticle type when rich_snippet is article.
_anansi_seo_scoreintScore from the editor’s SEO checks.
_anansi_primary_{taxonomy}int (term ID)Primary term, for example _anansi_primary_category. Registered for hierarchical, public taxonomies shown in REST.
_anansi_schema_{Type}array (serialized)A stored schema row in Rank Math’s shape: @type, the fields (with %variables%) and metadata { title, type: template or custom, isPrimary, builder }. Written by the schema editor and the importer. A second row of the same type gets a suffix: _anansi_schema_Product_2, _3… metadata.builder names the editor type (product, recipe…); such rows keep an organizer you entered on events, and local businesses get no isPartOf/publisher/inLanguage. Read with Anansi_Schema_Rows::get( $post_id ) or ::entries() (adds meta_id, key and source).
_anansi_schema_replacesint[]Rank Math rank_math_schema_* meta IDs that Anansi rows replace. Editing a Rank Math row in Anansi writes an Anansi copy and lists the original here; removing one only lists it. Listed rows are skipped by Anansi_Schema_Rows (front end, editor, abilities). Rank Math’s meta is never changed.
_anansi_videosarrayVideo details found in the post. Internal format, may change.

For text fields (title, description, canonical_url, focus_keyword, breadcrumb_title, the Facebook and Twitter title and description, snippet_name, snippet_desc), an empty string stored under the _anansi_ key means the user cleared the field on purpose. Anansi then stops and does not fall back to Rank Math’s value.

Term meta

_anansi_title, _anansi_description, _anansi_robots, _anansi_canonical_url, _anansi_breadcrumb_title. Same formats as the post keys.

User meta

_anansi_title, _anansi_description, _anansi_robots, _anansi_facebook_author (URL), _anansi_twitter_author (handle).

Writing meta

Write only to _anansi_* keys. update_post_meta() works and runs the registered sanitiser:

update_post_meta( $post_id, '_anansi_description', 'A short summary of the post.' );
update_post_meta( $post_id, '_anansi_robots', array( 'noindex', 'nofollow' ) );

For AI tools and automation, use the anansi/update-post-seo ability instead. It applies the same rules as the editor.

Reading meta: Anansi_Meta

Anansi_Meta reads a field the way Anansi does: _anansi_{key}, then rank_math_{key}, then any keys added with the anansi/meta/sources filter. The first stored, non-empty value wins.

MethodReturns
Anansi_Meta::post( int $post_id, string $key )The stored value, '' when none (also '' for auto-drafts).
Anansi_Meta::term( int $term_id, string $key )Same for a term.
Anansi_Meta::user( int $user_id, string $key )Same for a user.
Anansi_Meta::text( string $type, WP_Post\|WP_Term\|WP_User $object, string $key )A title or description with its variables replaced, '' when none. $type is post, term or user.
Anansi_Meta::keys( string $type, string $key )The meta keys tried, in order (after anansi/meta/sources).
Anansi_Meta::noindex( int $post_id )true when the post’s own robots contain noindex.

$key is the short key, without _anansi_: title, description, robots, canonical_url, focus_keyword, primary_category, and so on.

if ( class_exists( 'Anansi_Meta' ) ) {
    $raw      = Anansi_Meta::post( $post->ID, 'title' );          // "%title% for beginners %sep% %sitename%"
    $resolved = Anansi_Meta::text( 'post', $post, 'title' );       // "Bread baking for beginners - Acme"
    $keyword  = Anansi_Meta::post( $post->ID, 'focus_keyword' );   // "bread baking, sourdough"
    $noindex  = Anansi_Meta::noindex( $post->ID );
}

These values are what is stored for the object. They are not the final page output, which also uses the post type templates and the filters in hooks.md.

Reading settings

MethodOption
Anansi_Titles::get( string $key, $default = false )anansi_titles
Anansi_General::get( string $key, $default = false )anansi_general

Keys are Rank Math’s (for example title_separator, pt_post_title, breadcrumbs). Values are normalised the Rank Math way: 'on' and 'true' become true, 'off' and 'false' become false, numeric strings become integers. Missing keys return $default.

Final page values (not yet stable)

Anansi_Page::get() returns the per-request object Anansi uses to build the head block. Its methods title(), description(), robots(), canonical() and is_noindex() return the final, filtered values for the current request (title() and description() are HTML-escaped). It is handy for debugging and for code that has to agree with Anansi’s output, but it is not part of the documented API yet and may change.