Skip to content
Gaurav TiwariDocs

GT Link Manager Hooks & Filters

Updated November 1, 2026

GT Link Manager hooks let you bend the redirect pipeline without forking the plugin: log clicks, rewrite destination URLs, change status codes, add headers, override capabilities. This reference documents every action and filter with parameters and working examples. If you only learn one, learn gtlm_before_redirect; it powers every analytics integration in this course.

Actions

gtlm_activated

Fires immediately after the plugin activation routine completes. No parameters.

Use this to run one-time setup in your own plugin or theme after GT Link Manager tables and rules are in place.

add_action( 'gtlm_activated', function() {
    // Seed default links, log the event, etc.
} );

gtlm_after_save

Fires after a link is created or updated via the admin form.

Parameters:

$link_id, int. The ID of the saved link.

$insert_data, array. The normalized data that was written to the database.

Use this to sync links to an external system, invalidate a custom cache, or send a notification.

add_action( 'gtlm_after_save', function( $link_id, $insert_data ) {
    // $insert_data has keys: name, slug, url, redirect_type, rel, noindex,
    // is_active, category_id, tags, notes, link_mode, regex_replacement,
    // priority, geo_mode, geo_rules
}, 10, 2 );

gtlm_after_delete

Fires after a link is permanently deleted.

Parameters:

$link_id, int. The ID of the deleted link.

$link, array. The full link record as it existed before deletion.

add_action( 'gtlm_after_delete', function( $link_id, $link ) {
    // Clean up any external references to $link['slug']
}, 10, 2 );

gtlm_before_redirect

Fires just before the HTTP redirect response is sent, before any header goes out. This is the correct place to add click tracking, logging, or analytics. Anything slow in a callback delays the visitor, so keep it short or defer it to the shutdown action. It fires for every resolved redirect, including bots, HEAD requests and the Run Diagnostics check.

Parameters:

$link, array. The full link record from the database, including link_mode, regex_replacement, and priority fields.

$target_url, string. The destination URL the visitor is being redirected to (after any filter modifications and regex substitution).

$status, int. The HTTP status code (301, 302, or 307).

$headers, array. All response headers about to be sent.

$geo, array|null. Added in 1.7.0. Null for links without geolocation. For geo-targeted links, an array with url, redirect_type, country, and matched (true if a country rule won, false if the fallback was used). Callbacks registered with four arguments keep working.

add_action( 'gtlm_before_redirect', function( $link, $target_url, $status, $headers, $geo ) {
    // Log to your analytics service
    my_analytics_track( [
        'event'   => 'link_click',
        'slug'    => $link['slug'],
        'mode'    => $link['link_mode'],
        'url'     => $target_url,
        'status'  => $status,
        'country' => $geo ? $geo['country'] : '',
    ] );
}, 10, 5 );

gtlm_settings_saved

Fires after settings are saved via the admin settings form.

Parameters:

$settings, array. The new settings values that were saved, including enable_advanced_redirects.

add_action( 'gtlm_settings_saved', function( $settings ) {
    // React to setting changes
} );

gtlm_geo_blocked

Added in 1.7.0. Fires when a visitor is blocked because a geo-targeted link’s fallback is set to “show a 404” and no country rule matched.

Parameters:

$link, array. The full link record.

$country, string. The detected country code, or an empty string if unknown.

add_action( 'gtlm_geo_blocked', function( $link, $country ) {
    // Log blocked geo traffic
}, 10, 2 );

gtlm_click_recorded

Added in 1.8.0. Fires after a click has been added to a link’s lifetime count. It only runs when click counting is on under GT Links > Settings, and it skips any click that gtlm_count_click rejected.

Parameters:

$link_id, int. The ID of the link.

$link, array. The full link record.

add_action( 'gtlm_click_recorded', function( $link_id, $link ) {
    // Pass the count on to your own stats system
    my_stats_increment( 'link_clicks', $link['slug'] );
}, 10, 2 );

On PHP-FPM, the visitor has already been redirected by this point. On other setups the browser may still be waiting, so keep the callback short.


Added in 1.8.0. Fires when a request under your link prefix doesn’t resolve to a live link, which includes trashed and inactive links. GT Link Manager then sends a 404 through your theme.

Parameters:

$slug, string. The requested slug. It’s empty for a nested path under your prefix, such as /go/a/b.

add_action( 'gtlm_link_not_found', function( $slug ) {
    // Find dead links people still click
    error_log( "GT Link Manager: no live link for {$slug}" );
} );

It doesn’t fire for paths outside your prefix, or when gtlm_404_on_missing_link returns false.


gtlm_trash_purged

Added in 1.8.0. Fires after the daily cleanup job permanently deletes trashed links. It only fires when trash retention is above 0 days and the job removed at least 1 link.

Parameters:

$purged, int. The number of links removed.

$days, int. The retention period in days.

