9 min read

10 Hidden Gems in WordPress Every Web Developer Should Know

Shashank Dubey
Content & Marketing, Wbcom Designs · Published Jul 9, 2024 · Updated Aug 29, 2026
WordPress Experts by Wbcom Designs - galaxy background with handwriting text

Most WordPress developers use perhaps a fifth of what core ships. The rest sits in well-documented but rarely visited corners: APIs that remove the need for a plugin, functions that replace thirty lines of custom code, and tooling that turns a deployment into one command. This list covers ten of those features, with working snippets and the situations where we reach for each one on client projects.

Everything here is in WordPress core as of 7.0 (released May 2026), and several items (the Abilities API, Block Bindings, the Interactivity API) only became practical in the last year or so. Nothing requires a page builder or a premium plugin.

Hidden gems in WordPress: most developers use a fifth of core, and these ten features need no premium plugin

1. Custom post types with REST and block editor support

Custom post types are the oldest item on this list and still the most under-configured. The mistake we see weekly is registering a post type without show_in_rest, which silently gives you the classic editor, no block editor, no REST endpoint, and no access from the Site Editor’s query loop.

add_action( 'init', function () {
    register_post_type( 'course_review', array(
        'labels'       => array( 'name' => 'Course Reviews', 'singular_name' => 'Course Review' ),
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,
        'rest_base'    => 'course-reviews',
        'supports'     => array( 'title', 'editor', 'excerpt', 'author', 'custom-fields' ),
        'template'     => array(
            array( 'core/heading', array( 'level' => 2, 'placeholder' => 'Headline' ) ),
            array( 'core/paragraph', array( 'placeholder' => 'Your review' ) ),
        ),
        'template_lock' => 'insert',
    ) );
} );

Two details that do real work: template pre-fills new posts with a block structure so contributors do not start from a blank page, and custom-fields in supports is required for Block Bindings (item 6) to see the post’s meta. Register on init, never on plugins_loaded, or rewrite rules will not flush correctly.

2. The REST API, including the parts you write yourself

The REST API has been in core since 4.7, and custom post types with show_in_rest get CRUD endpoints for free under /wp-json/wp/v2/. What fewer developers do is register their own routes for the awkward cases: an aggregated dashboard payload, a webhook receiver, a form handler that should not be a page.

add_action( 'rest_api_init', function () {
    register_rest_route( 'wbcom/v1', '/member-stats/(?P<id>\d+)', array(
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => 'wbcom_member_stats',
        'permission_callback' => function ( WP_REST_Request $request ) {
            return current_user_can( 'read' ) && get_current_user_id() === (int) $request['id'];
        },
        'args' => array(
            'id' => array( 'validate_callback' => 'is_numeric', 'sanitize_callback' => 'absint' ),
        ),
    ) );
} );

Since WordPress 5.5 a route without a permission_callback throws a notice, and for good reason. Write the permission check first, then the callback. For anything that changes data, set methods to WP_REST_Server::CREATABLE and send the X-WP-Nonce header from wp.apiFetch so cookie authentication works.

3. The Abilities API (6.9) and what it means for AI tooling

The Abilities API, a hidden gem in WordPress 6.9, registers abilities AI agents can list and run over REST

The Abilities API landed in WordPress 6.9 (November 2025) and is the piece most developers have not looked at yet. It is a central registry where core, plugins and themes describe what they can do in a machine-readable form: a label, a description, an input schema, an output schema, a permission callback and an execute callback. Anything registered there can be listed at GET /wp-json/wp-abilities/v1/abilities and run at /wp-json/wp-abilities/v1/{namespace}/{ability}/run.

add_action( 'wp_abilities_api_init', function () {
    wp_register_ability( 'wbcom/pending-reviews', array(
        'label'               => 'Count pending course reviews',
        'description'         => 'Returns the number of course reviews awaiting moderation.',
        'category'            => 'site',
        'output_schema'       => array( 'type' => 'integer' ),
        'permission_callback' => fn() => current_user_can( 'moderate_comments' ),
        'execute_callback'    => fn() => (int) wp_count_posts( 'course_review' )->pending,
        'meta'                => array( 'show_in_rest' => true ),
    ) );
} );

