Submitting WordPress posts on publish
Published
WordPress can submit each new post for crawling the moment it goes live. A small plugin hooks the transition_post_status action, ignores everything except a change into the publish status, and posts the permalink to the IndexChex single-URL endpoint, POST /v2/google/url, with the API key read from wp-config.php.
The idea
New posts are the URLs most worth getting crawled quickly, and WordPress already knows exactly when each one goes live. Instead of exporting URLs to another tool, a few lines of PHP can send each permalink to an indexing API at publish time. This recipe uses the single-URL endpoint of IndexChex, the publisher of this handbook; the IndexChex software and API page summarises the service and its credit costs.
WordPress sites usually also expose an XML sitemap, and some hosts or plugins support IndexNow. A crawl submission is a separate step that asks for a Googlebot visit; it does not replace a sitemap, and it does not make Google index a page it would otherwise skip.
Choosing the hook
The WordPress developer reference documents transition_post_status as firing "when a post is transitioned from one status to another", with three arguments: the new status, the old status and the WP_Post object. Contributed notes on the same page point out that it also fires when a published post is merely updated, with old and new status both publish. The plugin therefore needs two checks: the new status must be publish, and the old one must not be.
The plugin
Save this as wp-content/plugins/indexchex-submit/indexchex-submit.php and activate it under Plugins.
<?php
/**
* Plugin Name: IndexChex submit on publish
* Description: Sends newly published post URLs to the IndexChex single-URL endpoint.
*/
defined( 'ABSPATH' ) || exit;
add_action( 'transition_post_status', 'ixc_submit_on_publish', 10, 3 );
function ixc_submit_on_publish( $new_status, $old_status, $post ) {
if ( 'publish' !== $new_status || 'publish' === $old_status ) {
return;
}
if ( 'post' !== $post->post_type || ! defined( 'INDEXCHEX_API_KEY' ) ) {
return;
}
$response = wp_remote_post(
'https://indexchex.com/api/v2/google/url',
array(
'headers' => array(
'Authorization' => INDEXCHEX_API_KEY,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
),
'body' => wp_json_encode( array( 'url' => get_permalink( $post ) ) ),
'timeout' => 15,
)
);
if ( is_wp_error( $response ) ) {
error_log( 'IndexChex submit failed: ' . $response->get_error_message() );
return;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! isset( $body['code'] ) || 0 !== $body['code'] ) {
error_log( 'IndexChex submit rejected: ' . wp_remote_retrieve_body( $response ) );
}
}
Add the key to wp-config.php, above the "stop editing" line, so it never sits in the database or in the plugin file:
define( 'INDEXCHEX_API_KEY', 'paste-your-key-here' );
What the endpoint returns
| HTTP status | Body | Meaning |
|---|---|---|
| 200 | {"code": 0} | Accepted as a standard submission |
| 402 | {"code": 1, "message": ...} | Not enough credits |
| 422 | {"code": 3, "message": ...} | Invalid request, for example a malformed URL |
| 429 | {"code": 2, "message": ...} | Too many requests in the current minute |
| 500 | {"code": 2} | The request could not be processed; try later |
The response shape follows SpeedyIndex's v2 schema, so an existing SpeedyIndex WordPress snippet usually needs only a new base URL and key; see migrating a SpeedyIndex integration. Each accepted post is a standard submission at 1 credit. Error handling beyond logging is covered in handling indexing API errors.
Adjusting the filter
- Pages and custom post types. Replace the
'post' !==check with! in_array( $post->post_type, array( 'post', 'page', 'product' ), true ). - Skip noindexed posts. If an SEO plugin marks a post noindex, submitting it wastes a credit, because Google will drop the page when it sees the rule. Read that plugin's post meta and return early.
- Private and password-protected posts.
privateis a separate status and never triggers the hook, but password-protected posts are published; checkpost_passwordif those should be skipped. - Staging sites. Leave
INDEXCHEX_API_KEYundefined on staging; the function returns before making any request.
Checking the result later
The single-URL endpoint does not schedule a re-check. To confirm the posts were indexed, collect a week's permalinks and run them through an index check, either with the Python script or a Google Sheets workflow. Sites without access to plugin code can achieve a similar flow from an RSS trigger with Zapier or Make.
Where this fits
A publish hook is the lightest form of automation: it covers one site, one event and one call. Agencies managing many sites often prefer a central script or a dedicated tool; what backlink indexer software is describes that range, and backlink indexer APIs compares the request formats different providers use. Field-level documentation is in the API reference.
FAQ
Why not hook save_post or publish_post?
save_post fires on every save, including autosaves and edits. transition_post_status passes both the old and new status, so the plugin can act only when a post moves into publish from another status.
Will scheduled posts be submitted?
Yes. When WordPress publishes a scheduled post, its status changes from future to publish, which fires the same hook at the scheduled time.
Does updating a published post submit it again?
No. An update leaves the status at publish, and the plugin returns early when the old status is already publish.
Does the request slow down publishing?
The call runs during the save request and waits up to the 15-second timeout set in the code. Lower the timeout, or move the call to a scheduled event, if editors notice a delay.
Terms used on this page
Sources
Cite this entry
IndexChex. (2026, October 8). Submitting WordPress posts on publish. backlinkindexersoftware.com. https://backlinkindexersoftware.com/wordpress-publish-hook/