add_action( 'gtlm_trash_purged', function( $purged, $days ) {
    error_log( "GT Link Manager: removed {$purged} links after {$days} days in the trash" );
}, 10, 2 );

gtlm_after_delete also fires for each link the job removes, so per-link cleanup belongs there.


Filters

gtlm_settings

Filters the plugin settings after they’re loaded from the database. This lets you programmatically override any setting without touching wp_options.

Parameters:

$settings, array. The settings array with keys base_prefix, default_redirect_type, default_rel, default_noindex, delete_data_on_uninstall, trash_retention_days, enable_click_tracking, enable_advanced_redirects, prefix_free_links, plus the 1.7.0 geolocation keys enable_geo_targeting, geo_detection_method, geo_custom_header, and geo_debug_header.

add_filter( 'gtlm_settings', function( $settings ) {
    $settings['base_prefix'] = 'out';
    $settings['enable_advanced_redirects'] = 1;
    return $settings;
} );

gtlm_prefix

Filters just the base prefix value at the point it’s used by the redirect handler and rewrite rule registration. Overriding here is more targeted than using gtlm_settings.

Parameters:

$prefix, string. The current base prefix.

add_filter( 'gtlm_prefix', function( $prefix ) {
    return 'ref';
} );

gtlm_cache_ttl

Filters the object cache TTL for slug lookups. The default is 0, which means the entry is cached without an expiry. Set an integer value in seconds to use a time-based expiry.

Parameters:

$ttl, int. Current TTL in seconds.

$slug, string. The slug being looked up (or direct:{path} for direct links, or regex_rules for regex rule sets).

$link, array|null. The link record if found, null if not.

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

gtlm_redirect_url

Filters the destination URL before the redirect is issued. The link has already been found in the database at this point. For regex links, capture group substitution has already been applied.

Parameters:

$url, string. The destination URL (after regex substitution, if applicable).

$link, array. The full link record.

$slug, string. The slug from the request (for standard links) or the matched path (for direct/regex links).

add_filter( 'gtlm_redirect_url', function( $url, $link, $slug ) {
    // Append a UTM parameter
    return add_query_arg( 'utm_source', 'yoursite', $url );
}, 10, 3 );

gtlm_redirect_code

Filters the HTTP status code used for the redirect.

Parameters:

$status, int. The redirect status code (301, 302, or 307).

$link, array. The full link record.

$slug, string. The slug or matched path from the request.

add_filter( 'gtlm_redirect_code', function( $status, $link, $slug ) {
    // Force all redirects to 302 for logged-in users
    if ( is_user_logged_in() ) {
        return 302;
    }
    return $status;
}, 10, 3 );

gtlm_rel_attributes

Filters the rel attribute values for the redirect response’s Link header.

Parameters:

$rel_values, array. An array of rel strings (e.g., ['nofollow', 'sponsored']).

$link, array. The full link record.

$slug, string. The slug or matched path from the request.

add_filter( 'gtlm_rel_attributes', function( $rel_values, $link, $slug ) {
    // Ensure nofollow is always present
    if ( ! in_array( 'nofollow', $rel_values, true ) ) {
        $rel_values[] = 'nofollow';
    }
    return $rel_values;
}, 10, 3 );

gtlm_headers

Filters the full array of response headers before they’re sent. This is the most powerful redirect filter, you can add, remove, or modify any header.

Parameters:

$headers, array. Associative array of header name to header value.

$link, array. The full link record.

$slug, string. The slug or matched path from the request.

add_filter( 'gtlm_headers', function( $headers, $link, $slug ) {
    $headers['Referrer-Policy'] = 'no-referrer';
    return $headers;
}, 10, 3 );

gtlm_capabilities

Filters the capability required to access specific plugin contexts. Use this to restrict or expand access.

Parameters:

$capability, string. The current capability string. The default is edit_posts, which includes Authors and Contributors.

$context, string. The context requesting the capability. The values are menu, links_page, edit_page, categories_page, import_export, actions, quick_edit, admin_bar, dashboard, rest_api and analytics_exclude (logged-in users who pass this check are left out of Advanced Analytics). The Settings page always requires manage_options and isn’t filterable, and the Analytics screen uses its own gtlm_analytics_capability filter.

add_filter( 'gtlm_capabilities', function( $capability, $context ) {
    // Only admins can manage links. Editors' clicks still stay out of analytics.
    if ( 'analytics_exclude' !== $context ) {
        return 'manage_options';
    }
    return $capability;
}, 10, 2 );

Filters the columns displayed in the All Links admin list table. Useful for adding or removing columns.

Parameters:

$columns, array. Associative array of column ID to column label.

add_filter( 'gtlm_link_columns', function( $columns ) {
    // Remove the tags column
    unset( $columns['tags'] );
    return $columns;
} );

gtlm_count_click

