WordPress Rewrite Rules Example: Add a Custom URL Endpoint Safely
Add custom WordPress rewrite rules safely with query vars, activation flushing, route validation, 404 handling, and collision prevention.
Published
April 20, 2026
Reading Time
3 min read
Updated
April 20, 2026

Implementation Notes
Extension points, code paths, and implementation choices that should survive contact with production.
Best For
WordPress developers, agencies, and technical teams building custom plugin or theme functionality with cleaner operational defaults.
Primary Topics
Editorial Focus
Build Pattern: Extension points, code paths, and implementation choices that should survive contact with production. Updated on April 20, 2026.
Full Report
Last reviewed: April 20, 2026
Custom URLs in WordPress are powerful, but rewrite rules can break a production site quickly when they are registered incorrectly. A bad rule can shadow existing URLs, create confusing 404s, or require repeated manual permalink flushing.
This guide shows a practical Rewrite API example for adding a custom URL endpoint. The pattern keeps the rule inside a plugin, registers query vars, flushes rules only on activation, and renders a controlled response.
Who this guide is for
This article is for WordPress developers building custom landing pages, tracking endpoints, documentation routes, simple public tools, or plugin-owned URL surfaces that do not fit normal posts or pages.
When to use rewrite rules
Use rewrite rules when WordPress needs to understand a pretty URL and route it to plugin logic. Do not use rewrite rules just to avoid creating a page, custom post type, REST route, or template. Pick the API that matches the feature.
Plugin example: public report URL
<?php
/**
* Plugin Name: VulnWP Report Route
* Description: Adds a custom pretty URL for public reports.
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'init', 'vulnwp_register_report_rewrite' );
add_filter( 'query_vars', 'vulnwp_register_report_query_vars' );
add_action( 'template_redirect', 'vulnwp_render_report_route' );
function vulnwp_register_report_rewrite() {
add_rewrite_rule(
'^security-report/([a-z0-9-]+)/?$',
'index.php?vulnwp_report_slug=$matches[1]',
'top'
);
}
function vulnwp_register_report_query_vars( $vars ) {
$vars[] = 'vulnwp_report_slug';
return $vars;
}
function vulnwp_render_report_route() {
$slug = get_query_var( 'vulnwp_report_slug' );
if ( '' === $slug ) {
return;
}
$slug = sanitize_key( $slug );
status_header( 200 );
nocache_headers();
wp_send_json(
array(
'slug' => $slug,
'status' => 'available',
)
);
}
This creates URLs like /security-report/example-slug/. The plugin sanitizes the slug and returns a controlled JSON response.
Flush rules on activation only
register_activation_hook(
__FILE__,
function () {
vulnwp_register_report_rewrite();
flush_rewrite_rules();
}
);
register_deactivation_hook(
__FILE__,
function () {
flush_rewrite_rules();
}
);
Do not call flush_rewrite_rules() on every request. It is expensive and unnecessary. Register the rules on every request, but flush only when rules change, usually on activation and deactivation.
Test the route
curl -i https://example.com/security-report/demo-report/
wp rewrite list --match=/security-report/demo-report/
The first command checks the HTTP response. The second helps inspect which rewrite rule matches when WP-CLI is available.
Avoid route collisions
Choose a unique prefix that belongs to the plugin or feature. Generic prefixes such as /report/, /api/, or /download/ are more likely to collide with pages, plugins, or future site structure. If the route is public, document it like any other URL contract.
Return 404 for invalid resources
A rewrite rule means WordPress can route the URL. It does not mean every matched URL is valid. If the requested resource does not exist, return a real 404 instead of a blank success response.
if ( ! vulnwp_report_exists( $slug ) ) {
status_header( 404 );
nocache_headers();
include get_query_template( '404' );
exit;
}
This matters for SEO. Search engines should not index unlimited fake URLs that happen to match the rewrite pattern.
Private endpoints
If the custom route exposes private data, add authentication and authorization before rendering. Rewrite rules are not security controls. They only map a URL to request variables.
if ( ! is_user_logged_in() || ! current_user_can( 'read_private_posts' ) ) {
auth_redirect();
}
For machine-readable APIs, a REST route with a permission_callback is often cleaner than a custom rewrite endpoint.
Production checklist
- Register rewrite rules on
init. - Register custom query vars.
- Flush rewrite rules only on activation, deactivation, or controlled migrations.
- Use a unique URL prefix to avoid collisions.
- Sanitize values extracted from the URL.
- Return proper status codes for missing or invalid resources.
- Test with existing pages and post slugs before launch.
Common mistakes
- Flushing on every request. This creates unnecessary overhead.
- Forgetting query vars. WordPress may strip unknown variables.
- Using overly broad regex. Broad rules can shadow existing content.
- No activation flush. New URLs may 404 until permalinks are refreshed.
- Treating URL values as trusted. Rewrite matches still come from user requests.
Related reading
For API-style JSON output, compare this with our register_rest_route example. For URL changes across a site, read the search-replace safety checklist.


