admin-ajax.php when core already ships better alternatives. Master five built-in patterns — WP HTTP API, programmatic post types, transient caching, safe save_post hooks, and custom REST endpoints — and you’ll write code that’s faster, cleaner, and survives core updates.WordPress powers over 40% of the web, yet many developers work around it rather than with it. The result is brittle code that breaks on updates and duplicates functionality WordPress already ships. Here are five patterns that experienced WordPress developers rely on daily.
1. Use wp_remote_get() Instead of Raw cURL
Raw cURL calls in WordPress plugins are a red flag. WordPress ships the WP HTTP API — a wrapper that handles proxies, SSL certificate verification, timeout configuration, and returns consistent WP_Error objects on failure.
// Bad
$ch = curl_init('https://api.example.com/data');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
// Good
$response = wp_remote_get('https://api.example.com/data');
if (is_wp_error($response)) {
// handle error cleanly
}
$body = wp_remote_retrieve_body($response);
The HTTP API also respects the http_request_args filter, so other plugins (like request logging or proxy middleware) can hook in without touching your code.
2. Register Custom Post Types in Code, Not Plugins
Reaching for a CPT plugin when you need a custom post type is over-engineering. register_post_type() is a single function call and gives you full control over labels, capabilities, REST API support, and rewrite rules.
add_action('init', function () {
register_post_type('product_review', [
'labels' => ['name' => 'Reviews', 'singular_name' => 'Review'],
'public' => true,
'show_in_rest' => true,
'supports' => ['title', 'editor', 'thumbnail'],
'rewrite' => ['slug' => 'reviews'],
]);
});
Putting this in a site-specific plugin (not your theme) keeps it active regardless of theme switches and avoids the content lock-in that comes from third-party CPT plugins.
3. Cache Expensive Queries with Transients
Every time a page loads, running a complex WP_Query or external API call burns time and money. WordPress transients are a zero-dependency caching layer backed by the options table by default — and automatically backed by Redis or Memcached if an object cache is installed.
function get_featured_posts() {
$cache_key = 'featured_posts_v1';
$posts = get_transient($cache_key);
if (false === $posts) {
$posts = new WP_Query(['category_name' => 'featured', 'posts_per_page' => 5]);
set_transient($cache_key, $posts, HOUR_IN_SECONDS);
}
return $posts;
}
Version the cache key (e.g. _v1) so you can bust it by incrementing without touching the database directly.
4. Avoid Infinite Loops in save_post
The save_post hook is one of the most common sources of production bugs. Calling wp_update_post() inside your hook fires save_post again — infinite loop, fatal error, corrupted post data.
add_action('save_post', function ($post_id) {
// Bail on autosaves and revisions
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
// Verify nonce
if (!isset($_POST['my_nonce']) || !wp_verify_nonce($_POST['my_nonce'], 'my_action')) {
return;
}
// Prevent re-entry
remove_action('save_post', __FUNCTION__);
wp_update_post(['ID' => $post_id, 'post_excerpt' => sanitize_text_field($_POST['my_field'])]);
add_action('save_post', __FUNCTION__);
});
Always check for revisions, verify a nonce, and use the remove_action / add_action pattern around any secondary wp_update_post() call.
5. Add Custom REST API Endpoints with register_rest_route()
If you are still using admin-ajax.php for custom server calls, stop. The WP REST API gives you namespaced, versioned, permission-checked endpoints with consistent JSON responses — and it works with any HTTP client.
add_action('rest_api_init', function () {
register_rest_route('myapp/v1', '/stats', [
'methods' => WP_REST_Server::READABLE,
'callback' => 'myapp_get_stats',
'permission_callback' => function () {
return current_user_can('read');
},
]);
});
function myapp_get_stats(WP_REST_Request $request) {
return new WP_REST_Response(['total_posts' => wp_count_posts()->publish], 200);
}
Your endpoint is now available at /wp-json/myapp/v1/stats. Add a nonce header (X-WP-Nonce) for authenticated requests from the frontend.
Wrapping Up
These five patterns — WP HTTP API, programmatic CPTs, transient caching, safe save_post hooks, and custom REST endpoints — are the difference between WordPress code that lasts and code that becomes technical debt. They are all built into core, well-documented, and tested across millions of sites. Use them.
