Luke Anderson WordPress

Useful WordPress Functions Every Developer Must Know (With Examples) | CloudyWP Australia

WordPress has thousands of built-in functions. You don’t need to memorize all of them — but there’s a core set of around 70 functions that real WordPress developers use every single day. Master these and you can build almost any custom theme, plugin, or feature without ever leaving the WordPress ecosystem.

This guide is that exact list — organized by category, each with a real working code example you can copy and paste. Bookmark this page; you’ll come back to it constantly.

How to Use This Guide

Each function below includes:

  • 📌 What it does in plain English
  • ⚙️ Function signature (parameters)
  • 💻 A real code example you can drop into a theme or plugin
  • 💡 Tips and gotchas where they matter

Jump to a section:

  1. The Loop & Post Data — have_posts(), the_title(), the_content()…
  2. Custom Queries — WP_Query, get_posts(), wp_reset_postdata()
  3. Post Meta / Custom Fields — get_post_meta(), update_post_meta()
  4. Site & URL Functions — home_url(), get_template_directory_uri()
  5. Conditional Tags — is_home(), is_single(), is_user_logged_in()
  6. Template Includes — get_header(), get_template_part()
  7. Enqueuing Scripts & Styles — wp_enqueue_script(), wp_enqueue_style()
  8. Hooks: Actions & Filters — add_action(), add_filter()
  9. Options API — get_option(), update_option()
  10. User Functions — wp_get_current_user(), current_user_can()
  11. Security & Escaping — esc_html(), sanitize_text_field()
  12. Nonces (CSRF Protection) — wp_nonce_field(), wp_verify_nonce()
  13. Database ($wpdb) — $wpdb->insert(), $wpdb->get_results()
  14. Menus & Sidebars — wp_nav_menu(), register_sidebar()
  15. Featured Images & Media — the_post_thumbnail(), wp_get_attachment_image_src()
  16. Comments — comments_template(), comment_form()
  17. Pagination — the_posts_pagination(), paginate_links()
  18. Categories, Tags & Terms — get_categories(), get_terms()
  19. Custom Post Types & Taxonomies — register_post_type()
  20. AJAX — wp_localize_script(), wp_send_json_success()

1. The Loop & Post Data

These are the absolute basics — used in every theme on every page.

have_posts() and the_post()

What it does: Together they form “The Loop” — WordPress’s standard way of iterating over posts.

if (have_posts()) :
    while (have_posts()) : the_post();
        the_title('<h2>', '</h2>');
        the_content();
    endwhile;
else :
    echo '<p>No posts found.</p>';
endif;

the_title()

What it does: Echoes the current post’s title. Accepts optional before/after HTML.

the_title('<h1 class="post-title">', '</h1>');
// Output: <h1 class="post-title">My Blog Post</h1>

get_the_title()

What it does: Same as the_title() but returns the value instead of echoing it.

$title = get_the_title();
$shortened = substr($title, 0, 50) . '...';
echo $shortened;

the_content()

What it does: Echoes the full post content (with shortcodes processed and embeds rendered).

the_content('Continue reading →');
// The argument is the "more" link text for posts with <!--more--> tags

the_excerpt() and get_the_excerpt()

What it does: Echoes (or returns) the post excerpt — manual excerpt if set, otherwise auto-generated from content.

<p class="card-summary"><?php echo get_the_excerpt(); ?></p>

What it does: Echoes/returns the URL to the current post.

<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>

get_the_ID() and the_ID()

What it does: Returns/echoes the current post ID.

$post_id = get_the_ID();
$views = get_post_meta($post_id, 'view_count', true);

the_author() and get_the_author()

<p>Written by <?php the_author(); ?></p>

the_date() and get_the_date()

What it does: Echoes/returns the post’s publish date with optional PHP date format.

<time><?php echo get_the_date('F j, Y'); ?></time>
// Output: <time>March 15, 2026</time>

the_category()

<p>Filed under: <?php the_category(', '); ?></p>

the_tags()

<?php the_tags('Tags: ', ', ', ''); ?>
// Output: Tags: design, css, frontend

