WordPress esc_url Example: Sanitize Output Links Safely
Use esc_url and esc_url_raw correctly so WordPress links, stored endpoints, and outbound requests stay predictable and safe.
Published
April 25, 2026
Reading Time
2 min read
Updated
April 25, 2026

Hardening Notes
Baselines, access reduction, and default settings that stand up in production.
Best For
Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.
Primary Topics
Editorial Focus
Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on April 25, 2026.
Full Report
Last reviewed: April 25, 2026
URLs move through WordPress code constantly: admin links, front-end buttons, profile links, download targets, redirects, REST roots, and external references. The common mistake is mixing storage-time sanitization and display-time escaping, then assuming one helper solves both problems everywhere.
This guide shows when to use esc_url() for output, when to use esc_url_raw() for storage or outbound requests, and how to keep URL handling predictable in plugin and theme code.
Use esc_url for displayed links
<?php
$report_url = admin_url( 'admin.php?page=vulnwp-reports' );
echo '<a class=\"button\" href=\"' . esc_url( $report_url ) . '\">Open report</a>';
esc_url() is for display. It cleans the URL and prepares it for HTML output, including entity handling that is appropriate in rendered markup.
Use esc_url_raw for storage and remote calls
$webhook_url = isset( $_POST['vulnwp_webhook_url'] )
? esc_url_raw( wp_unslash( $_POST['vulnwp_webhook_url'] ) )
: '';
update_option( 'vulnwp_webhook_url', $webhook_url, false );
$response = wp_remote_post( $webhook_url );
esc_url_raw() is the right choice when the value is being stored or sent programmatically. It avoids display-specific entity escaping that does not belong in the database or network request layer.
Validate allowed hosts when business rules matter
Escaping a URL does not mean it is an allowed destination. If the code should only talk to internal APIs or approved providers, add a host allowlist after sanitization.
$parsed_host = wp_parse_url( $webhook_url, PHP_URL_HOST );
$allowed_hosts = array( 'api.vulnwp.org', 'hooks.slack.com' );
if ( ! in_array( $parsed_host, $allowed_hosts, true ) ) {
return new WP_Error( 'invalid_host', 'Webhook host is not allowed.' );
}
Keep URL helpers scoped to their purpose
- Displayed HTML link:
esc_url() - Saved option or outbound request:
esc_url_raw() - Redirect target validation:
wp_validate_redirect()orwp_safe_redirect()
Those helpers overlap, but they are not interchangeable if you want the code to stay readable and correct.
Production checklist
- Escape displayed links with
esc_url(). - Sanitize stored URLs and outbound request targets with
esc_url_raw(). - Validate host rules separately when the integration requires it.
- Do not trust saved option values just because they came from an admin screen.
- Use redirect-specific helpers for redirect flows.
- Test edge cases such as missing protocol, whitespace, and unexpected schemes.
Common mistakes
- Using
esc_url()before saving to the database. That mixes display escaping with storage logic. - Assuming a sanitized URL is automatically approved. Business allowlists are a separate rule.
- Printing raw option values into links. Stored values still need output escaping.
- Using URL sanitization for redirect authorization. That does not prevent open redirects by itself.
- Ignoring disallowed protocols. Test non-http schemes explicitly.
Related reading
If the URL is used for redirects, pair this with the wp_safe_redirect guide. If the URL is sent to remote APIs, combine it with the HTTP API article so validation and transport rules stay aligned.


