Examples
Five concrete WordPress recipes that come up in real projects: a custom post type, a REST API endpoint, a shortcode, a metabox with sanitised meta, and a cron-driven job. Each is paste-ready into a small plugin or functions.php.
Five working WordPress snippets
EXAMPLE
<?php
// ============================================================
// 1) Custom post type — case studies / portfolio
// ============================================================
add_action('init', function () {
register_post_type('case_study', [
'labels' => [
'name' => 'Case Studies',
'singular_name' => 'Case Study',
'add_new_item' => 'Add New Case Study',
],
'public' => true,
'show_in_rest' => true, // exposes via REST + block editor
'has_archive' => 'case-studies',
'rewrite' => ['slug' => 'case-studies'],
'supports' => ['title','editor','thumbnail','excerpt','custom-fields'],
'taxonomies' => ['category', 'post_tag'],
'menu_icon' => 'dashicons-portfolio',
]);
});
// ============================================================
// 2) Custom REST API endpoint — typed, capability-gated
// ============================================================
add_action('rest_api_init', function () {
register_rest_route('shop/v1', '/refunds', [
'methods' => 'POST',
'callback' => 'shop_request_refund',
'permission_callback' => fn() => current_user_can('edit_orders'),
'args' => [
'order_id' => [
'required' => true,
'type' => 'integer',
'sanitize_callback' => 'absint',
],
'amount_cents' => [
'required' => true,
'type' => 'integer',
'validate_callback' => fn($v) => is_int($v) && $v > 0,
],
],
]);
});
function shop_request_refund(WP_REST_Request $req) {
$id = (int) $req->get_param('order_id');
$amount = (int) $req->get_param('amount_cents');
// ... do the work, return WP_REST_Response
return new WP_REST_Response(['queued' => true, 'order_id' => $id, 'amount_cents' => $amount], 202);
}
// ============================================================
// 3) Shortcode — render dynamic data inside a post or page
// ============================================================
add_shortcode('shop_ticker', function ($atts) {
$atts = shortcode_atts(['symbol' => 'BTC'], $atts);
$symbol = sanitize_text_field($atts['symbol']);
// imagine a transient cached fetch
$price = get_transient("ticker_$symbol");
if ($price === false) {
// fetch + sanitize
$price = 12345.67;
set_transient("ticker_$symbol", $price, 60);
}
return sprintf(
'<span class="ticker">%s: <strong>$%s</strong></span>',
esc_html($symbol),
esc_html(number_format($price, 2))
);
});
// Usage in a post: [shop_ticker symbol='ETH']
// ============================================================
// 4) Metabox — admin UI for custom fields, sanitised on save
// ============================================================
add_action('add_meta_boxes', function () {
add_meta_box('case_study_meta', 'Case Study Settings', 'case_study_meta_box', 'case_study', 'side');
});
function case_study_meta_box(WP_Post $post) {
wp_nonce_field('save_case_study_meta', 'case_study_nonce');
$client = get_post_meta($post->ID, '_client', true);
$url = get_post_meta($post->ID, '_url', true);
echo '<p><label>Client<br><input type="text" name="client" value="' . esc_attr($client) . '" class="widefat"></label></p>';
echo '<p><label>Live URL<br><input type="url" name="url" value="' . esc_attr($url) . '" class="widefat"></label></p>';
}
add_action('save_post_case_study', function (int $post_id) {
if (!isset($_POST['case_study_nonce']) ||
!wp_verify_nonce($_POST['case_study_nonce'], 'save_case_study_meta')) return;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
update_post_meta($post_id, '_client', sanitize_text_field($_POST['client'] ?? ''));
update_post_meta($post_id, '_url', esc_url_raw($_POST['url'] ?? ''));
});
// ============================================================
// 5) Cron job — daily housekeeping
// ============================================================
add_filter('cron_schedules', function ($schedules) {
$schedules['daily_at_2am'] = [
'interval' => DAY_IN_SECONDS,
'display' => 'Daily at 2am',
];
return $schedules;
});
register_activation_hook(__FILE__, function () {
if (!wp_next_scheduled('shop_daily_cleanup')) {
wp_schedule_event(strtotime('tomorrow 02:00'), 'daily', 'shop_daily_cleanup');
}
});
register_deactivation_hook(__FILE__, function () {
wp_clear_scheduled_hook('shop_daily_cleanup');
});
add_action('shop_daily_cleanup', function () {
// Reach into custom tables, purge stale transients, send a summary, etc.
global $wpdb;
$wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_%' AND option_value < UNIX_TIMESTAMP()");
});
// ============================================================
// Patterns to internalise
// ============================================================
// - Always escape output (esc_html, esc_attr, esc_url, wp_kses_post)
// - Always sanitise input (sanitize_text_field, absint, esc_url_raw)
// - Always verify nonces on form submits + capability-check on REST endpoints
// - Treat WP-Cron as 'best effort' — for important jobs use real cron + wp-cli
// - Keep this in a small plugin file, not in functions.php, so theme switches do not break it
Why it matters
Always escape on output and sanitise on input — different functions for different contexts, applied at the point of use. WordPress security is mostly that one habit; the rest is patches and least-privileged DB credentials. Get the escape-and-sanitise reflex into the team and most of the OWASP list goes away.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// See the lesson body for ready-to-paste snippets. echo 'Hello, WordPress';Try it Yourself »
Discussion
Loading…