2. Custom Queries

The Loop runs on the “main query” automatically. For custom queries (e.g., “show 3 latest products”), use these.

WP_Query

What it does: The flagship custom-query class. Most powerful and most-used.

$args = [
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'category_name'  => 'tutorials',
    'orderby'        => 'date',
    'order'          => 'DESC',
];

$query = new WP_Query($args);

if ($query->have_posts()) :
    while ($query->have_posts()) : $query->the_post();
        ?>
        <article>
            <h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
            <p><?php echo get_the_excerpt(); ?></p>
        </article>
        <?php
    endwhile;
    wp_reset_postdata();
endif;

⚠️ Always call wp_reset_postdata() after a custom query — otherwise WordPress thinks the last post in your custom query is the “current post.”

get_posts()

What it does: Simpler than WP_Query. Returns an array of post objects (no loop needed).

$posts = get_posts([
    'numberposts' => 3,
    'category'    => 5,
    'orderby'     => 'rand',
]);

foreach ($posts as $post) :
    setup_postdata($post);
    ?>
    <h3><?php the_title(); ?></h3>
    <?php
endforeach;
wp_reset_postdata();

wp_reset_postdata()

What it does: Restores the global $post to the main query’s current post. Critical after every custom query.

query_posts() ⚠️

Don’t use this. It overrides the main query and breaks pagination and other features. Use WP_Query or pre_get_posts instead.


3. Post Meta / Custom Fields

Custom fields let you attach extra data to any post.

get_post_meta()

What it does: Retrieves a custom field value.

// Get single value
$price = get_post_meta(get_the_ID(), 'product_price', true);
echo '$' . esc_html($price);

// Get array of all values for that key
$gallery = get_post_meta(get_the_ID(), 'gallery_images', false);

The 3rd parameter (true/false) is the most-asked-about. true = return single value. false = return array.

update_post_meta()

What it does: Adds OR updates a custom field. Smart — creates if missing, updates if exists.

update_post_meta(get_the_ID(), 'view_count', 42);
update_post_meta($post_id, 'last_viewed', current_time('mysql'));

add_post_meta()

What it does: Adds a new custom field. Use when you want multiple values for the same key.

add_post_meta($post_id, 'tagged_user', 'alice');
add_post_meta($post_id, 'tagged_user', 'bob');
// Now 'tagged_user' has 2 entries

delete_post_meta()

delete_post_meta($post_id, 'old_field');

4. Site & URL Functions

Never hard-code URLs. Use these functions so your site works when moved.

home_url()

What it does: Returns the site’s home URL.

<a href="<?php echo esc_url(home_url('/')); ?>">Home</a>
<a href="<?php echo esc_url(home_url('/contact')); ?>">Contact</a>

site_url() vs home_url()

Function Returns
home_url() The site’s homepage URL (what users see)
site_url() Where WordPress files live (often the same as home_url, but can differ in WordPress-in-subdirectory setups)

Use home_url() for user-facing links. Use site_url() for admin/internal stuff.

admin_url()

$dashboard = admin_url('index.php');
$new_post  = admin_url('post-new.php');

get_template_directory_uri()

What it does: Returns the URL to the active theme’s folder. Use for assets.

<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/main.css">
<img src="<?php echo get_template_directory_uri(); ?>/img/logo.png">

get_template_directory()

What it does: Returns the file path (not URL) to the theme folder. Use for PHP includes.

require get_template_directory() . '/inc/custom-functions.php';

get_stylesheet_uri()

What it does: Returns the URL to the active theme’s style.css.

<link rel="stylesheet" href="<?php echo get_stylesheet_uri(); ?>">

bloginfo()

What it does: Echoes various pieces of site info.

<title><?php bloginfo('name'); ?></title>
<meta name="description" content="<?php bloginfo('description'); ?>">
<meta charset="<?php bloginfo('charset'); ?>">

5. Conditional Tags

Check what page is currently being displayed. Essential for functions.php and template files.