Added in 1.8.0. Filters whether a click adds to a link’s lifetime count. It only runs when click counting is on. Without it, the count includes every resolved redirect, including bots and HEAD requests.

Parameters:

$count, bool. Whether to count the click. It starts as true.

$link, array. The full link record.

add_filter( 'gtlm_count_click', function( $count, $link ) {
    // Leave your editors out of the count
    return $count && ! current_user_can( 'edit_posts' );
}, 10, 2 );

This filter only affects the lifetime count. Advanced Analytics has gtlm_analytics_should_record.


Added in 1.8.0. Filters whether an unresolved link under your prefix returns a 404. Return false to let WordPress handle the request instead.

Parameters:

$send_404, bool. Whether to send the 404. It starts as true.

add_filter( 'gtlm_404_on_missing_link', '__return_false' );

It is wise to leave this on unless something depends on the old behavior. With it off, WordPress serves your front page with a 200 status for dead links, and search engines read that as your home page living at all those URLs.


Geolocation filters

Version 1.7.0 added five filters around country detection, rule matching and the link editor. You’ll only need them if the built-in detection or rule behavior doesn’t fit.

gtlm_geo_country

Filters the detected country code. This is the escape hatch for plugging in any detection method you like, a MaxMind database lookup, a session value, anything.

Parameters: $country, string, the detected ISO 3166-1 alpha-2 code or empty string; $source, string, the request variable it came from.

add_filter( 'gtlm_geo_country', function( $country, $source ) {
    // Plug in your own detection
    return $country;
}, 10, 2 );

gtlm_geo_sources

Filters the list of request variables checked for a country code, in priority order. Add your own header here if your infrastructure names it differently.

Parameters: $sources, array of request variable names.


gtlm_geo_country_groups

Filters the country groups usable in rules. The plugin ships with EU, which expands to all 27 member states. Add your own group to target a custom market in one token.

Parameters: $groups, array mapping a group token to an array of country codes.

add_filter( 'gtlm_geo_country_groups', function( $groups ) {
    $groups['DACH'] = [ 'DE', 'AT', 'CH' ];
    return $groups;
} );

gtlm_geo_matched_rule

Filters the resolved geo outcome just before the redirect is sent. Return null to trigger the link’s block fallback, or modify the resolved URL and status code.

Parameters: $resolved, array|null with url, redirect_type, country, matched; $rule, array, the winning rule (empty when the fallback applied); $link, array, the full link record; $country, string, the detected country.


gtlm_geo_quick_picks

Filters the one-click country buttons in the link editor. An entry can use country codes or a group token such as EU.

Parameters: $picks, array of entries, each with a label and an array of codes.

add_filter( 'gtlm_geo_quick_picks', function( $picks ) {
    $picks[] = [ 'label' => 'DACH', 'codes' => [ 'DE', 'AT', 'CH' ] ];
    return $picks;
} );

Advanced Analytics Filters

Advanced Analytics stays off until you turn it on under GT Links > Analytics > Settings. You’ll only need these 3 filters to change what it records, who can read it or when it warns you about storage.

gtlm_analytics_should_record

Added in 1.9.0. Filters whether a click is recorded in Advanced Analytics. The bot and logged-in checks run after this filter, so returning true can’t add those clicks back.

Parameters:

$record, bool. Whether to record the click. It starts as true.

$link, array. The full link record.

add_filter( 'gtlm_analytics_should_record', function( $record, $link ) {
    // Keep links tagged "internal" out of the reports
    $tags = array_map( 'trim', explode( ',', (string) ( $link['tags'] ?? '' ) ) );
    return $record && ! in_array( 'internal', $tags, true );
}, 10, 2 );

It doesn’t affect the lifetime count.


gtlm_analytics_capability

Added in 1.9.0. Filters the capability needed to read Advanced Analytics. It controls these parts:

  • The Analytics screen and its reports
  • The report links in All Links
  • CSV export
  • The REST endpoints that read analytics

The Settings tab and anything that changes analytics data always require manage_options.

Parameters:

$capability, string. The current capability. The default is manage_options.

add_filter( 'gtlm_analytics_capability', function( $capability ) {
    // Let editors read the reports
    return 'edit_others_posts';
} );

gtlm_analytics_storage_warning_bytes

Added in 1.9.1. Filters the size at which Advanced Analytics warns you about its storage. Collection never stops because of size. The warning only prompts you to delete old data.

Parameters:

$bytes, int. The warning level in bytes. It comes from the level on the Settings tab, which defaults to 100 MB.

add_filter( 'gtlm_analytics_storage_warning_bytes', function( $bytes ) {
    return 2 * GB_IN_BYTES;
} );

A value from this filter replaces the level on the Settings tab.


The click-tracking lessons later in this course are all applications of these hooks: start with the self-hosted click log or GA4 integration.

Share

Was this helpful?

Still stuck? Open a support ticket