The point of this over a plain REST route is discoverability. With the official MCP Adapter plugin installed, an AI agent (Claude, ChatGPT, or the WP AI Client that shipped in 7.0) can list your abilities and call them with proper permission checks, without you writing a bespoke integration for each assistant. We have started exposing moderation and reporting tasks on community sites this way. Read the Abilities API introduction on the Developer Blog before you design any new admin tooling; it changes where the logic should live.

4. Transients and the object cache

Any remote API call, expensive query or computed list that is the same for every visitor belongs in a transient. The pattern is short and the performance gain is usually the largest single win on a slow site.

function wbcom_get_exchange_rates() {
    $rates = get_transient( 'wbcom_rates' );
    if ( false === $rates ) {
        $response = wp_remote_get( 'https://api.example.com/rates', array( 'timeout' => 5 ) );
        if ( is_wp_error( $response ) ) {
            return array();
        }
        $rates = json_decode( wp_remote_retrieve_body( $response ), true );
        set_transient( 'wbcom_rates', $rates, 6 * HOUR_IN_SECONDS );
    }
    return $rates;
}

Transients live in the options table until a persistent object cache (Redis or Memcached) is present, at which point the same code transparently uses memory. That is the reason to prefer them over your own caching table: the upgrade path is “install a drop-in”, not “rewrite the plugin”. One caveat: never store anything larger than a few hundred kilobytes, and never rely on a transient existing, because the cache can be flushed at any moment.

5. WP-CLI for everything repetitive

WP-CLI ships with every reputable host and with Local, DDEV and wp-env. Developers who treat it as “the thing that installs plugins” miss most of the value. A few commands we run on almost every engagement:

  • wp search-replace 'https://old.example' 'https://new.example' --all-tables --precise for migrations, which handles serialised data correctly.
  • wp plugin list --update=available --format=csv piped into a report before maintenance windows.
  • wp cron event list and wp cron event run --due-now when a scheduled job “stopped working”.
  • wp profile stage --all (from the profile command package) to find which hook is eating load time.
  • wp user create with --role and --send-email=false for staging logins.

Writing your own commands takes one function. Register a class with WP_CLI::add_command( 'wbcom', 'Wbcom_CLI' ) and each public method becomes a subcommand, with PHPDoc comments turning into help text. We ship a CLI command with every plugin that imports or exports data, because support requests almost always involve “can you re-run the import”.

6. Block Bindings: dynamic content without a custom block

Block Bindings, stable since 6.5 and with a UI in the editor since 6.7, let you connect a core block’s attribute (a paragraph’s content, an image’s URL, a button’s link) to a source such as post meta. The result is a dynamic template without writing a block, a shortcode or a PHP template.

<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"course_duration"}}}}} -->
<p>Placeholder</p>
<!-- /wp:paragraph -->

For the binding to work, the meta key must be registered with register_post_meta(), show_in_rest => true and the post type must support custom-fields. You can also register custom sources with register_block_bindings_source() for values that come from anywhere else (a user field, a remote API, an option). On LearnDash and directory sites this has replaced a surprising number of small ACF blocks.

7. The Interactivity API for front-end behaviour

The Interactivity API (core since 6.5, powering the Query Loop’s instant pagination and the image Lightbox) is a small, declarative way to add front-end state to blocks with HTML directives instead of a React bundle. A toggle, a tab set, a filterable list or a “load more” button can be written in a few lines of PHP-rendered markup plus a tiny store in JavaScript.

<div data-wp-interactive="wbcom/faq" <?php echo get_block_wrapper_attributes(); ?>
     <?php echo wp_interactivity_data_wp_context( array( 'open' => false ) ); ?>>
  <button data-wp-on--click="actions.toggle" data-wp-bind--aria-expanded="context.open">Question</button>
  <p data-wp-bind--hidden="!context.open">Answer</p>
</div>

The store is registered with store( 'wbcom/faq', { actions: { toggle() { const c = getContext(); c.open = !c.open; } } } ) in a script module. It renders server-side, so there is no flash of empty content, and several blocks on a page share one runtime. Use it whenever the alternative was “enqueue jQuery and write a click handler”.

