RankReady hooks and filters for developers
The four verified RankReady extension points, what each one does, and a working code example for every one.
Last updated October 15, 2026
RankReady exposes three filters and one action as its primary extension points. Those four are covered in full below, with a short example for each. Beyond them the plugin ships a further set of public-intent filters, listed in the reference table at the foot of this page. Everything in either list is a permanent contract: once shipped, RankReady does not remove a hook or change what it means. A hook in neither list is internal and may change without notice.
Put the snippets below in your child theme’s functions.php or, better, in a small site-specific plugin so they survive a theme change.
Summary table
| Name | Type | Purpose |
|---|---|---|
rankready_llms_txt_content |
Filter | Override or edit the generated /llms.txt body. |
rankready_force_llms_txt |
Filter | Force RankReady to serve /llms.txt even when another SEO plugin’s module is active. |
rnrd_is_pro |
Filter | The licence gate, for reading only. Do not filter it. |
rnrd_loaded |
Action | Fires once RankReady has loaded, giving add-on code a safe boot point. |
rankready_llms_txt_content
This filter hands you the generated body of /llms.txt before it is sent. Return it modified, or return something else entirely. Use it when you need a section the admin screens do not produce, or when you want to generate the file yourself and keep RankReady only as the routing layer.
Append a section
add_filter( 'rankready_llms_txt_content', function ( $content ) {
$extra = "## Company";
$extra .= "- Support: https://example.com/support";
$extra .= "- Status: https://status.example.com";
return $content . $extra;
} );
Replace the body entirely
add_filter( 'rankready_llms_txt_content', function ( $content ) {
$custom = get_option( 'my_handwritten_llms_txt' );
return $custom ? $custom : $content;
} );
Two things to keep in mind. First, you are responsible for what you return: RankReady does not re-validate a body you replaced. Second, whatever you return is what gets an ETag, so a body that changes on every request defeats the 304 response that /llms.txt would otherwise send.
rankready_force_llms_txt
Some SEO plugins ship their own llms.txt module. When RankReady detects one, it stands down rather than fighting over the same URL. This filter overrides that decision and tells RankReady to serve the file anyway.
add_filter( 'rankready_force_llms_txt', '__return_true' );
Use it when you have deliberately chosen RankReady’s output over the other plugin’s and you do not want to disable the other plugin’s module. Only one of the two can win the URL, so decide which one you want and be consistent. If you would rather the other plugin served the file, leave this filter alone and turn RankReady’s llms.txt off under AI Visibility instead.
You can also gate it, for example while you compare the two outputs on a staging site:
add_filter( 'rankready_force_llms_txt', function ( $force ) {
return defined( 'WP_ENVIRONMENT_TYPE' ) && 'staging' === WP_ENVIRONMENT_TYPE;
} );
rnrd_is_pro
The licence gate. RankReady Pro hooks this filter when a valid licence is active, and the Free plugin reads it to decide which Pro surfaces to unlock. As far as your own code is concerned it is a read-only signal.
// Read it. True only when a valid RankReady Pro licence is active.
if ( function_exists( 'rnrd_is_pro' ) && rnrd_is_pro() ) {
// RankReady Pro is licensed on this site.
}
Do not filter this hook. Forcing it true does not give you RankReady Pro. The Pro plugin loads none of its engines without a valid licence, so nothing paid starts working; what you get instead is the Free plugin showing Pro-facing surfaces the site is not licensed for, and no support path when they behave oddly. If your companion code needs its own switch, use your own option or constant.
rnrd_loaded
An action that fires once RankReady has finished loading. This is the safe point at which to run code that depends on RankReady being present. Hooking earlier risks running before the plugin exists; hooking here means you never have to guess at plugin load order.
add_action( 'rnrd_loaded', function () {
// RankReady is loaded. Safe to register your own additions here.
add_filter( 'rankready_llms_txt_content', 'my_llms_txt_additions' );
} );
Because rnrd_loaded only fires when RankReady is active, an add-on that does all its work inside this action is inert when the plugin is deactivated. That is usually what you want: no fatal errors, no orphaned filters, nothing to clean up.
Further public hooks
These are shipped, supported and covered by the same promise as the four above: they will keep working, and their meaning will not change. They are grouped here rather than given their own sections because each one is a single value with an obvious job. Names beginning rankready_ and rnrd_ are both current; the difference is only which era of the plugin added them.
| Name | Type | Receives | Purpose |
|---|---|---|---|
rankready_exclude_from_llms |
Filter | bool $exclude, WP_Post $post |
Exclude a single post from llms.txt, llms-full.txt and the Markdown endpoints. |
rankready_allowed_post_types |
Filter | array $types |
Which post types RankReady treats as content for summaries, FAQ and Markdown. |
rankready_author_post_types |
Filter | array $types |
Which post types get the author box and Person schema. |
rankready_freshness_post_types |
Filter | array $types |
Which post types the Content Fresh tracker watches. |
rankready_inject_schema |
Filter | bool $inject |
Return false to stop RankReady injecting any JSON-LD on the current request. |
rankready_ai_schema_properties |
Filter | array $props, int $post_id |
Edit the AI schema properties before they are emitted. |
rankready_force_standalone_schema |
Filter | bool $force |
Emit RankReady schema as its own graph instead of merging into the SEO plugin output. |
rankready_force_accept_negotiation |
Filter | bool $force |
Keep Accept-header negotiation on even when Cloudflare APO is detected. Read the Cloudflare page before using it. |
rankready_md_cache_max_age |
Filter | int $seconds |
TTL for the Markdown endpoint cache headers. Defaults to one hour. A value of 0 or less sends no-store. |
rankready_okf_cache_max_age |
Filter | int $seconds |
TTL for the Open Knowledge Format cache headers. Defaults to one hour. |
rankready_md_watched_meta_keys |
Filter | string[] $keys, int $post_id |
Add exact post meta keys whose change should refresh that post’s cached Markdown. It adds to RankReady’s own keys and never removes one. Empty by default. |
rankready_md_cache_salt |
Filter | string $salt |
A salt for add-ons whose settings change every post’s Markdown. Changing it invalidates all cached Markdown on any cache backend. Letters and digits only. Empty by default. |
rankready_md_frontmatter |
Filter | string[] $extra, WP_Post $post |
Extra YAML frontmatter lines for a post. One complete key: value line per entry, already escaped by you. |
rankready_md_intro |
Filter | string[] $extra, WP_Post $post, bool $has_frontmatter |
Extra Markdown lines placed under the title, for sites that switched the frontmatter off. |
rankready_md_has_facts |
Filter | bool $has_facts, WP_Post $post |
Return true when your code writes real facts into a post’s Markdown although its body is empty, so the endpoint answers 200 instead of 404. False by default. |
rankready_exclude_commerce_pages |
Filter | bool $exclude, WP_Post $post |
Whether the WooCommerce cart, checkout and account pages are kept out of the Markdown output. True by default. |
rankready_cloudflare_apo_active |
Filter | bool $active |
Report Cloudflare APO as active when RankReady cannot detect it itself. False by default. |
rankready_purge_urls |
Filter | array $urls |
The full list of URLs RankReady asks the cache to purge. |
rankready_post_purge_urls |
Filter | array $urls, WP_Post $post |
The URLs purged for one post. Add your own derived URLs here. |
rankready_cloudflare_api_token |
Filter | string $token |
Supply the Cloudflare API token from your own store instead of the settings field. |
rankready_cloudflare_zone_id |
Filter | string $zone_id |
Supply the Cloudflare zone id from your own store instead of the settings field. |
rnrd_person_schema |
Filter | array $person, int $user_id, callable $meta |
The author Person node before it is emitted. This is the hook RankReady Pro itself uses to add the extended E-E-A-T fields. |
rnrd_template_redirect_priority |
Filter | int $priority |
The template_redirect priority RankReady claims. Defaults to -9999. Only change it to resolve a conflict with another plugin. |
rnrd_sslverify |
Filter | bool $verify |
SSL verification on RankReady outbound requests. Defaults to true. Only set false on a staging site with a self-signed certificate. |
rnrd_tips_webhook_url |
Filter | string $url |
Where the tips opt-in posts. Point it at your own endpoint to keep the address in house. |
rnrd_post_derived_output_purged |
Action | int $post_id |
Fires after RankReady has purged the derived output for a post. A safe point to purge your own copies. |
The rankready_md_* hooks arrived in 1.4.1 and are permanent from that version. Two of these change behaviour the plugin deliberately chose, so read the matching page before using them: rankready_force_accept_negotiation overrides the Cloudflare APO guard, and rnrd_sslverify turns off certificate checking on outbound requests.
Practical notes
- Do not hook what is not listed. The four above plus the reference table below are the documented surface. Anything else you find by reading the source is internal.
- Return the right type. Each filter expects the same type it was handed: a string stays a string, a boolean stays a boolean, an array stays an array. The Receives column above names it for every hook. Returning the wrong type gives you output you did not intend, and in the case of the post-type filters, silently no output at all.
- Test the endpoint, not the code. After changing a filter, fetch the URL and read what actually came back.
curl https://example.com/llms.txtis the check that matters. - Watch your caches. If a page cache or CDN sits in front of the site, clear it after changing filter output, otherwise you will be reading a stored copy of the old body.
These hooks change what RankReady publishes. They do not change what any AI system does with it. Publishing a file makes your content readable. Whether it is fetched, and what happens after, is outside the plugin’s control.