Hooks & filters

Every llmstxt_* hook is a stable extension point. Drop code into a theme’s functions.php, a must-use plugin, or a site-specific plugin - the plugin picks it up without any configuration changes.


Filters

llmstxt_template_llmstxt

Filters the site-index template string before generation runs. Use this to restructure the top-level llms.txt layout in code rather than through the admin UI.

ParameterTypeDescription
$templatestringThe full Mustache template for the site index

Returns: string - the modified template.

Example: Add a custom ## Podcasts section to the index when a podcast CPT is registered.

add_filter( 'llmstxt_template_llmstxt', function ( $template ) {
    if ( ! post_type_exists( 'podcast' ) ) {
        return $template;
    }

    $section = "\n## Podcasts\n{{podcasts_list}}\n";
    // Insert before the closing footer line
    return str_replace( '---', $section . '---', $template );
} );

llmstxt_template_{type}

Filters the per-item template for a specific content type. The {type} portion is replaced with the content type slug when the hook fires.

ParameterTypeDescription
$templatestringThe Mustache template for this content type
$postWP_PostThe post object being rendered (not available for term types)

Returns: string - the modified template.

Valid {type} values:

Hook nameApplies to
llmstxt_template_postBlog posts
llmstxt_template_pagePages
llmstxt_template_productWooCommerce products
llmstxt_template_product_catWooCommerce product categories
llmstxt_template_cpt_defaultAll other public custom post types
llmstxt_template_tax_defaultAll other public custom taxonomies

Example 1: Append a custom _review_score meta field to every post entry.

add_filter( 'llmstxt_template_post', function ( $template, $post ) {
    $score = get_post_meta( $post->ID, '_review_score', true );
    if ( $score ) {
        $template .= "\n**Review score:** {$score}/10\n";
    }
    return $template;
}, 10, 2 );

Example 2: Add warranty information to products in a specific category, using a conditional block so it only appears when the field is non-empty.

add_filter( 'llmstxt_template_product', function ( $template, $post ) {
    if ( ! has_term( 'electronics', 'product_cat', $post->ID ) ) {
        return $template;
    }

    $warranty_block = "\n{{#meta_warranty_years}}**Warranty:** {{meta_warranty_years}} years{{/meta_warranty_years}}\n";
    return $template . $warranty_block;
}, 10, 2 );

meta_warranty_years must be listed under Settings → Advanced → Custom Field Mapping (a Pro feature) for the variable to resolve.


Export visibility

Public llms.txt and .md output only includes posts that pass export visibility checks. Before any filter runs, the plugin enforces publish status, password protection, and scheduled-future dates.

Posts marked noindex in a supported SEO plugin are excluded automatically. Built-in support covers:

  • Yoast SEO
  • Rank Math
  • SEOPress
  • All in One SEO (AIOSEO)
  • Slim SEO

If SEO meta is missing or ambiguous, the post is included by default - the plugin does not over-exclude.

Use the filters below to add support for other SEO plugins, override a noindex decision, or apply custom editorial rules.

llmstxt_seo_noindex_check

Runs after built-in SEO plugin checks. Use this to extend noindex detection or override a result before the final export gate.

ParameterTypeDescription
$is_noindexbooltrue when any built-in SEO check flagged the post as noindex
$postWP_PostThe post being evaluated

Returns: bool - true to treat the post as noindex (exclude from public output), false to allow export.

When to use:

  • Add support for an SEO plugin not in the built-in list
  • Override a false positive (force-include a noindexed post in llms.txt)
  • Apply a custom editorial exclusion rule via post meta

Example 1 - add The SEO Framework support: The site runs The SEO Framework (not in the built-in list). Its noindex flag is stored in _genesis_noindex:

add_filter( 'llmstxt_seo_noindex_check', function ( bool $is_noindex, WP_Post $post ): bool {
    if ( $is_noindex ) {
        return true;
    }

    if ( function_exists( 'the_seo_framework' ) ) {
        $tsf_noindex = get_post_meta( $post->ID, '_genesis_noindex', true );
        if ( '1' === $tsf_noindex ) {
            return true;
        }
    }

    return false;
}, 10, 2 );

Example 2 - force-include a strategic landing page: A PPC landing page is set to noindex in Yoast (to keep it out of Google), but the marketing team still wants AI assistants to discover it via llms.txt. Return false for that specific post:

add_filter( 'llmstxt_seo_noindex_check', function ( bool $is_noindex, WP_Post $post ): bool {
    // Post ID 482: /spring-promo - noindex in Yoast, but include in AI output.
    if ( 482 === $post->ID ) {
        return false;
    }

    return $is_noindex;
}, 10, 2 );

This filter runs before llmstxt_post_is_publicly_exportable. Returning false here does not bypass publish status, password protection, or scheduled-future checks - those are always enforced.


