Redirect System

Keyboard shortcuts
  • JNext lesson
  • KPrevious lesson
  • /Search lessons
  • EscClear search

How redirects work

When a visitor requests a URL on your site, GT Link Manager attempts to match it against your configured links using a three-step resolution process. The plugin intercepts requests early — on the init hook at priority 0 — before any WordPress template logic runs.

If a match is found at any step, the plugin issues an HTTP redirect response and exits immediately. No WordPress page or template is ever loaded. The PHP process ends right after sending the redirect headers. This early interception is what makes GT Link Manager faster than CPT-based redirect plugins.

Three-step redirect resolution

The redirect handler resolves incoming requests in a specific order. Each step only runs if the previous step found no match.

Step 1: Standard prefix-based lookup

This is the fastest path and always runs first. The handler checks if the request URL matches the pattern yoursite.com/prefix/slug. If it does, it extracts the slug and queries the database for a matching link with link_mode = 'standard'.

The lookup uses the slug column’s unique index for O(1) performance. For most sites using only standard links, this is the only step that ever executes.

Step 2: Direct (prefix-free) exact match

This step only runs if Advanced Redirects is enabled in settings and no standard match was found. The handler extracts the clean request path (stripping any WordPress subdirectory prefix) and looks for a link where link_mode = 'direct' and the slug matches the path exactly.

Direct links are cached under a direct:-prefixed cache key, separate from standard link caches.

Step 3: Regex pattern match

This step only runs if Advanced Redirects is enabled and neither standard nor direct matching found a result. The handler loads all active regex rules (ordered by priority, ascending) and tests each pattern against the request path.

Regex patterns use # as the delimiter to avoid conflicts with / in URL patterns. The first matching pattern wins.

When a regex rule matches, the target URL is constructed in one of two ways:

  • If the regex_replacement field is set, preg_replace is used with the full pattern and replacement string. This gives you full regex substitution power.
  • If regex_replacement is empty, simple $1, $2 token replacement is applied to the destination URL. Each $N is replaced with the corresponding capture group from the match.

Regex rules are cached as a group under the regex_rules cache key. The cache is invalidated when any link is created, updated, or deleted.

Rewrite rules

The redirect system depends on a custom WordPress rewrite rule for standard links. The rule pattern is:

^{prefix}/([^/]+)/?$

This matches any URL that starts with the configured prefix, followed by a single path segment (the slug), with an optional trailing slash. The captured slug is passed to WordPress as the gtlm_slug query variable.

Rewrite rules are registered on init (priority 1) each time WordPress loads. WordPress caches parsed rewrite rules in wp_options, so in practice the rule registration is a no-op most of the time.

Rewrite rules must be flushed whenever the prefix changes. If you change the prefix in settings and don’t flush, the old prefix continues to match (because the old rule is cached) and the new prefix returns a 404. The Flush Permalinks button on the settings page handles this.

Direct and regex links do not use rewrite rules. They operate by parsing REQUEST_URI directly, which means they work regardless of permalink structure.

Database lookup and caching

After extracting the slug from the request, the plugin queries the wp_gtlm_links table. Results are cached using the WordPress object cache under the group gtlm_links.

Cache keys are scoped by link mode:

  • Standard links: cache key is the slug
  • Direct links: cache key is direct:{path}
  • Regex rules: cache key is regex_rules (all rules cached as a group)

On a site using a persistent object cache (Redis, Memcached), the database is queried only once per cache key for the lifetime of the cache entry.

The cache TTL defaults to 0 (no expiration). The gtlm_cache_ttl filter lets you set an explicit TTL in seconds:

add_filter( 'gtlm_cache_ttl', function( $ttl, $slug, $link ) {
    return 3600; // Cache for one hour
}, 10, 3 );

The cache is invalidated automatically whenever a link is created, updated, or deleted. When settings change (particularly the prefix), the entire gtlm_links cache group is flushed.

Redirect headers

When a matching link is found, the plugin builds and sends the following response headers:

Cache-Control: no-store, no-cache, must-revalidate, max-age=0 — prevents browsers and CDNs from caching the redirect response. This ensures that if you update the destination URL, the change takes effect immediately on the next request.

X-Redirect-By: GT Link Manager — identifies the source of the redirect for debugging purposes.

Location: {destination URL} — the target URL the visitor is being sent to.

If noindex is enabled on the link, an additional header is sent:

X-Robots-Tag: noindex, nofollow — instructs search engine crawlers not to index the redirect URL.

If rel attributes are configured on the link, an additional header is sent:

Link: <{destination URL}>; rel="{rel values}" — declares the relationship between the redirect URL and the destination.

Inactive and trashed links

Links with is_active = 0 or a non-null trashed_at timestamp are skipped during redirect resolution. Even if a slug matches, the redirect is not issued. The request continues through normal WordPress routing, typically resulting in a 404.

Site-relative targets

Destination URLs that start with / are treated as site-relative and automatically prepended with home_url(). This means you can set a destination like /contact/ and the redirect will resolve to yoursite.com/contact/. External URLs (starting with http:// or https://) are used as-is.

Modifying redirect behavior

Several filters let you modify the redirect at runtime, after the link record has been loaded.

Override the destination URL:

add_filter( 'gtlm_redirect_url', function( $url, $link, $slug ) {
    // Modify or replace the destination URL
    return $url;
}, 10, 3 );

Override the HTTP status code:

add_filter( 'gtlm_redirect_code', function( $status, $link, $slug ) {
    // Return 301, 302, or 307
    return $status;
}, 10, 3 );

Override the rel attributes:

add_filter( 'gtlm_rel_attributes', function( $rel_values, $link, $slug ) {
    // $rel_values is an array like ['nofollow', 'sponsored']
    return $rel_values;
}, 10, 3 );

Override all headers:

add_filter( 'gtlm_headers', function( $headers, $link, $slug ) {
    // $headers is an associative array
    $headers['X-Custom-Header'] = 'value';
    return $headers;
}, 10, 3 );

Hook into the redirect before it fires:

add_action( 'gtlm_before_redirect', function( $link, $target_url, $status, $headers ) {
    // Log the redirect, update a click counter, send to analytics, etc.
}, 10, 4 );

The gtlm_before_redirect action is the right place to add click tracking if you need it. The $link parameter is the full link record array from the database, including link_mode, regex_replacement, and priority fields.

When no matching link is found

If no match is found across all three resolution steps, the plugin takes no action. WordPress continues its normal routing — which will typically result in a 404 page.

If you want to handle the no-match case differently (redirect to the homepage, log the miss, etc.), you can hook into template_redirect at a lower priority and check for the gtlm_slug query variable yourself.

Subdirectory installs

The redirect handler accounts for WordPress installations in a subdirectory. If WordPress is installed at yoursite.com/blog/, standard links resolve at yoursite.com/blog/go/slug, and direct/regex links match against the path after the subdirectory prefix. This works without any additional configuration.

WordPress multisite

GT Link Manager is not explicitly designed for multisite. The database tables are created per-site on activation, and settings are stored per-site in wp_options. Whether it works correctly in a multisite context depends on how your network is configured. Network-wide activation is not supported.