8. theme.json and style variations

Even on a classic theme, a theme.json file in the theme root lets you define the colour palette, font sizes, spacing scale and block-level defaults that the editor exposes, and it generates the CSS custom properties for you (--wp--preset--color--primary and friends). Setting settings.color.custom to false keeps clients on the palette you designed.

Drop additional JSON files into a styles/ directory and each becomes a style variation selectable under Appearance → Editor → Styles. A dark mode, a seasonal palette or a white-label variant for a client’s sub-brand becomes a 40-line file rather than a CSS fork. Our BuddyX theme moved its entire colour system onto this token approach, which is why a child theme can restyle the whole community with no Kirki or Customizer code.

9. Application passwords and the HTTP API

Application passwords (core since 5.6) give any user a revocable credential for REST access without exposing their real password. Create one under Users → Profile → Application Passwords, then authenticate with HTTP Basic auth over HTTPS. This is how headless front-ends, deployment scripts, and the MCP servers we run against client sites authenticate, and it is why “install a JWT plugin” is rarely the right answer any more.

On the other side of the connection, wp_remote_get(), wp_remote_post() and wp_safe_remote_get() wrap cURL with sensible defaults, proxy support and filters (http_request_args, pre_http_request) that let you mock a remote API in tests. Always set a timeout; the default of five seconds will block a page render if the remote service hangs.

10. Dependency-aware asset loading and script modules

Two small things in the enqueue system save hours. First, wp_enqueue_script() accepts an $args array (since 6.3) with 'strategy' => 'defer' or 'async', which fixes most “render-blocking JavaScript” Lighthouse warnings without a performance plugin. Second, wp_enqueue_script_module() (since 6.5) loads native ES modules with an import map, which is what the Interactivity API uses and what you should use for any new front-end code.

wp_enqueue_script( 'wbcom-tracking', plugins_url( 'js/tracking.js', __FILE__ ), array(), '1.4.0', array( 'strategy' => 'defer', 'in_footer' => true ) );

wp_enqueue_script_module( 'wbcom-faq', plugins_url( 'js/faq.js', __FILE__ ), array( '@wordpress/interactivity' ), '1.4.0' );

Pair either with wp_add_inline_script() or wp_localize_script() to pass data, and with wp_set_script_translations() if the script contains strings.

Which one to learn first

Which hidden gem in WordPress to learn first by project type, from theme.json to the Abilities API
If you mostly build…Start withThen
Client brochure sitestheme.json and style variationsBlock Bindings
Membership or community sitesCustom REST routes, transientsAbilities API
Plugins for distributionWP-CLI commands, script modulesInteractivity API
Headless or multi-site integrationsApplication passwords, HTTP APIAbilities API

Frequently asked questions

Do I still need Advanced Custom Fields?

For complex repeater and flexible-content layouts, ACF Pro remains faster to build with. For a handful of plain fields shown in a template, register_post_meta() plus Block Bindings covers it in core, with no licence and no lock-in.

Is the Abilities API safe to expose on a production site?

Yes, provided every ability has a strict permission_callback. Abilities without show_in_rest are PHP-only and never reachable over HTTP. Treat them exactly as you would a REST route.

Does the Interactivity API replace React in blocks?

No. The editor side of a block is still React. The Interactivity API is for the front end, where it is lighter and server-rendered. Use both in the same block.

Where do these snippets go?

In a small site-specific plugin under wp-content/plugins/, or a must-use plugin in mu-plugins/ if it should never be deactivated. Not in the theme’s functions file, which disappears the day the theme changes.

If your team wants help adopting any of these on an existing codebase, or building a plugin that uses them properly, you can hire a WordPress developer from Wbcom for a short review engagement. Otherwise, pick one item from the table, spend an afternoon on it, and delete the plugin it replaces.

Shashank Dubey
Content & Marketing, Wbcom Designs

Shashank Dubey, a contributor of Wbcom Designs is a blogger and a digital marketer. He writes articles associated with different niches such as WordPress, SEO, Marketing, CMS, Web Design, and Development, and many more.

Related reading