if (is_home())          // Blog index page
if (is_front_page())    // Site homepage
if (is_single())        // Single blog post
if (is_page())          // Static page
if (is_page('about'))   // Specific page by slug
if (is_category())      // Category archive
if (is_archive())       // Any archive page
if (is_search())        // Search results
if (is_404())           // 404 page
if (is_admin())         // WP admin area
if (is_user_logged_in()) // Logged-in user
if (is_singular('product')) // Single post of post type "product"

Real-world example:

// Show sidebar only on blog posts and pages, not on homepage
if (is_single() || is_page()) {
    get_sidebar();
}

6. Template Includes

Reuse template parts across pages.

<?php get_header(); ?>

<main>...content...</main>

<?php get_sidebar(); ?>
<?php get_footer(); ?>

You can also load named variants:

get_header('shop');     // Loads header-shop.php
get_footer('minimal');  // Loads footer-minimal.php

get_template_part()

What it does: Loads a custom template fragment. The cleanest way to keep code DRY.

// Loads content-card.php
get_template_part('content', 'card');

// Pass data with the 3rd argument (WP 5.5+)
get_template_part('content', 'card', ['featured' => true]);

comments_template()

if (comments_open() || get_comments_number()) {
    comments_template();
}

7. Enqueuing Scripts & Styles

Never hard-code <link> or <script> tags. Use these functions for proper caching, dependencies, and plugin compatibility.

wp_enqueue_style()

function my_theme_styles() {
    wp_enqueue_style(
        'main-style',                           // Handle (unique name)
        get_template_directory_uri() . '/css/main.css', // URL
        [],                                     // Dependencies
        '1.0.0',                                // Version (for cache-busting)
        'all'                                   // Media: all, screen, print
    );
}
add_action('wp_enqueue_scripts', 'my_theme_styles');

wp_enqueue_script()

function my_theme_scripts() {
    wp_enqueue_script(
        'main-js',                              // Handle
        get_template_directory_uri() . '/js/main.js',
        ['jquery'],                             // Dependencies
        '1.0.0',                                // Version
        true                                    // Load in footer? (true recommended)
    );
}
add_action('wp_enqueue_scripts', 'my_theme_scripts');

wp_localize_script()

What it does: Pass PHP variables to JavaScript. Essential for AJAX.

wp_enqueue_script('main-js', '/js/main.js', ['jquery'], '1.0', true);

wp_localize_script('main-js', 'myAjax', [
    'ajaxurl' => admin_url('admin-ajax.php'),
    'nonce'   => wp_create_nonce('my_ajax_action'),
    'home'    => home_url(),
]);

// In JavaScript: myAjax.ajaxurl, myAjax.nonce, myAjax.home

wp_dequeue_style() / wp_dequeue_script()

What it does: Remove styles/scripts added by another plugin or theme.

function remove_woo_styles() {
    wp_dequeue_style('woocommerce-general');
}
add_action('wp_enqueue_scripts', 'remove_woo_styles', 100);

8. Hooks: Actions & Filters

The single most important concept in WordPress development.

Hook type What it does
Action Runs your code at a specific moment (e.g., “when WordPress loads”, “before sending email”)
Filter Modifies data before WordPress uses it (e.g., change post title before display)

add_action()

function send_welcome_email($user_id) {
    $user = get_userdata($user_id);
    wp_mail($user->user_email, 'Welcome!', 'Thanks for joining.');
}
add_action('user_register', 'send_welcome_email');

add_filter()

function add_emoji_to_title($title) {
    return '🚀 ' . $title;
}
add_filter('the_title', 'add_emoji_to_title');

do_action() / apply_filters()

What it does: Lets YOUR plugin/theme create its own hooks for others to extend.

// In your plugin
function my_plugin_render_widget() {
    echo '<div class="my-widget">';
    do_action('my_plugin_before_content');
    echo '<p>The widget content</p>';
    do_action('my_plugin_after_content');
    echo '</div>';
}

// Now anyone can hook into your plugin
add_action('my_plugin_after_content', function() {
    echo '<small>Powered by My Plugin</small>';
});

remove_action() / remove_filter()

