This click stats dashboard widget puts your link analytics where you already look every morning: the WordPress admin dashboard. Total clicks across time ranges, a daily sparkline, your top 10 links, and top referrers, all rendered from the self-hosted click log with zero external requests.
Requirements
- GT Link Manager 1.5+ installed and active
- The Click Log: Custom Database Table snippet must be installed first (creates the
wp_gtlm_click_logtable) - User must have the
edit_postscapability (filterable viagtlm_capabilities)
Installation
- Install the Click Log: Custom Database Table snippet first if you haven’t already
- Add the snippet below to your theme’s
functions.phpor a site-specific plugin - Visit your WordPress Dashboard — the widget appears automatically
The Code
<?php
/**
* GT Link Manager - Click Stats Dashboard Widget
*
* Adds a WordPress admin dashboard widget showing:
* - Total clicks (today, 7 days, 30 days, all time)
* - Top 10 clicked links (last 30 days)
* - Clicks-per-day sparkline chart (last 30 days)
* - Top referrers
*
* Requires: click-log-db.php snippet (creates the wp_gtlm_click_log table).
*/
defined( 'ABSPATH' ) || exit;
add_action( 'wp_dashboard_setup', 'gtlm_stats_register_widget' );
function gtlm_stats_register_widget(): void {
if ( ! current_user_can( apply_filters( 'gtlm_capabilities', 'edit_posts', 'dashboard_widget' ) ) ) {
return;
}
wp_add_dashboard_widget(
'gtlm_click_stats',
'GT Link Manager - Click Stats',
'gtlm_stats_render_widget'
);
}
function gtlm_stats_render_widget(): void {
global $wpdb;
$table = $wpdb->prefix . 'gtlm_click_log';
// Check if click log table exists.
$table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
if ( ! $table_exists ) {
echo '<p>No click data yet. Install the <strong>click-log-db.php</strong> snippet to start logging clicks.</p>';
return;
}
$now = current_time( 'mysql', true );
// ── Summary counts ───────────────────────────────────────────────────────
$today = gmdate( 'Y-m-d 00:00:00' );
$week = gmdate( 'Y-m-d 00:00:00', strtotime( '-7 days' ) );
$month = gmdate( 'Y-m-d 00:00:00', strtotime( '-30 days' ) );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$count_today = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE created_at >= %s", $today ) );
$count_week = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE created_at >= %s", $week ) );
$count_month = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE created_at >= %s", $month ) );
$count_all = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );
echo '<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px;text-align:center;">';
printf( '<div><div style="font-size:24px;font-weight:700;color:#1d2327;">%s</div><div style="color:#646970;font-size:12px;">Today</div></div>', number_format_i18n( $count_today ) );
printf( '<div><div style="font-size:24px;font-weight:700;color:#1d2327;">%s</div><div style="color:#646970;font-size:12px;">7 Days</div></div>', number_format_i18n( $count_week ) );
printf( '<div><div style="font-size:24px;font-weight:700;color:#1d2327;">%s</div><div style="color:#646970;font-size:12px;">30 Days</div></div>', number_format_i18n( $count_month ) );
printf( '<div><div style="font-size:24px;font-weight:700;color:#1d2327;">%s</div><div style="color:#646970;font-size:12px;">All Time</div></div>', number_format_i18n( $count_all ) );
echo '</div>';
// ── Daily clicks chart (last 30 days) ────────────────────────────────────
$daily = $wpdb->get_results( $wpdb->prepare(
"SELECT DATE(created_at) as day, COUNT(*) as clicks FROM {$table} WHERE created_at >= %s GROUP BY DATE(created_at) ORDER BY day ASC",
$month
) );
if ( $daily ) {
$max_clicks = max( array_column( $daily, 'clicks' ) );
$days_map = [];
foreach ( $daily as $row ) {
$days_map[ $row->day ] = (int) $row->clicks;
}
echo '<div style="margin-bottom:20px;">';
echo '<h4 style="margin:0 0 8px;font-size:13px;color:#646970;">Clicks per Day (30 days)</h4>';
echo '<div style="display:flex;align-items:end;gap:2px;height:60px;">';
for ( $i = 29; $i >= 0; $i-- ) {
$day = gmdate( 'Y-m-d', strtotime( "-{$i} days" ) );
$clicks = $days_map[ $day ] ?? 0;
$height = $max_clicks > 0 ? max( 2, round( ( $clicks / $max_clicks ) * 60 ) ) : 2;
$color = $clicks > 0 ? '#2271b1' : '#dcdcde';
printf(
'<div title="%s: %s clicks" style="flex:1;height:%dpx;background:%s;border-radius:2px 2px 0 0;min-width:4px;"></div>',
esc_attr( $day ),
number_format_i18n( $clicks ),
$height,
$color
);
}
echo '</div></div>';
}
// ── Top 10 links ─────────────────────────────────────────────────────────
$top_links = $wpdb->get_results( $wpdb->prepare(
"SELECT slug, COUNT(*) as clicks FROM {$table} WHERE created_at >= %s GROUP BY slug ORDER BY clicks DESC LIMIT 10",
$month
) );
if ( $top_links ) {
echo '<h4 style="margin:0 0 8px;font-size:13px;color:#646970;">Top Links (30 days)</h4>';
echo '<table style="width:100%;border-collapse:collapse;font-size:13px;">';
$top_max = (int) $top_links[0]->clicks;
foreach ( $top_links as $i => $row ) {
$bar_width = $top_max > 0 ? round( ( (int) $row->clicks / $top_max ) * 100 ) : 0;
$bg = $i % 2 === 0 ? '#f6f7f7' : '#fff';
printf(
'<tr style="background:%s;"><td style="padding:4px 8px;"><code>%s</code></td><td style="padding:4px 8px;text-align:right;white-space:nowrap;">%s</td><td style="padding:4px 8px;width:40%%;"><div style="height:8px;background:#2271b1;border-radius:4px;width:%d%%;"></div></td></tr>',
$bg,
esc_html( $row->slug ),
number_format_i18n( (int) $row->clicks ),
$bar_width
);
}
echo '</table>';
}
// ── Top referrers ────────────────────────────────────────────────────────
$referrers = $wpdb->get_results( $wpdb->prepare(
"SELECT referrer, COUNT(*) as clicks FROM {$table} WHERE created_at >= %s AND referrer != '' GROUP BY referrer ORDER BY clicks DESC LIMIT 5",
$month
) );
if ( $referrers ) {
echo '<h4 style="margin:16px 0 8px;font-size:13px;color:#646970;">Top Referrers (30 days)</h4>';
echo '<ol style="margin:0;padding-left:20px;font-size:13px;">';
foreach ( $referrers as $row ) {
$host = wp_parse_url( $row->referrer, PHP_URL_HOST ) ?: $row->referrer;
printf(
'<li style="margin-bottom:4px;">%s <span style="color:#646970;">(%s)</span></li>',
esc_html( $host ),
number_format_i18n( (int) $row->clicks )
);
}
echo '</ol>';
}
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}How It Works
- Registers a dashboard widget via
wp_dashboard_setupwith permission checking - Displays four summary counters: Today, 7 Days, 30 Days, and All Time click totals
- Renders a 30-day sparkline bar chart showing daily click volume using inline CSS (no external JS libraries needed)
- Lists the top 10 most-clicked link slugs with click counts and visual bar indicators
- Shows the top 5 referrer domains with click counts
- Gracefully handles missing click log table with an informative message
Configuration Notes
The widget respects the gtlm_capabilities filter, so you can control which user roles see the stats. By default, any user with edit_posts can view the widget.
All queries use WordPress’s $wpdb with prepared statements for security. The widget is rendered with inline styles for compatibility across WordPress admin themes.
The widget reads from the table built in the click log lesson, so install that snippet first. For deeper analysis, export the table or push events to Matomo.