Security
WordPress is the most widespread system and therefore the most attacked. Most break-ins happen not through the core but through themes and plugins with careless code. The rules are few, but they must always be followed: sanitise input, escape output, check the token and the rights.
Escaping output
Any value is escaped before it is printed on a page — with the function that fits the place:
<?php
// card.php
?>
<h2><?php echo esc_html($title); ?></h2>
<a href="<?php echo esc_url($link); ?>" title="<?php echo esc_attr($hint); ?>">Open</a>
<div class="content"><?php echo wp_kses_post($html); ?></div>| Function | Where to use it |
|---|---|
esc_html() | Text between tags |
esc_attr() | An attribute value |
esc_url() | An address in href and src |
esc_js() | A string inside JavaScript |
wp_kses_post() | HTML in which only safe tags are allowed |
Escape as late as possible — right in the output line.
Sanitising input
Data from a visitor is cleaned before it is saved:
$name = sanitize_text_field($_POST['name'] ?? '');
$email = sanitize_email($_POST['email'] ?? '');
$age = absint($_POST['age'] ?? 0);
$slug = sanitize_title($_POST['slug'] ?? '');
$comment = sanitize_textarea_field($_POST['comment'] ?? '');
$page = esc_url_raw($_POST['website'] ?? '');sanitize_text_field() removes tags, line breaks and extra spaces.
Nonces
A nonce confirms that a request was sent from a page of your site rather than staged by someone else's. One function adds it to a form:
<?php
// form.php
?>
<form method="post">
<?php wp_nonce_field('mytheme_feedback', 'feedback_nonce'); ?>
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>The nonce is verified when the form is handled:
// functions.php
add_action('init', function () {
if (!isset($_POST['feedback_nonce'])) {
return;
}
if (!wp_verify_nonce($_POST['feedback_nonce'], 'mytheme_feedback')) {
wp_die('The form has expired. Please refresh the page.');
}
$message = sanitize_textarea_field($_POST['message'] ?? '');
// save the message
});Links have wp_nonce_url(), and dashboard pages check_admin_referer().
Checking capabilities
A nonce answers "where did the request come from"; capabilities answer "may this user do it":
if (!current_user_can('edit_post', $postId)) {
wp_die('You do not have permission.', 403);
}
if (!current_user_can('manage_options')) {
return;
}Check the capability for the action, not the role: roles on a site can be changed.
Database queries
Values go into SQL queries only through $wpdb->prepare() — the previous lesson covered that.
Protecting files
<?php
// includes/helpers.php
defined('ABSPATH') || exit;In the wp-config.php of a live site the dashboard code editor and error display are turned off:
// wp-config.php
define('DISALLOW_FILE_EDIT', true);
define('WP_DEBUG', false);What else matters
| Measure | Why |
|---|---|
| Update the core, themes and plugins | Most break-ins use vulnerabilities fixed long ago |
| Delete unused plugins | A deactivated plugin can still be vulnerable |
| HTTPS | The password and cookies cannot be intercepted on the network |
| Backups | The only way to recover after a break-in |
Common mistake
Sanitising on save does not replace escaping on output. Data may have reached the database another way — through an import, another plugin, an old version of the code. The rule to remember: sanitise on the way in, escape on the way out — both, always.