IndexNow is a simple protocol that allows websites to instantly notify search engines about URL changes.
It's supported by Bing, Yandex, Naver, Seznam, and other search engines.
✓ Instant notification to multiple search engines
✓ Simple API key authentication (no OAuth required)
✓ Supports batch URL submissions (up to 10,000 URLs)
✓ Free to use with no quotas
✓ Fire-and-forget protocol (no status checking needed)
Configuration Required
Configure your IndexNow API key below to enable instant indexing with Bing and other search engines.
Your IndexNow API key is required to submit URLs for instant indexing. You can generate a random key or use your own (32+ character hexadecimal string).
After saving, a verification file will be automatically created at your site root.
Select which endpoint to use for submitting URLs. The IndexNow.org endpoint is recommended as it notifies multiple search engines at once.
Selected post types will be automatically submitted to IndexNow when published or updated.
Select which user roles can see and access this plugin's menu, settings, and options in the WordPress admin area.
30,
'max_memory_limit' => 256,
'log_batch_size' => 1000,
'action_scheduler_batches' => 1,
'otto_rate_limit' => 10,
'otto_cache_ttl' => 30,
'queue_cleanup_days' => 31
);
}
public function get_execution_setting($key, $default = null) {
$settings = get_option('metasync_execution_settings', array());
$defaults = $this->get_default_execution_settings();
if (empty($settings)) {
return isset($defaults[$key]) ? $defaults[$key] : $default;
}
return isset($settings[$key]) ? $settings[$key] : (isset($defaults[$key]) ? $defaults[$key] : $default);
}
public function get_all_execution_settings() {
$settings = get_option('metasync_execution_settings', array());
$defaults = $this->get_default_execution_settings();
return wp_parse_args($settings, $defaults);
}
public function can_change_memory_limit() {
$current_limit = ini_get('memory_limit');
$test_result = @ini_set('memory_limit', $current_limit);
return $test_result !== false;
}
public function get_server_limits() {
$max_execution_time = ini_get('max_execution_time');
$memory_limit = ini_get('memory_limit');
// Prefer the server-configured value: WordPress (wp_raise_memory_limit)
// or this plugin (apply_memory_limit) may have already changed the
// runtime limit via ini_set, so ini_get no longer reflects the server
// configuration. get_cfg_var reads the php.ini value untouched.
$config_memory_limit = get_cfg_var('memory_limit');
if ($config_memory_limit !== false && $config_memory_limit !== '') {
$memory_limit = $config_memory_limit;
}
$memory_limit_mb = $this->parse_memory_limit_to_mb($memory_limit);
$can_change_memory = $this->can_change_memory_limit();
$memory_limit_display = $memory_limit_mb == -1 ? 'Unlimited' : $memory_limit_mb . ' MB';
return array(
'max_execution_time' => $max_execution_time == -1 ? 'Unlimited' : $max_execution_time . ' seconds',
'memory_limit' => $memory_limit_display,
'max_execution_time_raw' => $max_execution_time,
'memory_limit_raw' => $memory_limit_mb,
'can_change_memory' => $can_change_memory
);
}
/**
* Allowed range for the Max Memory Limit setting: 64 MB up to the
* server-configured memory limit (null max when the server is unlimited).
*/
public function get_memory_limit_allowed_range() {
$server_limits = $this->get_server_limits();
$max = $server_limits['memory_limit_raw'] == -1 ? null : max(64, (int) $server_limits['memory_limit_raw']);
return array(
'min' => 64,
'max' => $max
);
}
public function apply_memory_limit() {
if (!$this->can_change_memory_limit()) {
return false;
}
$memory_limit_mb = $this->get_execution_setting('max_memory_limit');
$result = @ini_set('memory_limit', $memory_limit_mb . 'M');
return $result !== false;
}
public function parse_memory_limit_to_mb($memory_limit) {
if ($memory_limit == -1 || $memory_limit == '-1') {
return -1;
}
$memory_limit = trim($memory_limit);
$last = strtolower(substr($memory_limit, -1));
$value = (int) $memory_limit;
switch ($last) {
case 'g':
$value *= 1024;
break;
case 'm':
break;
case 'k':
$value /= 1024;
break;
}
return $value;
}
public function render_execution_settings_section() {
$settings = $this->get_all_execution_settings();
$server_limits = $this->get_server_limits();
$memory_range = $this->get_memory_limit_allowed_range();
$warnings = array();
if ((int)$server_limits['max_execution_time_raw'] !== -1 && (int)$settings['max_execution_time'] > (int)$server_limits['max_execution_time_raw']) {
$warnings['max_execution_time'] = true;
}
if ($server_limits['memory_limit_raw'] != -1 && $settings['max_memory_limit'] > $server_limits['memory_limit_raw']) {
$warnings['max_memory_limit'] = true;
}
?>
Configure resource limits and execution parameters for plugin operations. Adjust these settings based on your server capabilities.
sprintf('Connect your WordPress site to your %1$s dashboard to retrieve your API key and %2$s UUID. This does not create a WordPress login session.', $plugin_name, $otto_name),
'apikey' => sprintf('Auto-generated authentication token used for secure API communication between your WordPress site and %s services. You can refresh this token if needed for security purposes.', $plugin_name),
'otto_pixel_uuid' => sprintf('Your unique %s tracking pixel identifier. This UUID is used to track %s modifications and analytics on your website pages.', $otto_name, $otto_name),
'otto_disable_on_loggedin' => sprintf('Disable %s modifications when you are logged in to WordPress. This allows you to see and edit the original content without %s\'s enhancements during editing sessions.', $otto_name, $otto_name),
'otto_disable_preview_button' => sprintf('Hide the %s frontend toolbar that displays the status indicator, preview button, and debug button. Enable this for a cleaner frontend experience.', $otto_name),
'otto_wp_rocket_compat' => sprintf('WP Rocket Compatibility Mode: Controls how %s interacts with WP Rocket. "Auto" (recommended) allows both to work together by avoiding DONOTCACHEPAGE constant unless necessary for Brizy pages or SG Optimizer conflicts. This ensures WP Rocket\'s JavaScript delay and optimization features continue working.', $otto_name),
'otto_disable_for_bots' => sprintf('Enable bot detection to automatically skip %s processing for search engine crawlers, SEO tools, and other bots. This reduces unnecessary API calls and improves performance.', $otto_name),
'otto_bot_whitelist' => sprintf('Enter bot names or user-agent patterns (one per line) that should always be processed by %s, even when "Disable for Bots" is enabled. For example: Googlebot, Bingbot. This allows you to ensure specific search engines always see your optimized content.', $otto_name),
'otto_bot_blacklist' => sprintf('Enter bot names or user-agent patterns (one per line) that should always be blocked from %s processing, regardless of other settings. For example: BadBot, MaliciousCrawler. Use this to exclude problematic crawlers or unwanted traffic sources.', $otto_name),
'otto_bot_statistics_link' => 'View detailed bot detection statistics including total hits, API calls saved, breakdown by bot type, and unique bot entries with hit counts.',
'disable_common_robots_metabox' => 'Hide the Common Robots meta box from post and page edit screens. This removes the robots meta tag controls (index/noindex, follow/nofollow) from the editor interface.',
'disable_advance_robots_metabox' => 'Hide the Advanced Robots meta box from post and page edit screens. This removes advanced robots directives like max-snippet, max-image-preview, and max-video-preview settings.',
'disable_redirection_metabox' => 'Hide the Redirection meta box from post and page edit screens. This removes the URL redirect configuration options from the editor interface.',
'disable_canonical_metabox' => 'Hide the Canonical URL meta box from post and page edit screens. This removes the canonical URL override field from the editor interface.',
'disable_social_opengraph_metabox' => 'Hide the Social Media & Open Graph meta box from post and page edit screens. This removes Facebook, Twitter, and other social media meta tag controls from the editor.',
'disable_schema_markup_metabox' => 'Hide the Schema Markup meta box from post and page edit screens. This removes the structured data (Article, FAQ, Product, Recipe, etc.) configuration from the editor interface.',
'disable_seo_metabox' => 'Hide the SEO Title & Meta Description meta box from post and page edit screens. This removes the Classic editor SEO fields (the Gutenberg sidebar is unaffected).',
'open_external_links' => sprintf('Automatically add target="_blank" attribute to external links appearing in your posts, pages, and other post types when rendered by %s.', $otto_name),
'content_genius_sync_roles' => 'Select which WordPress user roles should be synchronized with Content Genius. This determines which users will have their profiles and permissions synced for content collaboration.',
'permalink_structure' => sprintf('Displays your current WordPress permalink structure. %s works best with pretty permalinks (not "Plain"). If you see a warning, visit Settings > Permalinks to change your structure.', $plugin_name),
'hide_dashboard_framework' => sprintf('Hide the main %s dashboard from the WordPress admin menu. This is useful if you want to reduce menu clutter but still keep the plugin active.', $plugin_name),
'show_admin_bar_status' => sprintf('Display the %s status indicator in the WordPress admin bar at the top of your screen. This provides quick visibility of plugin status and key metrics.', $plugin_name),
'enable_auto_updates' => sprintf('Allow WordPress to automatically update the %s plugin when new versions are released. Recommended for security patches, but you may prefer manual updates for major versions.', $plugin_name),
'import_external_data' => sprintf('Import your existing SEO settings and metadata from other popular SEO plugins like Yoast, Rank Math, or All in One SEO. This makes migration to %s seamless without losing your SEO data.', $plugin_name),
'import_seo_metadata' => sprintf('Migrate your existing SEO titles and meta descriptions from Yoast, Rank Math, or All in One SEO. This one-click import preserves your search rankings by copying your optimized meta data to %s, even if the source plugin is deactivated.', $plugin_name),
'default_page_builder' => 'Choose how Content Genius stores synced articles. "Gutenberg" works with all themes and page builders. Selecting a specific builder converts content to that builder\'s widget format, which inherits the builder\'s global typography and layout styles.',
'og_image_dimensions' => 'Adds your share image\'s width, height, and file type to the Open Graph tags so Facebook, LinkedIn, and others render your preview instantly at the right size instead of guessing. Leave on unless another plugin already outputs these.',
'article_timestamps' => 'Tells social platforms and search engines when each post was first published and last updated, so shares and rich results show accurate dates. Safe to leave enabled.',
'article_author' => 'Adds a link to the author\'s profile or archive page to each post\'s social tags, so shared articles can show author attribution. You can override the author URL per-post. Turn off if you\'d rather not expose author pages.',
'article_section' => 'Labels each post with its main topic (its primary category) in the social tags, helping platforms categorize your content. Falls back to the first category if no primary one is set.',
'article_tags' => 'Adds each of your WordPress post tags as a separate social "tag" so platforms understand the topics a post covers. Turn off if you don\'t want your tags exposed in share metadata.',
'twitter_image_alt' => 'Adds descriptive alt text to the image shown when your page is shared on X/Twitter, improving accessibility for screen-reader users. Uses your saved alt text or the image\'s own alt attribute.'
);
}
public function get_field_section($field_id) {
$sections = $this->get_accordion_sections_config();
foreach ($sections as $section_key => $section_data) {
if (in_array($field_id, $section_data['fields'])) {
return $section_key;
}
}
return null;
}
public function render_accordion_sections($page) {
global $wp_settings_sections, $wp_settings_fields;
if (!isset($wp_settings_fields[$page])) {
return;
}
$sections_config = $this->get_accordion_sections_config();
echo '
';
}
public function render_llms_txt_section() {
global $wp_settings_fields;
$page = Metasync_Admin::$page_slug . '_general';
$section = Metasync_Admin::SECTION_LLMS_TXT;
if (empty($wp_settings_fields[$page][$section])) {
return;
}
echo '
';
$llms_tooltips = array(
'llms_txt_enabled' => 'Publishes a file at yoursite.com/llms.txt listing your key pages so AI assistants like ChatGPT and Perplexity can find and cite your content. Safe to leave off if you don\'t want AI crawlers guided to your pages.',
'llms_full_enabled' => 'Also publishes a larger file with the full text of your posts (not just links). This exposes complete article content and can be large — keep the post limit low on smaller hosting plans.',
);
foreach ($wp_settings_fields[$page][$section] as $field) {
echo '
', $status_message, $refresh_help);
}
public function linkgraph_token_callback()
{
printf(
'',
isset(Metasync::get_option('general')['linkgraph_token']) ? esc_attr(Metasync::get_option('general')['linkgraph_token']) : ''
);
printf(
'',
isset(Metasync::get_option('general')['linkgraph_customer_id']) ? esc_attr(Metasync::get_option('general')['linkgraph_customer_id']) : ''
);
?>
diff($ago);
$string = [
'y' => 'year',
'm' => 'month',
'w' => 'week',
'd' => 'day',
'h' => 'hour',
'i' => 'minute',
's' => 'second',
];
foreach ($string as $k => &$v) {
if (isset($diff->$k) && $diff->$k) {
$v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
} else {
unset($string[$k]);
}
}
if (!$full) $string = array_slice($string, 0, 1);
return $string ? implode(', ', $string) . ' ago' : 'just now';
}
public function searchatlas_api_key_callback()
{
// Whether a key is stored at rest (encrypted or legacy plaintext).
$stored_api_key = Metasync::get_option('general')['searchatlas_api_key'] ?? '';
$has_api_key = ($stored_api_key !== '');
// Decrypted key for masking only. Never rendered in cleartext.
// false => an encrypted key exists but cannot be decrypted (salts changed).
$decrypted_api_key = Metasync::get_searchatlas_api_key();
$decrypt_failed = ($decrypted_api_key === false);
// Build a masked representation: bullets + last 4 chars. The cleartext
// key is never emitted into the page.
$masked_api_key = '';
if (!$decrypt_failed && $decrypted_api_key !== '') {
$masked_api_key = str_repeat('*', 8) . substr($decrypted_api_key, -4);
}
$otto_uuid = isset(Metasync::get_option('general')['otto_pixel_uuid']) ? Metasync::get_option('general')['otto_pixel_uuid'] : '';
$has_otto_uuid = !empty($otto_uuid);
$is_fully_connected = $this->admin_instance->is_heartbeat_connected();
printf('
');
printf('
');
printf('One-Click Authentication');
printf('
');
printf('
');
if ($is_fully_connected) {
printf('Your %s account is fully synced with active heartbeat API. You can re-authenticate to refresh your connection or connect a different account.', esc_html(Metasync::get_effective_plugin_name()));
} elseif ($has_api_key && !$has_otto_uuid) {
printf('Your %s API key is configured, but %s UUID is missing. Please re-authenticate to complete the setup.', esc_html(Metasync::get_effective_plugin_name()), esc_html(Metasync::get_whitelabel_otto_name()));
} else {
printf('Connect your %s account with one click. This will automatically configure your API key and %s UUID below, enabling all plugin features.', esc_html(Metasync::get_effective_plugin_name()), esc_html(Metasync::get_whitelabel_otto_name()));
}
printf('
');
?>
AI-Powered SEO Automation: By authenticating, you authorize Brain (our AI assistant) to access your WordPress admin capabilities through the Model Context Protocol (MCP). This enables intelligent automation for SEO optimizations, content enhancements, and performance improvements.
');
printf('');
if ($has_api_key) {
printf('');
}
printf('
');
printf('• Make sure you have a %s account before connecting ', esc_html(Metasync::get_effective_plugin_name()));
printf('• The authentication window will open in a popup - please allow popups ');
printf('• The process typically takes 15-30 seconds to complete ');
printf('• Your API key will be stored securely and shown masked below ');
printf('• If you encounter issues, try disabling ad blockers temporarily ');
printf('• Contact %s if you need assistance', Metasync::SUPPORT_EMAIL, Metasync::SUPPORT_EMAIL);
printf('
');
printf('');
printf('
');
printf('');
printf('
');
printf('');
// Read-only masked display. The API key is managed via one-click
// authentication only; the cleartext key is never rendered into the
// page HTML (View Source / DOM). No form input is emitted, so saving
// settings cannot overwrite or expose the stored key.
$display_has_key = (!$decrypt_failed && $masked_api_key !== '') ? '1' : '0';
printf(
'
',
esc_attr($display_has_key)
);
if ($decrypt_failed) {
printf('Stored API key could not be read. Please re-authenticate above.');
} elseif ($masked_api_key !== '') {
echo esc_html($masked_api_key);
} else {
printf('Not configured');
}
printf('
');
printf('
');
if ($decrypt_failed) {
printf('Your stored API key could not be decrypted (your site security keys may have changed). Please re-authenticate above to reconnect.');
} elseif ($is_fully_connected) {
printf('Your %s API key is stored securely and used for communication with the platform. Use the authentication button above to refresh or change accounts.', esc_html(Metasync::get_effective_plugin_name()));
} elseif ($has_api_key && !$has_otto_uuid) {
printf('Your API key is configured but %s UUID is missing. Re-authenticate above to complete the setup and enable dashboard access.', esc_html(Metasync::get_whitelabel_otto_name()));
} else {
printf('Use the authentication button above to securely connect your account. Your API key is stored encrypted and shown masked here.');
}
printf('
',
esc_attr($timestamp),
$this->time_elapsed_string($timestamp)
);
}
}
public function bing_site_verification_callback()
{
printf(
'',
isset(Metasync::get_option('searchengines')['bing_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['bing_site_verification']) : ''
);
printf(' Enter Bing Webmaster Tools verification code: ');
printf(' Get from here ');
}
public function yandex_site_verification_callback()
{
printf(
'',
isset(Metasync::get_option('searchengines')['yandex_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['yandex_site_verification']) : ''
);
printf(' Enter Yandex verification code: ');
printf(' Get from here ');
}
public function google_site_verification_callback()
{
printf(
'',
isset(Metasync::get_option('searchengines')['google_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['google_site_verification']) : ''
);
printf(' Enter Google Search Console verification code: ');
printf(' Get from here ');
}
public function pinterest_site_verification_callback()
{
printf(
'',
isset(Metasync::get_option('searchengines')['pinterest_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['pinterest_site_verification']) : ''
);
printf(' Enter Pinterest verification code: ');
printf(' Get from here ');
}
public function local_seo_person_organization_callback()
{
$person_organization = Metasync::get_option('localseo')['local_seo_person_organization'] ?? '';
?>
Choose whether the site represents a person or an organization. ');
}
public function local_seo_name_callback()
{
printf(
'',
isset(Metasync::get_option('localseo')['local_seo_name']) ? esc_attr(Metasync::get_option('localseo')['local_seo_name']) : get_bloginfo()
);
printf(' Your name or company name ');
}
public function local_seo_logo_callback()
{
$local_seo_logo = Metasync::get_option('localseo')['local_seo_logo'] ?? '';
printf(
'',
isset(Metasync::get_option('localseo')['local_seo_logo']) ? esc_attr(Metasync::get_option('localseo')['local_seo_logo']) : ''
);
printf(' ');
printf('
Min Size: 160Χ90px, Max Size: 1920X1080px. A squared image is preferred by the search engines.
');
printf('');
printf('', wp_get_attachment_image_src($local_seo_logo, 'medium')[0] ?? '');
$button_type = 'hidden';
if ($local_seo_logo) {
$button_type = 'button';
}
printf('', $button_type);
printf('');
}
public function local_seo_url_callback()
{
printf(
'',
isset(Metasync::get_option('localseo')['local_seo_url']) ? esc_attr(Metasync::get_option('localseo')['local_seo_url']) : home_url()
);
printf(' URL of the item. ');
}
public function local_seo_email_callback()
{
printf(
'',
isset(Metasync::get_option('localseo')['local_seo_email']) ? esc_attr(Metasync::get_option('localseo')['local_seo_email']) : ''
);
printf(' Search engines display your email address. ');
}
public function local_seo_phone_callback()
{
printf(
'',
isset(Metasync::get_option('localseo')['local_seo_phone']) ? esc_attr(Metasync::get_option('localseo')['local_seo_phone']) : ''
);
printf(' Search engines may prominently display your contact phone number for mobile users. ');
}
public function local_seo_address_callback()
{
printf(
' ',
isset(Metasync::get_option('localseo')['address']['street']) ? esc_attr(Metasync::get_option('localseo')['address']['street']) : ''
);
printf(
' ',
isset(Metasync::get_option('localseo')['address']['locality']) ? esc_attr(Metasync::get_option('localseo')['address']['locality']) : ''
);
printf(
' ',
isset(Metasync::get_option('localseo')['address']['region']) ? esc_attr(Metasync::get_option('localseo')['address']['region']) : ''
);
printf(
' ',
isset(Metasync::get_option('localseo')['address']['postalcode']) ? esc_attr(Metasync::get_option('localseo')['address']['postalcode']) : ''
);
printf(
' ',
isset(Metasync::get_option('localseo')['address']['country']) ? esc_attr(Metasync::get_option('localseo')['address']['country']) : ''
);
}
public function local_seo_business_type_callback()
{
$types = self::get_business_types();
sort($types);
$business_type = Metasync::get_option('localseo')['local_seo_business_type'] ?? '';
?>
$day_time) {
?>
', count($opening_days));
printf(' ');
printf(' Select opening hours. You can add multiple sets if you have different opening or closing hours on some days or if you have a mid-day break. Times are specified using 24:00 time. ');
}
public function local_seo_phone_numbers_callback()
{
$number_types = ['Customer Service', 'Technical Support', 'Billing Support', 'Bill Payment', 'Sales', 'Reservations', 'Credit Card Support', 'Emergency', 'Baggage Tracking', 'Roadside Assistance', 'Package Tracking'];
$types = isset(Metasync::get_option('localseo')['phonetype']) ? Metasync::get_option('localseo')['phonetype'] : '';
$numbers = isset(Metasync::get_option('localseo')['phonenumber']) ? Metasync::get_option('localseo')['phonenumber'] : '';
?>
$phone_number) {
?>
', count($phone_numbers));
printf(' ');
printf(' Search engines may prominently display your contact phone number for mobile users. ');
}
public function local_seo_price_range_callback()
{
printf(
'',
isset(Metasync::get_option('localseo')['local_seo_price_range']) ? esc_attr(Metasync::get_option('localseo')['local_seo_price_range']) : ''
);
printf(' The price range of the business, for example $$$. ');
}
public function local_seo_about_page_callback()
{
?>
Search engines tag your about us page. ');
}
public function local_seo_contact_page_callback()
{
?>
Search engines tag your contact page. ');
}
public function local_seo_map_key_callback()
{
printf(
'',
isset(Metasync::get_option('localseo')['local_seo_map_key']) ? esc_attr(Metasync::get_option('localseo')['local_seo_map_key']) : ''
);
printf(' An API Key is required to display embedded Google Maps on your site. ');
}
public function local_seo_geo_coordinates_callback()
{
printf(
'',
isset(Metasync::get_option('localseo')['local_seo_geo_coordinates']) ? esc_attr(Metasync::get_option('localseo')['local_seo_geo_coordinates']) : ''
);
printf(' Latitude and longitude values separated by comma. ');
}
public function header_snippets_callback()
{
printf(
'',
isset(Metasync::get_option('codesnippets')['header_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['header_snippet']) : ''
);
}
public function footer_snippets_callback()
{
printf(
'',
isset(Metasync::get_option('codesnippets')['footer_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['footer_snippet']) : ''
);
}
public function common_robot_meta_tags_callback()
{
$common_robots_meta = Metasync::get_option('common_robots_meta') ?? Metasync::get_option('common_robots_mata') ?? '';
?>