remove_action('wp_head', 'wp_generator'); // Hide WordPress version from <head>

9. Options API

Store and retrieve site-wide settings without touching the database directly.

get_option()

$site_name = get_option('blogname');
$admin_email = get_option('admin_email');
$my_setting = get_option('my_plugin_setting', 'default-value');

update_option()

update_option('my_plugin_setting', 'new-value');
update_option('my_array_setting', ['enabled' => true, 'count' => 5]);

add_option()

What it does: Only adds if it doesn’t exist. Use for plugin install defaults.

add_option('my_plugin_version', '1.0.0');

delete_option()

delete_option('old_setting');

10. User Functions

wp_get_current_user()

$user = wp_get_current_user();

if ($user->ID) {
    echo 'Hello, ' . esc_html($user->display_name);
    echo 'Your email: ' . esc_html($user->user_email);
    echo 'Your roles: ' . implode(', ', $user->roles);
}

is_user_logged_in()

if (is_user_logged_in()) {
    echo 'Welcome back!';
} else {
    echo '<a href="' . esc_url(wp_login_url()) . '">Log in</a>';
}

current_user_can()

What it does: Check user permissions. Use this before allowing any admin action.

if (current_user_can('edit_posts')) {
    // Show edit button
}

if (current_user_can('manage_options')) {
    // Show admin settings
}

if (current_user_can('edit_post', $post_id)) {
    // Check on a specific post
}

get_userdata() / get_user_by()

$user = get_userdata(5);
$user = get_user_by('email', '[email protected]');
$user = get_user_by('login', 'alice');

wp_create_user() / wp_insert_user()

$user_id = wp_create_user('newuser', 'password123', '[email protected]');

if (!is_wp_error($user_id)) {
    echo 'User created with ID ' . $user_id;
}

11. Security & Escaping

Always escape output. Always sanitize input. This prevents XSS, SQL injection, and most security vulnerabilities.

Output escaping

Function Use case
esc_html() Plain text output inside HTML
esc_attr() HTML attribute values
esc_url() URLs in href, src
esc_js() Strings inside inline JS
esc_textarea() Content inside <textarea>
wp_kses_post() HTML that can contain safe tags (like post content)
<a href="<?php echo esc_url($link); ?>" title="<?php echo esc_attr($title); ?>">
    <?php echo esc_html($name); ?>
</a>

Input sanitization

Function Use case
sanitize_text_field() Single-line text input
sanitize_textarea_field() Multi-line text
sanitize_email() Email addresses
sanitize_title() Slugs / URL-friendly text
sanitize_key() Identifiers (lowercase, alphanumeric only)
absint() Positive integers
wp_kses_post() Allow safe HTML, strip dangerous tags
$name  = sanitize_text_field($_POST['name']);
$email = sanitize_email($_POST['email']);
$bio   = wp_kses_post($_POST['bio']);
$age   = absint($_POST['age']);

12. Nonces (CSRF Protection)

Nonces protect against cross-site request forgery. Use them in every form and AJAX request.

wp_nonce_field()

<form method="post" action="">
    <?php wp_nonce_field('save_my_form', 'my_form_nonce'); ?>
    <input type="text" name="email">
    <input type="submit" value="Save">
</form>

wp_verify_nonce() / check_admin_referer()

// In your form handler
if (!isset($_POST['my_form_nonce']) ||
    !wp_verify_nonce($_POST['my_form_nonce'], 'save_my_form')) {
    wp_die('Security check failed.');
}

// Now safe to process
$email = sanitize_email($_POST['email']);

wp_create_nonce()

Use for AJAX:

wp_localize_script('main-js', 'myAjax', [
    'nonce' => wp_create_nonce('my_ajax_action'),
]);
// In JS
fetch(myAjax.ajaxurl, {
    method: 'POST',
    body: new URLSearchParams({
        action: 'my_handler',
        nonce: myAjax.nonce,
    }),
});
// In handler
add_action('wp_ajax_my_handler', function() {
    check_ajax_referer('my_ajax_action', 'nonce');
    wp_send_json_success(['message' => 'Saved!']);
});

