WordPress custom routing without hacking the Rewrite API

A client called about a big WooCommerce site with a custom event registration plugin someone built years ago. It worked, but the URLs were a mess. Think /?event_action=view_attendees&event_id=123. They wanted clean URLs instead, like /events/123/attendees. Sounds simple, but the site was already a tangle of plugins and custom code, and throwing more rewrite rules at it felt like asking for trouble. This is a common problem once a WordPress site grows into a real application: you outgrow the basics of the Rewrite API.

My first thought was to just do it the “quick” way. Add a new rule with add_rewrite_rule() to handle the new URL structure, register the new query variables, and then slap a big if/else block in a template_redirect hook to check the vars and load the right template. And yeah, it worked for that one URL. But then they wanted another for editing, and another for exporting a CSV. It was turning into a procedural nightmare. One wrong regex and the whole thing could conflict with another plugin. Total mess.

The problem with just using the Rewrite API

The catch is that the Rewrite API isn’t really a routing system, at least not the way a developer coming from Laravel or Symfony would picture one. Its main job is to translate a pretty URL into a set of WordPress query variables. That’s it. It doesn’t map a URL directly to a function or controller. The logic sits completely apart from the URL definition, and that’s where things get fragile on bigger projects.

A real routing system works differently. It sets up explicit relationships: this URL path maps to this specific piece of logic. That is more robust and a lot easier to read. Carl Alexander walked through this in a detailed post on building a routing system, laying out the object-oriented approach. Instead of a pile of scattered functions, you end up with a simple, declarative map of your application’s endpoints.

// A cleaner, more declarative way to think about routes
$router->add_route('event_attendees', new Route(
    '/events/{id}/attendees',
    'my_display_event_attendees_callback'
));

$router->add_route('event_edit', new Route(
    '/events/{id}/edit',
    'my_edit_event_form_callback'
));

// The logic is neatly separated
function my_display_event_attendees_callback($id) {
    // Go get attendees for event $id and load a template...
}

So, what’s the point?

Switching from the standard Rewrite API to a structured, object-oriented routing system for WordPress custom routing is not about making URLs look nicer. It is about making the whole application more stable, easier to maintain, and easier for another developer, or for you six months from now, to follow.

  • It’s declarative: all your custom endpoints live in one place.
  • It’s maintainable: the path and the logic are linked, so you are not hunting through hooks and conditionals to debug a URL.
  • It scales: as the application grows, you just add routes, and the core logic does not get more complicated.

This stuff gets complicated fast. If you are tired of debugging someone else’s mess and just want the site to work, get in touch with my team. We have probably seen it before.

Once you are building something that feels more like an app on top of WordPress, you have to start thinking like an app developer, and that means building a real routing system. Trust me, it saves you a lot of pain later.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.