{"id":44,"date":"2026-09-09T04:25:59","date_gmt":"2026-09-09T04:25:59","guid":{"rendered":"https:\/\/nirohelp.com\/docs\/44\/filter-hooks\/"},"modified":"2026-09-09T04:25:59","modified_gmt":"2026-09-09T04:25:59","slug":"filter-hooks","status":"publish","type":"nirohelp_doc","link":"https:\/\/nirohelp.com\/docs\/developers\/filter-hooks\/","title":{"rendered":"Filter hooks reference"},"content":{"rendered":"<p>Filters change a value without replacing a function. NiroHelp exposes seven.<\/p>\n<h2><code>nirohelp_settings_menus<\/code><\/h2>\n<pre><code class=\"language-php\">apply_filters( 'nirohelp_settings_menus', $menus );\n<\/code><\/pre>\n<p>Receives the whole settings schema, keyed by tab slug. Add a tab, a section or<br \/>\na field; return the modified array. Both the classic Settings API screen and<br \/>\n<code>GET \/settings\/schema<\/code> (which the React screen renders from) read the filtered<br \/>\nresult.<\/p>\n<pre><code class=\"language-php\">add_filter( 'nirohelp_settings_menus', function ( $menus ) {\n    $menus['general']['sections']['branding'] = [\n        'label'  =&gt; 'Branding',\n        'desc'   =&gt; 'Custom branding overrides.',\n        'fields' =&gt; [\n            'logo_url' =&gt; [\n                'type'    =&gt; 'text',\n                'label'   =&gt; 'Logo URL',\n                'default' =&gt; '',\n            ],\n        ],\n    ];\n\n    return $menus;\n} );\n<\/code><\/pre>\n<p><strong>Do not call <code>Settings::get()<\/code> from inside this filter.<\/strong> <code>Settings::get()<\/code><br \/>\nreads the option and, on a miss, would call back into the schema \u2014 the<br \/>\nrecursion this design deliberately breaks. For the same reason, never call<br \/>\n<code>nirohelp_ticket_form_fields()<\/code> here.<\/p>\n<h2><code>nirohelp_product_post_types<\/code><\/h2>\n<pre><code class=\"language-php\">apply_filters( 'nirohelp_product_post_types', [] );\n<\/code><\/pre>\n<p><code>nirohelp_product<\/code> is registered against an empty post type list, and NiroHelp<br \/>\nappends docs and tickets through this filter. Append your own CPT to make<br \/>\nproducts assignable there too:<\/p>\n<pre><code class=\"language-php\">add_filter( 'nirohelp_product_post_types', function ( $types ) {\n    $types[] = 'release_note';\n    return $types;\n} );\n<\/code><\/pre>\n<h2><code>nirohelp_auto_status_transition_on_comment<\/code><\/h2>\n<pre><code class=\"language-php\">apply_filters( 'nirohelp_auto_status_transition_on_comment', true, $ticket, $is_client, $comment_id );\n<\/code><\/pre>\n<p>Default <code>true<\/code>. Return <code>false<\/code> to skip the automatic status flip (client reply<br \/>\n-&gt; Waiting, agent reply -&gt; In Progress) for one specific comment. The auto<br \/>\nresponder wraps its own <code>add_comment()<\/code> call in this so a bot reply does not<br \/>\nmove the ticket when <strong>Change ticket status<\/strong> is off.<\/p>\n<pre><code class=\"language-php\">add_filter( 'nirohelp_auto_status_transition_on_comment', function ( $allow, $ticket, $is_client, $comment_id ) {\n    \/\/ Don't auto-flip when an agent posts an internal note.\n    if ( ! $is_client &amp;&amp; get_comment_meta( $comment_id, 'is_internal', true ) ) {\n        return false;\n    }\n\n    return $allow;\n}, 10, 4 );\n<\/code><\/pre>\n<h2><code>nirohelp_get_posts<\/code><\/h2>\n<pre><code class=\"language-php\">apply_filters( 'nirohelp_get_posts', $posts, $args );\n<\/code><\/pre>\n<p>Wraps every call to <code>Helpers\\Utility::get_posts()<\/code>. Receives the result array<br \/>\nand the original arguments. Use it to reorder, to hide posts from some<br \/>\nreaders, or to attach extra data to the rows.<\/p>\n<h2><code>nirohelp_docs_api_max_per_page<\/code><\/h2>\n<pre><code class=\"language-php\">apply_filters( 'nirohelp_docs_api_max_per_page', 100 );\n<\/code><\/pre>\n<p>The ceiling the <code>count<\/code> parameter on <code>GET \/docs\/<\/code> and<br \/>\n<code>GET \/docs\/product\/{slug}<\/code> is clamped to. Raise it if you serve a large library<br \/>\nto a trusted consumer; it exists so an anonymous caller cannot ask for the<br \/>\nwhole table in one request.<\/p>\n<h2><code>nirohelp_can_register_users<\/code><\/h2>\n<pre><code class=\"language-php\">apply_filters( 'nirohelp_can_register_users', $allowed );\n<\/code><\/pre>\n<p>The single gate on every public path that would create a WordPress account \u2014<br \/>\n<code>nirohelp_get_user()<\/code>, <code>API\\Authenticate::login()<\/code> and <code>::wp_signup()<\/code>, and the<br \/>\nsign-up panel in the login form view. <code>$allowed<\/code> is already true when core&#8217;s<br \/>\n<code>users_can_register<\/code> is on <strong>or<\/strong> the plugin&#8217;s own <strong>Create accounts for new<br \/>\nclients<\/strong> box is ticked.<\/p>\n<pre><code class=\"language-php\">\/\/ Only ever create accounts for one domain.\nadd_filter( 'nirohelp_can_register_users', function ( $allowed ) {\n    $email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';\n    return $allowed &amp;&amp; str_ends_with( $email, '@example.com' );\n} );\n<\/code><\/pre>\n<p>Add this check to any new account-creating path you write.<\/p>\n<h2><code>nirohelp_encryption_seed<\/code><\/h2>\n<pre><code class=\"language-php\">apply_filters( 'nirohelp_encryption_seed', $seed );\n<\/code><\/pre>\n<p>The seed used to derive the key that encrypts stored credentials. Change it and<br \/>\nexisting ciphertext stops decrypting, so treat it as install-time<br \/>\nconfiguration, not a runtime knob.<\/p>\n<h2>Not a filter: <code>NIROHELP_CLOUD_SITE<\/code><\/h2>\n<p>Pointing NiroHelp at a different AI backend is a <strong>constant<\/strong>, not a filter.<br \/>\nDefine it in <code>wp-config.php<\/code>, before plugins load:<\/p>\n<pre><code class=\"language-php\">define( 'NIROHELP_CLOUD_SITE', 'https:\/\/rag.dev.example.com' );\n<\/code><\/pre>\n<p><code>nirohelp_cloud_site_url()<\/code> reads it and falls back to<br \/>\n<code>https:\/\/my.nirosuite.com<\/code>. There is no <code>nirohelp_cloud_site_url<\/code> filter and no<br \/>\n<code>nirohelp_rag_site_url<\/code> filter; if you find either named in older notes, it is<br \/>\nthis constant they meant.<\/p>\n<h2>Not a filter yet: <code>nirohelp_ai_upgrade_url<\/code><\/h2>\n<p><code>apply_filters( 'nirohelp_ai_upgrade_url', $url, $creds )<\/code> exists in<br \/>\n<code>Controllers\\Admin\\AI<\/code>, but it sits behind <code>if ( false &amp;&amp; 'free' === $plan )<\/code><br \/>\n\u2014 an intentionally dead branch waiting on paid plans. <strong>It never fires<br \/>\ntoday.<\/strong> Do not build on it.<\/p>\n<h2>A note on stability<\/h2>\n<p>The list is deliberately small. If you want to filter something that is not<br \/>\nexposed, prefer hooking the surrounding action and overwriting the value.<br \/>\nFilters that prove broadly useful get added in later releases; ad-hoc ones<br \/>\nadded to a fork break on update.<\/p>\n","protected":false},"author":1,"featured_media":0,"template":"","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}}},"nirohelp_product":[],"nirohelp_topic":[12],"class_list":["post-44","nirohelp_doc","type-nirohelp_doc","status-publish","hentry","nirohelp_topic-developers"],"_links":{"self":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_doc\/44","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_doc"}],"about":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/types\/nirohelp_doc"}],"author":[{"embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/users\/1"}],"wp:attachment":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/media?parent=44"}],"wp:term":[{"taxonomy":"nirohelp_product","embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_product?post=44"},{"taxonomy":"nirohelp_topic","embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_topic?post=44"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}