A pageview tool tells you who arrived. It says nothing about which link they left through. Two ways to count outbound clicks with no cookies, no Tag Manager and no consent banner, plus the naming rules that keep the data usable.
An outbound click is a custom event. A cookieless analytics tool counts the pageview when someone lands; leaving through a link is a separate action you have to ask it to record.
data-gm-event="outbound-github" to the link. One delegated listener records the click, including links rendered after page load.window.ghostmetrics.track('outbound-click', { section: 'footer' }) from your own handler when you need logic around the click.section string. Encode the destination in the name.If you only need the code, jump to the two methods below. If you are deciding whether this is worth doing, start with what you cannot see today.
Every analytics dashboard has an exit-pages report. It tells you the last page of a session: the page a visitor was on before they closed the tab or went somewhere else. It does not tell you which link took them there, or whether they left through a link at all. A visitor who closes the tab and one who clicks your partner link look identical in that report.
That gap matters on pages whose job is to send people elsewhere. Affiliate recommendations, a "view source on GitHub" link, a sponsor logo, a link to your app store listing, a booking page on a third-party scheduler, a docs page pointing at an external API reference. On these pages the outbound click is the conversion, and a pageview count alone cannot grade them. Two articles with identical traffic can send very different numbers of people to the same partner, and only a click event shows the difference.
The same applies in reverse for editorial decisions. If a resources page lists twenty external links, the click counts tell you which five earn their place and which fifteen are padding. That is a better editing signal than scroll depth alone, and the two combine well: a reader who reached the bottom of the page and clicked is a different reader from one who bounced at the headline.
Google Analytics 4 can log some outbound clicks automatically through its enhanced measurement setting; check current GA4 docs for exactly which clicks it counts and what it reports. The price of that convenience is the usual one: cookies by default, so consent is generally required for EU and UK visitors, and a heavy script. This guide covers the cookieless route, where you opt each link in and keep a tiny script. For the broader pattern of events without GA, see how to track custom events without Google Analytics.
The simplest approach needs no JavaScript of your own. Add data-gm-event to the anchor and give it a name:
<a href="https://github.com/your-org/your-repo"
data-gm-event="outbound-github"
rel="noopener">View the source on GitHub</a>
GhostMetrics installs one delegated listener on the document. It reads the attribute at click time, so it does not matter when the link was created. Links in server-rendered HTML work. So do links rendered after load by a client-side router, a comment widget, a modal or a lazy-loaded section. There is nothing to re-bind after a route change and no scan that runs once and misses late elements.
Three practical notes. First, put the attribute on the link itself; if your link wraps an icon and text, clicking either still counts because the listener walks up from the clicked element. That walk records every tagged ancestor, so do not tag both a card and a link inside it, or one click becomes two events. Second, the attribute records the event name only; a section value needs the programmatic call below. Third, you can put the attribute on non-link elements too, such as a button that opens an external URL through JavaScript, so you can use the same convention for "Buy on Amazon" buttons and plain text links.
This method is the right default for editorial sites, docs and landing pages. A content editor can tag a link in the CMS by adding an attribute, without a deploy and without asking an engineer. It is also easy to audit: a search for data-gm-event="outbound- in your repository or page source lists every tracked exit.
When the click needs logic around it, call the tracker yourself. The API is one function:
window.ghostmetrics.track('outbound-click', { section: 'pricing-footer' });
The first argument is the event name. The second is optional metadata, and only section is stored; any other keys you pass are ignored. Use it for placement ("header", "footer", "sidebar", "inline") so you can tell whether the same destination performs better in one slot than another.
Programmatic calls earn their keep in three cases: links generated from data (a list of 200 tool links rendered from JSON), links where you already have a click handler, and rules that apply to a whole class of links. The last one is the common request, "count every external link without tagging each one". Here is a small handler that does it:
document.addEventListener('click', function (e) {
var a = e.target.closest && e.target.closest('a[href]');
if (!a || a.closest('[data-gm-event]')) return; // already tracked
var url;
try { url = new URL(a.href, location.href); } catch (err) { return; }
if (!/^https?:$/.test(url.protocol)) return; // skip mailto:, tel:
if (url.origin === location.origin) return; // skip internal links
if (window.ghostmetrics) {
window.ghostmetrics.track('outbound-click', { section: url.hostname });
}
});
This records a single event name, outbound-click, and puts the destination hostname in section. The guard on data-gm-event prevents double counting when you have also tagged a link, or an element around it, by hand. The trade-off is where the hostname lives. The Custom Events panel groups by event name, so you will see one row, outbound-click, with a total. To split it by destination you read the raw export, where each row carries its section value; the manifest.json that ships with the export describes every column.
That is why the two methods are complementary. Use the attribute for the handful of links you care about by name (the sponsor, the app store, the signup on a partner site) so they show up as their own rows in the dashboard. Use the catch-all handler as a net underneath, and analyze the long tail in a spreadsheet.
Event names are the only thing the dashboard groups by, so they are your schema. Names invented ad hoc by three people over twelve months produce a panel full of near-duplicates. A convention costs nothing and fixes this up front. A workable scheme:
outbound-github, not Outbound GitHub Click.outbound- for exits, download- for files, cta- for your own calls to action. You can scan a panel and sort mentally.outbound-appstore survives a rewrite from "Get the app" to "Download".section (set through track(), since the attribute carries no section), so the footer link and the hero link to the same place share one row and split in the export.| Goal | Event name | section value | Where you read it |
|---|---|---|---|
| Affiliate link to one merchant | outbound-merchant-name | review-table or inline | Custom Events row; section split in export |
| Source code link | outbound-github | header or footer | Custom Events row |
| Social profile links | outbound-social | x, linkedin, youtube | One row; platform split in export |
| Third-party booking page | outbound-booking | pricing or contact | Custom Events row |
| Every external link, untagged | outbound-click | destination hostname | Export grouped by section |
A section value only exists when the event is sent with window.ghostmetrics.track(); a link tagged with the attribute alone records the name and the page.
Keep the named list short. Fifteen named exits is a dashboard you can read; two hundred is a spreadsheet, and the catch-all method plus the export handles that better. A good rule: name an exit if you would act on its count, and let the catch-all cover the rest.
Do this once per site. The steps assume the GhostMetrics tag is already in your <head>; if it is not, adding analytics to a static site covers the one-line install for most hosts.
data-gm-event to each anchor. If you use a CMS with a link dialog that cannot add attributes, switch that block to an HTML embed, or use the catch-all handler and read the export.gm.js. Leave the guard on data-gm-event in place.Step 6 catches the most confusing false alarm: your own browser blocking your own test. It is also a reminder that a cookieless tool still respects explicit opt-out signals, so the counts reflect visitors who did not send one.
A raw click count is nearly useless on its own. Three comparisons make it informative.
Clicks per pageview of the hosting page. If outbound-merchant-name fired 40 times and the review page had 1,000 pageviews, the link converts at 4 percent of views. The same 40 clicks on a page with 10,000 views is 0.4 percent, which is a layout problem, not a success. The Top Pages report gives you the denominator; the Custom Events panel gives you the numerator.
Clicks by source. Events carry the session's first-touch UTM source, medium and campaign, so a newsletter campaign that sends 300 visits and 45 partner clicks can be told apart from organic traffic that sends 300 visits and 6 clicks. The dashboard panel shows event name, count and last fired; to cut clicks by campaign, group the raw CSV by utm_campaign. Consistent UTM naming is what makes that grouping work; see the UTM parameters guide.
Clicks against scroll depth. The pages table shows the average scroll depth of the hosting page. If a link sits near the bottom of a page that readers typically leave halfway down, moving the link up is a cheaper fix than rewriting the paragraph around it. Scroll depth is recorded per pageview and clicks are separate events, so treat this as a page-level comparison, not a match of the same visits.
One more habit: look at the week-over-week trend, not a single day. Small sites see click counts in the single digits per link per day, and a swing from 3 to 6 is noise. Wait for enough volume that the comparison means something, and do not redesign a page on twelve clicks.
Every tool makes trade-offs. These are the ones you will notice.
section. If you link to many distinct URLs on the same domain and care which one, you need a name per URL or a section convention you can parse.rel attributes and referrer policy. Adding noreferrer hides your site from their analytics. If you want partners to credit you, do not add it, and add UTM parameters to links you are allowed to tag. Our direct traffic explainer covers how stripped referrers show up as "direct" on the receiving end.Know which gestures count. The tracker listens for the browser's click event: a normal click, Ctrl or Cmd-click and pressing Enter on a focused link all fire it, and the beacon is queued with sendBeacon before the browser navigates. A middle-click and "Open link in new tab" from the context menu do not fire click, so they are not counted. Test your own critical links in the browsers your readers use and compare against a partner-side count before you rely on a number for a payout decision.
The point of this table is the mechanism, not a feature checklist. Where a detail changes with plans or versions, check the vendor's current docs.
| Tool | Outbound clicks | Cookies / consent | Where the data goes |
|---|---|---|---|
| GhostMetrics | Opt in per link with data-gm-event, or track() from code; no automatic detection | No cookies, no localStorage; honors GPC and DNT | Custom Events panel; raw CSV/JSON export on every plan |
| Google Analytics 4 | Enhanced measurement can log outbound clicks automatically; check current docs for what it records | Cookies (_ga) by default; consent generally required in the EU/UK | Reports, explorations, free BigQuery export; thresholding can apply |
| Plausible | Offers outbound link tracking as an opt-in script option; check current docs | Cookieless | Goals and custom properties; check current docs |
| Umami | Custom events through a data attribute or a function call | Cookieless | Events view; full database if self-hosted |
| Cloudflare Web Analytics | No custom events | Cookieless | Not available for click tracking |
GA4 wins on automatic coverage and on tying clicks to Google Ads audiences if you run paid campaigns; that is a legitimate reason to keep it. If you want the cookieless version of the same measurement, with an attribute you control and no banner, see Google Analytics alternatives for the wider field, and analytics without a consent banner for what removing the banner involves.
GhostMetrics is cookieless web analytics we run on this site. Install one script tag and add data-gm-event to the links you care about. The free plan is free forever: one site, unlimited pageviews, real-time dashboard, every view, full history, including the Custom Events panel.
Need more sites or a public dashboard? Pro is $9.99/mo flat, unlimited pageviews, unlimited websites (fair-use ceiling of 250), with a 30-day free trial; a card starts the trial, nothing is charged for 30 days, and you can cancel anytime. Raw event export as CSV or JSON is on every plan, including free, so your click data is always yours to group and chart.
Start free, no card See the live demoDisclosure: GhostMetrics is built by Null Agency and we use it on this site, so we are not a neutral party about our own product. We kept the claims to what the free and paid plans do, and pointed to GA4 where its automatic outbound tracking and ads integration genuinely fit better. Third-party features and pricing change; check each vendor's current docs before deciding.