The internet does not need another list of fifty WordPress functions. It needs a shorter one.
This is the shortlist. Five functions (fine, four functions and one API, we'll get to that) that do the heavy lifting on every single site we ship, including the one you're reading right now. No theory, no filler. Ten years of agency builds, distilled to the pieces that earn their place every time.
Everything below lives happily in a child theme's functions.php or a code snippets plugin. Pick one home, keep it versioned, and let's go.
add_filter() & add_action()
Every serious WordPress conversation starts here. WordPress is not a black box. It's a timeline of events, and hooks are the doors into it. add_action() runs your code at a specific moment. add_filter() intercepts a value on its way to the page, changes it, and hands it back like nothing happened.
// Do something at a moment in time.
add_action( 'wp_footer', 'op_tracking_snippet' );
// Intercept a value, change it, hand it back.
add_filter( 'excerpt_length', fn () => 24 );
That's the entire mechanism. With it, you reshape core behavior, plugin output, and theme markup without touching a single file that doesn't belong to you. No forked plugins. No "temporary" edits in someone else's codebase that outlive everyone involved.
If you learn one thing about WordPress, learn this. Then hold the thought. Section 05 turns this key into a scalpel.
add_shortcode()
React people have components. So do we. Ours are older, need zero build steps, and ship in one file.
A shortcode is a tag you invent. PHP renders it, and it can be dropped into any page, template, or builder widget. Our entire site runs on this pattern: the blog archive, the case study grids, the hero sections. Each one is a PHP snippet that returns markup, placed wherever it belongs with a single tag. One source of truth, portable across themes and builders, friendly to caching.
add_shortcode( 'op_team_grid', function ( $atts ) {
$a = shortcode_atts( [ 'count' => 6 ], $atts );
$people = get_posts( [
'post_type' => 'team',
'posts_per_page' => (int) $a['count'],
] );
$out = '<div class="op-team">';
foreach ( $people as $p ) {
$out .= '<article>' . esc_html( get_the_title( $p ) ) . '</article>';
}
return $out . '</div>';
} );
Now [op_team_grid count="8"] works anywhere on the site.
One rule, carved in stone: return the markup, never echo it. Echoing inside a shortcode is how content ends up at the top of the page and how afternoons end up ruined.
set_transient() & get_transient()
Some things are expensive. A remote API call. A monster meta query. Anything that makes the server think for longer than it should, on every single page load, for every single visitor.
The transients API is the cache you already own. A value, a key, an expiry. Nothing to install.
function op_get_rates() {
$rates = get_transient( 'op_rates' );
if ( false === $rates ) {
$response = wp_remote_get( 'https://api.example.com/rates' );
if ( is_wp_error( $response ) ) {
return [];
}
$rates = json_decode( wp_remote_retrieve_body( $response ), true );
set_transient( 'op_rates', $rates, 12 * HOUR_IN_SECONDS );
}
return $rates;
}
The first visitor pays. Everyone else rides free for the next twelve hours. And if the host runs a persistent object cache like Redis, transients automatically get faster. Same code, better hardware underneath.
Two lines of API. That's the whole discipline.
wp_enqueue_script() + conditional loading
Performance in 2026 is not a plugin you install. It's the weight you refuse to ship. Core Web Vitals punish every kilobyte of JavaScript a page loads and never uses. Most WordPress sites load everything, everywhere, always.
The fix is old-fashioned discipline. Enqueue properly, and only where the code is actually needed. On our own site, the blog's styles and scripts load on the blog, and nowhere else. The homepage has never met them.
add_action( 'wp_enqueue_scripts', function () {
if ( ! is_page( 'blog' ) ) {
return;
}
wp_enqueue_script(
'op-blog',
get_stylesheet_directory_uri() . '/js/blog.js',
[],
'1.4.0',
[ 'strategy' => 'defer' ]
);
} );
Note the last argument. Since WordPress 6.3 you can declare a loading strategy right in the enqueue call, either defer or async, no filter gymnastics required. A small flag with real interactivity wins.
The conditional tags do the targeting. is_page(), is_singular(), is_post_type_archive(). Learn three of them and your waterfall chart gets shorter overnight.
“Every WordPress developer alive has, at some point, run a regular expression over a string of HTML. We've all sinned. That era is over. Most developers haven't noticed.
The HTML API: WP_HTML_Tag_Processor
Confession time. Every WordPress developer alive has, at some point, run a regular expression over a string of HTML. We've all sinned. We all knew it was fragile. We did it anyway, because the alternative was loading a full DOM parser to change one attribute.
That era is over, and most developers haven't noticed. Since version 6.2, WordPress ships an HTML API, and its first tool, the Tag Processor, does exactly one job with surgical calm. Find tags, read or modify their attributes, return the updated markup. One pass. No regex. Nothing breaks when a client pastes markup you didn't expect.
Here's where it gets good for anyone building animated frontends. We drive our interfaces with GSAP, and the markup we need to target often comes from places we don't control. The editor, a plugin, an embed. So we tag it on the way out, server-side:
add_filter( 'the_content', function ( $html ) {
$p = new WP_HTML_Tag_Processor( $html );
while ( $p->next_tag( 'img' ) ) {
$p->add_class( 'js-reveal' );
$p->set_attribute( 'data-op-reveal', 'fade-up' );
}
return $p->get_updated_html();
} );
Every image in every post now carries a clean animation hook. No regex, no fragile string surgery, no praying the markup stays polite. Pair it with the master key from section 01 and you can reshape any HTML that flows through a filter. Lazy-loading rules, rel attributes on external links, design-system classes on third-party output.
And this is only the opening act. Since 6.4, its big brother WP_HTML_Processor understands actual structure. Nested tags, inner content, walking the tree. It's the machinery WordPress is quietly rebuilding itself on.
Most developers still haven't heard of it. Now you're not most developers.
query_posts()
It's in a thousand old tutorials, and it's a trap. query_posts() throws away the main query WordPress already ran and runs a new one in its place. Wrecking pagination, doubling database work, and lying to every conditional tag downstream.
query_posts( [
'category_name' => 'insights',
'posts_per_page' => 5,
] );
while ( have_posts() ) {
the_post();
the_title();
}
$q = new WP_Query( [
'category_name' => 'insights',
'posts_per_page' => 5,
] );
while ( $q->have_posts() ) {
$q->the_post();
the_title();
}
wp_reset_postdata();
The replacements have been standard for years: new WP_Query() for secondary loops (with wp_reset_postdata() after), and the pre_get_posts hook when you want to shape the main query itself.
If you spot query_posts() during a codebase takeover, adjust the estimate. Where there's one of these, there are others.
The shortlist: five WordPress functions, zero excuses.
Hooks to step in. Shortcodes to build. Transients to cache. Enqueue to stay lean. The HTML API to operate on markup like an adult.
None of it is glamorous. All of it is load-bearing. Cinematic frontends stand on boring foundations. That's the plumbing under every site we build, including this one. The pretty stuff is in the work →