13. Database ($wpdb)

For custom queries that can’t be done with WP_Query. Always use $wpdb->prepare() to prevent SQL injection.

$wpdb->insert()

global $wpdb;
$wpdb->insert(
    $wpdb->prefix . 'my_table',
    [
        'name'  => 'Alice',
        'email' => '[email protected]',
        'date'  => current_time('mysql'),
    ],
    ['%s', '%s', '%s'] // Format: %s string, %d integer, %f float
);

$new_id = $wpdb->insert_id;

$wpdb->update()

$wpdb->update(
    $wpdb->prefix . 'my_table',
    ['name' => 'Alice Smith'], // SET
    ['id' => 5],               // WHERE
    ['%s'],                    // Format for SET
    ['%d']                     // Format for WHERE
);

$wpdb->delete()

$wpdb->delete(
    $wpdb->prefix . 'my_table',
    ['id' => 5],
    ['%d']
);

$wpdb->get_results() and prepare()

$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}my_table WHERE status = %s AND views > %d",
        'published',
        100
    )
);

foreach ($results as $row) {
    echo esc_html($row->name);
}

$wpdb->get_var() and $wpdb->get_row()

// Get single value
$count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts}");

// Get single row
$row = $wpdb->get_row(
    $wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE ID = %d", 5)
);

dbDelta()

What it does: Creates or updates database tables. Use in plugin activation hooks.

register_activation_hook(FILE, 'my_plugin_install');

function my_plugin_install() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'my_table';
    $charset = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        name varchar(100) NOT NULL,
        email varchar(100) NOT NULL,
        date datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY (id)
    ) $charset;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);
}

14. Menus & Sidebars

register_nav_menus() (in functions.php)

function my_theme_menus() {
    register_nav_menus([
        'primary' => ('Primary Menu', 'my-theme'),
        'footer'  => ('Footer Menu', 'my-theme'),
    ]);
}
add_action('after_setup_theme', 'my_theme_menus');
<?php
wp_nav_menu([
    'theme_location' => 'primary',
    'menu_class'     => 'nav-list',
    'container'      => 'nav',
    'container_class' => 'main-nav',
    'depth'          => 2,
]);
?>

register_sidebar()

function my_theme_sidebars() {
    register_sidebar([
        'name'          => ('Main Sidebar', 'my-theme'),
        'id'            => 'main-sidebar',
        'description'   => 'Appears on blog posts',
        'before_widget' => '<section id="%1$s" class="widget %2$s">',
        'after_widget'  => '</section>',
        'before_title'  => '<h3 class="widget-title">',
        'after_title'   => '</h3>',
    ]);
}
add_action('widgets_init', 'my_theme_sidebars');

dynamic_sidebar() (in template)

<?php if (is_active_sidebar('main-sidebar')) : ?>
    <aside><?php dynamic_sidebar('main-sidebar'); ?></aside>
<?php endif; ?>

has_post_thumbnail()

if (has_post_thumbnail()) {
    the_post_thumbnail('large');
}

the_post_thumbnail() and get_the_post_thumbnail()

the_post_thumbnail('medium', ['class' => 'rounded shadow']);

// Sizes: thumbnail, medium, large, full, or custom

wp_get_attachment_image_src()

What it does: Get the URL of an attachment.

$thumbnail_id = get_post_thumbnail_id();
$image = wp_get_attachment_image_src($thumbnail_id, 'full');

if ($image) {
    echo '<img src="' . esc_url($image[0]) . '" width="' . $image[1] . '" height="' . $image[2] . '">';
}

add_image_size() (in functions.php)

add_action('after_setup_theme', function() {
    add_theme_support('post-thumbnails');
    add_image_size('card-thumb', 400, 300, true);  // 400x300 cropped
    add_image_size('hero-banner', 1920, 800, true);
});

// Use in template:
the_post_thumbnail('card-thumb');

wp_get_attachment_url()

$url = wp_get_attachment_url($attachment_id);

16. Comments

comments_template()

if (comments_open() || get_comments_number()) {
    comments_template();
}