llmstxt_post_is_publicly_exportable

Final gate for whether a post may appear in public llms.txt / .md output. Runs after all built-in checks and llmstxt_seo_noindex_check.

ParameterTypeDescription
$exportableboolDefault true when all prior checks passed
$postWP_PostThe post being evaluated

Returns: bool - false to exclude the post from public output.

When to use: Custom exclusion rules that are not SEO-related - membership tiers, editorial flags, CPT-specific policies.

Example - exclude posts flagged by editorial workflow: Editors mark sponsor content or thin pages with a custom _exclude_from_llms meta field:

add_filter( 'llmstxt_post_is_publicly_exportable', function ( bool $exportable, WP_Post $post ): bool {
    if ( get_post_meta( $post->ID, '_exclude_from_llms', true ) ) {
        return false;
    }

    return $exportable;
}, 10, 2 );

This filter only runs when the post has already passed publish, password, future-date, and SEO noindex checks. It cannot resurrect drafts or password-protected posts.


llmstxt_post_file_mode Pro

Controls whether an individual .md file is written to disk or served as a live WordPress endpoint, decided per-post. Overrides the site-wide and per-type setting from the Settings tab.

ParameterTypeDescription
$modestringCurrent mode: 'physical' or 'endpoint'
$post_idintThe post being evaluated
$typestringContent type slug (e.g. 'post', 'product')

Returns: string - 'physical' or 'endpoint'.

Example 1 - category-based routing: Serve products in the sale category as live endpoints (so price changes are always current), write everything else to disk.

add_filter( 'llmstxt_post_file_mode', function ( $mode, $post_id, $type ) {
    if ( 'product' === $type && has_term( 'sale', 'product_cat', $post_id ) ) {
        return 'endpoint';
    }
    return $mode;
}, 10, 3 );

Example 2 - opt-in via post meta: Let editors flag individual posts as live-endpoint by setting a _llmstxt_live custom field to 1.

add_filter( 'llmstxt_post_file_mode', function ( $mode, $post_id, $type ) {
    if ( get_post_meta( $post_id, '_llmstxt_live', true ) ) {
        return 'endpoint';
    }
    return $mode;
}, 10, 3 );

Example 3 - recency-based routing: Write posts older than 30 days to disk; serve recently updated content as live endpoints so edits appear immediately without a regeneration run.

add_filter( 'llmstxt_post_file_mode', function ( $mode, $post_id, $type ) {
    $modified = get_post_modified_time( 'U', true, $post_id );
    if ( $modified && ( time() - $modified ) < ( 30 * DAY_IN_SECONDS ) ) {
        return 'endpoint';
    }
    return $mode;
}, 10, 3 );

llmstxt_acf_repeater_output Pro

Filters the Markdown string produced when rendering an ACF repeater field referenced via {{acf_fieldname}} in a template. Requires ACF (free or Pro) to be active.

ParameterTypeDescription
$outputstringThe default Markdown output for the repeater
$rowsarrayAll row data from the repeater field
$sub_fieldsarrayACF sub-field definitions
$field_namestringThe ACF field name (e.g. 'faq_items')

Returns: string - the replacement Markdown.

Example: Render an faq_items repeater (with question and answer sub-fields) as a proper Q&A section instead of a flat list.

add_filter( 'llmstxt_acf_repeater_output', function ( $output, $rows, $sub_fields, $field_name ) {
    if ( 'faq_items' !== $field_name || empty( $rows ) ) {
        return $output;
    }

    $md = "## Frequently Asked Questions\n\n";
    foreach ( $rows as $row ) {
        $q = esc_html( $row['question'] ?? '' );
        $a = esc_html( $row['answer'] ?? '' );
        $md .= "**Q: {$q}**\n\n{$a}\n\n";
    }
    return $md;
}, 10, 4 );

llmstxt_acf_flexible_content_output Pro

Filters the Markdown string produced when rendering an ACF flexible content field referenced via {{acf_fieldname}} in a template. Requires ACF Pro to be active.

ParameterTypeDescription
$outputstringThe default Markdown output for the flexible content field
$layouts_valuearrayThe resolved layout data (layout name + field values)
$layouts_configarrayACF layout configuration definitions
$field_namestringThe ACF field name (e.g. 'page_sections')

Returns: string - the replacement Markdown.

Example: Apply per-layout Markdown formatting for a page_sections flexible content field - headings for hero layouts, blockquotes for pullquote layouts.

add_filter( 'llmstxt_acf_flexible_content_output', function ( $output, $layouts_value, $layouts_config, $field_name ) {
    if ( 'page_sections' !== $field_name ) {
        return $output;
    }

    $md = '';
    foreach ( $layouts_value as $layout ) {
        switch ( $layout['acf_fc_layout'] ) {
            case 'hero':
                $md .= "# {$layout['heading']}\n\n{$layout['body']}\n\n";
                break;
            case 'pullquote':
                $md .= "> {$layout['quote']}\n> - {$layout['attribution']}\n\n";
                break;
            default:
                $md .= "{$layout['text']}\n\n";
        }
    }
    return $md ?: $output;
}, 10, 4 );

