Ghost provides multiple methods to implement Google Analytics 4, each suited to different technical requirements and hosting configurations. This guide covers both code injection (easiest) and custom theme integration (most control).
Prerequisites
Before implementing GA4 on Ghost:
- GA4 Property Created - Set up your GA4 property in Google Analytics
- Measurement ID - Obtain your GA4 measurement ID (format:
G-XXXXXXXXXX) - Ghost Admin Access - Owner or Administrator role required for code injection
- Theme Access - Self-hosted Ghost or ability to upload custom themes (for theme method)
Method 1: Code Injection (Recommended for Most Users)
Code injection is the fastest way to add GA4 without modifying theme files. Changes survive theme updates and work on both Ghost(Pro) and self-hosted installations.
Step 1: Access Code Injection
- Log in to Ghost Admin (
yourdomain.com/ghost) - Navigate to Settings → Code Injection
- Locate the Site Header section
Step 2: Add GA4 Tracking Code
Paste the following code in the Site Header field:
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
Replace G-XXXXXXXXXX with your actual GA4 measurement ID.
Step 3: Save and Verify
- Click Save in the top-right corner
- Open your Ghost site in a new browser tab
- Use Google Tag Assistant or GA Debugger browser extension to verify tracking
- Check Google Analytics → Realtime to see active users
Enhanced Code Injection with Ghost Data
For better tracking context, include Ghost-specific metadata:
<!-- Google Analytics 4 with Ghost Metadata -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
// Configure GA4 with Ghost context
gtag('config', 'G-XXXXXXXXXX', {
'custom_map': {
'dimension1': 'ghost_content_type',
'dimension2': 'ghost_member_status'
}
});
// Set Ghost-specific dimensions
{{#post}}
gtag('event', 'page_view', {
'ghost_content_type': 'post',
'ghost_post_id': '{{id}}',
'ghost_author': '{{primary_author.name}}'
});
{{/post}}
{{#page}}
gtag('event', 'page_view', {
'ghost_content_type': 'page'
});
{{/page}}
{{#is "home"}}
gtag('event', 'page_view', {
'ghost_content_type': 'home'
});
{{/is}}
{{#member}}
gtag('set', 'user_properties', {
'ghost_member_status': 'member'
});
{{/member}}
</script>
Note: Code injection has limited Handlebars support. For full dynamic tracking, use the custom theme method.
Method 2: Custom Theme Integration
For maximum control and access to all Ghost Handlebars helpers, integrate GA4 directly into your theme.
Step 1: Download Your Theme
For Ghost(Pro):
- Navigate to Settings → Design
- Scroll to Installed Themes
- Click Download next to your active theme
For Self-Hosted:
cd /var/www/ghost/content/themes/
cp -r your-theme-name your-theme-name-ga4
Step 2: Create Analytics Partial
Create a new file: partials/analytics.hbs in your theme directory:
{{!-- partials/analytics.hbs --}}
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
// Base configuration
gtag('config', 'G-XXXXXXXXXX', {
'send_page_view': false, // We'll send custom page_view events
'custom_map': {
'dimension1': 'content_type',
'dimension2': 'author_name',
'dimension3': 'member_status',
'dimension4': 'visibility',
'dimension5': 'primary_tag'
}
});
// Build custom page_view event with Ghost data
var pageData = {
'page_title': '{{meta_title}}',
'page_location': '{{@site.url}}{{url}}',
'content_type': '{{#is "post"}}post{{/is}}{{#is "page"}}page{{/is}}{{#is "home"}}home{{/is}}{{#is "tag"}}tag{{/is}}{{#is "author"}}author{{/is}}',
{{#post}}
'author_name': '{{primary_author.name}}',
'visibility': '{{visibility}}',
'published_date': '{{published_at}}',
{{#primary_tag}}
'primary_tag': '{{name}}',
{{/primary_tag}}
{{/post}}
{{#member}}
'member_status': 'member',
'member_uuid': '{{uuid}}',
{{/member}}
{{^member}}
'member_status': 'visitor',
{{/member}}
};
// Send enhanced page_view
gtag('event', 'page_view', pageData);
</script>
Step 3: Include Partial in Theme
Edit default.hbs and add the partial before \{\{ghost_head\}\}:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{meta_title}}</title>
{{!-- Google Analytics --}}
{{> analytics}}
{{ghost_head}}
</head>
Step 4: Upload and Activate Theme
For Ghost(Pro):
- Zip your modified theme directory
- Navigate to Settings → Design → Change theme
- Upload the ZIP file
- Click Activate next to your uploaded theme
For Self-Hosted:
# Restart Ghost to load theme changes
ghost restart
Then activate via Ghost Admin → Settings → Design.
Step 5: Verify Installation
- Open your Ghost site
- Open browser DevTools → Console
- Look for GA4 debug messages (if using GA Debugger extension)
- Check Google Analytics → Realtime → Overview
- Verify custom dimensions appear in GA4 (may take 24-48 hours)
Performance Optimization
Preconnect to GA4 Domains
Add to your theme's <head> section (before analytics partial):
<link rel="preconnect" href="https://www.google-analytics.com">
<link rel="preconnect" href="https://www.googletagmanager.com">
Defer Loading for Non-Critical Tracking
<!-- Defer GA4 until after page load -->
<script>
window.addEventListener('load', function() {
var script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
script.async = true;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
});
</script>
Conditional Loading by Content Type
Only load GA4 on specific content types:
{{!-- Only track posts and pages, not admin or RSS --}}
{{#is "post, page"}}
{{> analytics}}
{{/is}}
Ghost Members Integration
Track member status and subscription tier:
<script>
{{#if @member}}
// Member-specific tracking
gtag('set', 'user_properties', {
'member_status': '{{#if @member.paid}}paid{{else}}free{{/if}}',
'member_created': '{{@member.created_at}}',
{{#if @member.subscriptions}}
'subscription_tier': '{{@member.subscriptions.[0].tier.name}}',
{{/if}}
});
{{else}}
// Visitor tracking
gtag('set', 'user_properties', {
'member_status': 'visitor'
});
{{/if}}
</script>
Ghost(Pro) vs. Self-Hosted Considerations
Ghost(Pro)
- Code Injection: Full support, easiest method
- Theme Integration: Requires theme upload via Admin
- CDN: Cloudflare caching may delay tracking updates (usually not an issue)
- SSL: Automatic HTTPS (GA4 requires secure sites)
Self-Hosted
- File Access: Direct theme editing in
/content/themes/ - Server-Side Tracking: Can implement GA4 Measurement Protocol
- Caching: Configure Nginx/Varnish to exclude tracking scripts
- Performance: Full control over async/defer strategies
Validation and Testing
Real-Time Testing
- Open Google Analytics → Realtime → Overview
- Visit your Ghost site in incognito mode
- Navigate between posts and pages
- Verify events appear in real-time report
Debug Mode
Add debug_mode to GA4 configuration:
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
Open browser DevTools → Console to see detailed event logging.
Tag Assistant
Install Google Tag Assistant Chrome extension:
- Click extension icon on your Ghost site
- Verify GA4 tag fires on page load
- Check for errors or warnings
- Review enhanced measurement events
Common Issues
GA4 Not Tracking
- Verify Measurement ID - Check for typos in
G-XXXXXXXXXX - Ad Blockers - Test in incognito without extensions
- Code Placement - Ensure script is in
<head>before\{\{ghost_head\}\} - Caching - Clear Ghost cache or wait 5-10 minutes for CDN refresh
Duplicate Tracking
- Code Injection + Theme - Remove one implementation
- Multiple GA4 Tags - Search theme for duplicate
gtagcalls - GTM Conflict - Don't use both GTM and direct GA4 implementation
Member Data Not Tracking
- Members Disabled - Ensure Ghost Members feature is enabled (Settings → Membership)
- Context Issues -
\{\{@member\}\}only works in theme, not code injection - Cache Problems - Member context may be cached; test with different browsers
Performance Impact
- Too Many Events - Limit custom events in theme
- Synchronous Loading - Use
asyncattribute on GA4 script - Large Data Layers - Keep custom dimensions minimal
Next Steps
- Google Analytics Event Tracking - Track Ghost-specific events
- Google Analytics E-commerce - Track Ghost memberships and subscriptions
- Troubleshooting Tracking Issues - Debug GA4 problems