comment_form() with custom args

comment_form([
    'title_reply'  => 'Leave a Comment',
    'label_submit' => 'Post Comment',
    'class_submit' => 'btn btn-primary',
    'comment_field' => '<p><textarea name="comment" rows="5" required></textarea></p>',
]);

wp_list_comments()

if (have_comments()) :
    ?>
    <ol class="comment-list">
        <?php
        wp_list_comments([
            'style'       => 'ol',
            'short_ping'  => true,
            'avatar_size' => 60,
        ]);
        ?>
    </ol>
    <?php
endif;

comments_open() / get_comments_number()

if (comments_open()) {
    echo 'Comments are open';
}

echo get_comments_number() . ' comments';

17. Pagination

the_posts_pagination()

the_posts_pagination([
    'prev_text' => '← Previous',
    'next_text' => 'Next →',
    'mid_size'  => 2,
    'screen_reader_text' => 'Posts navigation',
]);
$args = ['posts_per_page' => 10, 'paged' => get_query_var('paged') ?: 1];
$query = new WP_Query($args);

// ...loop...

echo paginate_links([
    'total'   => $query->max_num_pages,
    'current' => max(1, get_query_var('paged')),
    'format'  => '?paged=%#%',
]);

wp_reset_postdata();

previous_post_link() / next_post_link()

<div class="post-nav">
    <?php previous_post_link('<span class="prev">← %link</span>'); ?>
    <?php next_post_link('<span class="next">%link →</span>'); ?>
</div>

18. Categories, Tags & Terms

get_categories()

$categories = get_categories([
    'orderby'    => 'name',
    'hide_empty' => true,
]);

foreach ($categories as $cat) :
    echo '<a href="' . esc_url(get_category_link($cat->term_id)) . '">'
       . esc_html($cat->name) . ' (' . $cat->count . ')</a><br>';
endforeach;

get_tags()

$tags = get_tags(['hide_empty' => true]);
foreach ($tags as $tag) {
    echo '<a href="' . esc_url(get_tag_link($tag->term_id)) . '">#' . esc_html($tag->name) . '</a> ';
}

get_terms() (for custom taxonomies)

$terms = get_terms([
    'taxonomy'   => 'product_category',
    'hide_empty' => false,
]);

foreach ($terms as $term) {
    echo esc_html($term->name);
}

wp_get_post_terms()

// Get all categories of a post
$post_cats = wp_get_post_terms(get_the_ID(), 'category');

// Get custom taxonomy terms
$brands = wp_get_post_terms(get_the_ID(), 'brand');

the_category() / the_tags() (display in loop)

the_category(', ');         // Comma-separated list
the_tags('Tags: ', ', ');  // With "Tags:" prefix

19. Custom Post Types & Taxonomies

register_post_type()

function register_my_cpt() {
    register_post_type('product', [
        'labels' => [
            'name'          => 'Products',
            'singular_name' => 'Product',
            'add_new'       => 'Add Product',
            'add_new_item'  => 'Add New Product',
            'edit_item'     => 'Edit Product',
        ],
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true, // For Gutenberg
        'supports'     => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
        'menu_icon'    => 'dashicons-cart',
        'rewrite'      => ['slug' => 'products'],
    ]);
}
add_action('init', 'register_my_cpt');

register_taxonomy()

function register_my_taxonomies() {
    register_taxonomy('product_category', 'product', [
        'labels' => [
            'name'          => 'Product Categories',
            'singular_name' => 'Product Category',
        ],
        'hierarchical' => true,   // true = like categories, false = like tags
        'show_in_rest' => true,
        'rewrite'      => ['slug' => 'product-category'],
    ]);
}
add_action('init', 'register_my_taxonomies');

20. AJAX

WordPress has a built-in AJAX system. Don’t roll your own.

Setup in PHP

// 1. Enqueue your JS and pass the AJAX URL + nonce
function my_ajax_setup() {
    wp_enqueue_script('my-ajax', get_template_directory_uri() . '/js/ajax.js', ['jquery'], '1.0', true);
    wp_localize_script('my-ajax', 'myAjax', [
        'ajaxurl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('my_ajax_nonce'),
    ]);
}
add_action('wp_enqueue_scripts', 'my_ajax_setup');