Actions

Project LLMs.txt exposes two kinds of actions:

  • Regeneration triggers (llmstxt_regenerate*) - fire these from import tools, custom scripts, or other plugins to start regeneration.
  • Post-generation callbacks (llmstxt_scheduled_regeneration) - hook these to run work after regeneration completes.

Regeneration triggers

Fire any of these actions with do_action() to trigger regeneration from code. They respect the same debounce and lock logic as auto-regeneration - if generation is already in progress, the request queues gracefully via WP Cron rather than erroring.

ActionRegeneratesTier
llmstxt_regeneratellms.txt + llms-full.txtFree
llmstxt_regenerate_indexllms.txt onlyFree
llmstxt_regenerate_fullllms-full.txt onlyFree
llmstxt_regenerate_mdAll individual .md filesPro (no-op on free)
llmstxt_regenerate_postSingle post ($post_id)Free

llmstxt_regenerate

Regenerates both llms.txt and llms-full.txt. No arguments are passed.

Example: Trigger a full regeneration from a custom deploy script.

do_action( 'llmstxt_regenerate' );

llmstxt_regenerate_index

Regenerates llms.txt only. No arguments are passed.

Example: Rebuild the site index without re-scraping full page content.

do_action( 'llmstxt_regenerate_index' );

llmstxt_regenerate_full

Regenerates llms-full.txt only. No arguments are passed.

Example: Refresh the full-content file after a template change.

do_action( 'llmstxt_regenerate_full' );

llmstxt_regenerate_md Pro

Regenerates all individual .md files from cached post content. No arguments are passed. On the free tier this is a silent no-op.

Example: Rewrite physical .md files without rebuilding the index files.

do_action( 'llmstxt_regenerate_md' );

llmstxt_regenerate_post

Regenerates a single post by ID. Validates that $post_id is a positive integer and that the post exists; silently no-ops otherwise.

ParameterTypeDescription
$post_idintThe WordPress post ID to regenerate

Example: Regenerate one post after a programmatic update.

do_action( 'llmstxt_regenerate_post', 42 );

llmstxt_regenerate_post uses a per-post lock. If that post is already being generated, the request is queued via WP Cron instead of running immediately.


WP All Import

WP All Import (and similar bulk import tools) often bypass normal WordPress publish hooks, so auto-regeneration may not fire. Hook pmxi_after_xml_import and fire a regeneration action when the import finishes.

llmstxt_regenerate rebuilds both llms.txt and llms-full.txt. Swap in llmstxt_regenerate_index, llmstxt_regenerate_full, llmstxt_regenerate_md, or llmstxt_regenerate_post if you only need part of the output updated.

After any import completes

// Regenerate llms.txt after any WP All Import run completes
add_action( 'pmxi_after_xml_import', function( $import_id, $import ) {
    do_action( 'llmstxt_regenerate' );
}, 10, 2 );

After a specific import completes

// Regenerate llms.txt after specific WP All Import run completes
function after_xml_import( $import_id, $import ) {
    // Only run if import ID is 5.
    if ($import_id == 5) {
         do_action( 'llmstxt_regenerate' );
    }
}
add_action( 'pmxi_after_xml_import', 'after_xml_import', 10, 2 );

llmstxt_scheduled_regeneration Pro

Fires after the debounced background regeneration completes - 5 minutes after a publish, update, or delete triggered it. No arguments are passed.

Hook into this to run any post-generation work: purge CDN caches, notify external services, log regeneration events, or ping a URL so downstream systems know fresh content is available.

Example: Purge a Cloudflare cache zone after the files are regenerated.

add_action( 'llmstxt_scheduled_regeneration', function () {
    $zone_id   = defined( 'CF_ZONE_ID' )   ? CF_ZONE_ID   : '';
    $api_token = defined( 'CF_API_TOKEN' ) ? CF_API_TOKEN : '';

    if ( ! $zone_id || ! $api_token ) {
        return;
    }

    wp_remote_post(
        "https://api.cloudflare.com/client/v4/zones/{$zone_id}/purge_cache",
        [
            'headers' => [
                'Authorization' => "Bearer {$api_token}",
                'Content-Type'  => 'application/json',
            ],
            'body' => wp_json_encode( [
                'files' => [
                    home_url( '/llms.txt' ),
                    home_url( '/llms-full.txt' ),
                ],
            ] ),
        ]
    );
} );

Define CF_ZONE_ID and CF_API_TOKEN as constants in wp-config.php to keep credentials out of the database.