// 2. Handle the AJAX request
add_action('wp_ajax_save_form', 'handle_save_form');         // Logged-in users
add_action('wp_ajax_nopriv_save_form', 'handle_save_form');  // Visitors

function handle_save_form() {
    check_ajax_referer('my_ajax_nonce', 'nonce');

    $name = sanitize_text_field($_POST['name']);
    $email = sanitize_email($_POST['email']);

    if (empty($name) || empty($email)) {
        wp_send_json_error(['message' => 'All fields are required']);
    }

    // Save to database...

    wp_send_json_success(['message' => 'Saved!', 'id' => 42]);
}

JavaScript side

fetch(myAjax.ajaxurl, {
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    body: new URLSearchParams({
        action: 'save_form',        // Matches wp_ajax_save_form
        nonce: myAjax.nonce,
        name: 'Alice',
        email: '[email protected]',
    }),
})
.then(r => r.json())
.then(data => {
    if (data.success) {
        console.log('Saved!', data.data.message);
    } else {
        console.error(data.data.message);
    }
});

wp_send_json_success() / wp_send_json_error()

These automatically set headers, encode JSON, and exit. Always use them in AJAX handlers.


Top 20 Daily-Use Cheat Sheet

If you only memorize these, you can build 80% of WordPress projects:

Function Use case
have_posts() + the_post() The Loop
the_title() / the_content() / the_permalink() Display post data
get_the_ID() Current post ID
WP_Query Custom queries
wp_reset_postdata() After custom queries
get_post_meta($id, 'key', true) Custom fields
update_post_meta() Save custom fields
home_url() / get_template_directory_uri() URLs
is_home() / is_single() / is_page() Conditional checks
get_header() / get_footer() Template includes
wp_enqueue_style() / wp_enqueue_script() Load CSS/JS
add_action() / add_filter() Hooks
get_option() / update_option() Site settings
current_user_can('capability') Permission checks
esc_html() / esc_url() / esc_attr() Output escaping
sanitize_text_field() Input sanitization
wp_nonce_field() / wp_verify_nonce() Form security
wp_nav_menu() Display menus
the_post_thumbnail() Featured image
the_posts_pagination() Pagination

Common Mistakes to Avoid

Mistake Why it’s bad Fix
Using query_posts() Breaks main query & pagination Use WP_Query or pre_get_posts filter
Forgetting wp_reset_postdata() Breaks subsequent loops Always reset after WP_Query
Hard-coding URLs Breaks when site moves Use home_url(), get_template_directory_uri()
Hard-coding <link>/<script> tags No caching, no dependency management Use wp_enqueue_style/script()
Echoing $_POST data directly XSS vulnerability Sanitize input, escape output
Not using nonces CSRF vulnerability Add wp_nonce_field() to all forms
Direct $_GET/$_POST SQL queries SQL injection Use $wpdb->prepare()
update_option() on every page load Slow + DB bloat Cache via transients or autoload
Putting CPTs in functions.php Data lost when theme changes Put in plugin or MU plugin
Forgetting wp_head() / wp_footer() Plugins break Always include both in templates

Wrapping Up

You don’t need to memorize 70 functions today. Start with the Top 20 cheat sheet above — you’ll use those daily. Add the rest as you encounter use cases. Within a few months you’ll have them all in muscle memory.

WordPress’s API is huge, but consistent. Once you understand the patterns — hooks, escaping, the loop, custom queries — you can build virtually anything: blogs, e-commerce, membership sites, headless apps, custom admin panels.

💡 Pro tip: When you forget a function name or syntax, the WordPress Code Reference is the official documentation. Search there before Stack Overflow — it’s faster and always up to date.


Related reading on this blog: [wordpress-theme-development-from-scratch], [wsl-docker-php-setup], [useful-docker-commands]

Discussion

Be the first to comment

Leave a comment

Get a quote