Ghost provides unique user interactions through its Members, Newsletter, and Portal systems. This guide shows how to track Ghost-specific events with GA4 for comprehensive user behavior analysis.
Ghost Event Categories
Member Events
- Member Signup - New member registration via Ghost Portal
- Member Login - Existing member authentication
- Member Logout - Member session termination
- Profile Update - Member profile or preference changes
Subscription Events
- Newsletter Signup - Newsletter subscription via Portal
- Newsletter Unsubscribe - Newsletter opt-out
- Tier Upgrade - Free to paid member conversion
- Tier Downgrade - Paid to free member change
- Subscription Cancelled - Paid membership cancellation
Content Events
- Post View - Individual post page view
- Member Content Access - Member-only content viewed
- Paid Content Access - Paid-tier content viewed
- Comment Submitted - Native Ghost comment posted
- Bookmark Added - Content saved/bookmarked
Portal Events
- Portal Opened - Ghost Portal modal triggered
- Portal Closed - Portal dismissed without action
- Payment Form Viewed - Checkout step initiated
Event Tracking Implementation
Method 1: Ghost Portal Events (Recommended)
Ghost Portal emits custom events you can capture with GA4. Add this to your theme's default.hbs:
{{!-- In default.hbs, before </body> --}}
<script>
// Listen for Ghost Portal events
window.addEventListener('portal-ready', function() {
console.log('Ghost Portal initialized');
});
// Member signup event
window.addEventListener('portal-signup', function(event) {
if (typeof gtag !== 'undefined') {
gtag('event', 'sign_up', {
method: 'ghost_portal',
event_category: 'member',
event_label: 'member_signup'
});
}
});
// Member signin event
window.addEventListener('portal-signin', function(event) {
if (typeof gtag !== 'undefined') {
gtag('event', 'login', {
method: 'ghost_portal',
event_category: 'member',
event_label: 'member_login'
});
}
});
// Member signout event
window.addEventListener('portal-signout', function(event) {
if (typeof gtag !== 'undefined') {
gtag('event', 'logout', {
event_category: 'member',
event_label: 'member_logout'
});
}
});
// Newsletter subscription
window.addEventListener('portal-subscribe', function(event) {
if (typeof gtag !== 'undefined') {
gtag('event', 'newsletter_signup', {
event_category: 'newsletter',
event_label: 'newsletter_subscribe'
});
}
});
// Portal opened
window.addEventListener('portal-open', function(event) {
if (typeof gtag !== 'undefined') {
gtag('event', 'portal_open', {
event_category: 'engagement',
event_label: 'ghost_portal_opened'
});
}
});
// Portal closed
window.addEventListener('portal-close', function(event) {
if (typeof gtag !== 'undefined') {
gtag('event', 'portal_close', {
event_category: 'engagement',
event_label: 'ghost_portal_closed'
});
}
});
</script>
Method 2: Content Interaction Tracking
Track content engagement specific to Ghost:
{{!-- In post.hbs template --}}
<script>
{{#post}}
// Track post view with metadata
if (typeof gtag !== 'undefined') {
gtag('event', 'view_item', {
content_type: 'post',
item_id: '{{id}}',
item_name: '{{title}}',
item_category: '{{primary_tag.name}}',
item_brand: '{{primary_author.name}}',
{{#if visibility}}
content_visibility: '{{visibility}}',
{{/if}}
{{#member}}
viewer_type: 'member',
{{/member}}
{{^member}}
viewer_type: 'visitor',
{{/member}}
});
}
{{/post}}
</script>
Method 3: Member Content Access
Track when members access gated content:
{{!-- Track member-only content access --}}
{{#has visibility="members"}}
<script>
{{#member}}
// Member accessed members-only content
if (typeof gtag !== 'undefined') {
gtag('event', 'member_content_access', {
event_category: 'content',
event_label: 'members_only_viewed',
content_id: '{{post.id}}',
content_title: '{{post.title}}',
member_tier: '{{#if @member.paid}}paid{{else}}free{{/if}}'
});
}
{{/member}}
</script>
{{/has}}
{{!-- Track paid-only content access --}}
{{#has visibility="paid"}}
<script>
{{#member}}
{{#if @member.paid}}
// Paid member accessed premium content
if (typeof gtag !== 'undefined') {
gtag('event', 'premium_content_access', {
event_category: 'content',
event_label: 'paid_content_viewed',
content_id: '{{post.id}}',
content_title: '{{post.title}}',
{{#if @member.subscriptions}}
subscription_tier: '{{@member.subscriptions.[0].tier.name}}',
{{/if}}
});
}
{{/if}}
{{/member}}
</script>
{{/has}}
Method 4: Newsletter Signup Tracking
For email newsletter forms (separate from Portal):
{{!-- In theme where newsletter forms exist --}}
<form class="newsletter-form" id="ghost-newsletter-form">
<input type="email" name="email" placeholder="Your email address" required>
<button type="submit">Subscribe</button>
</form>
<script>
document.getElementById('ghost-newsletter-form').addEventListener('submit', function(e) {
// Track newsletter form submission
if (typeof gtag !== 'undefined') {
gtag('event', 'generate_lead', {
event_category: 'newsletter',
event_label: 'newsletter_form_submit',
method: 'inline_form'
});
}
});
</script>
Method 5: Reading Progress Tracking
Track how far users read posts:
{{!-- In post.hbs --}}
<script>
{{#post}}
(function() {
var milestones = [25, 50, 75, 100];
var tracked = [];
function trackReadingProgress() {
var articleHeight = document.querySelector('article').offsetHeight;
var windowHeight = window.innerHeight;
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
var scrollPercent = ((scrollTop + windowHeight) / articleHeight) * 100;
milestones.forEach(function(milestone) {
if (scrollPercent >= milestone && tracked.indexOf(milestone) === -1) {
tracked.push(milestone);
if (typeof gtag !== 'undefined') {
gtag('event', 'scroll_depth', {
event_category: 'engagement',
event_label: 'reading_progress',
value: milestone,
post_id: '{{id}}',
post_title: '{{title}}'
});
}
}
});
}
var scrollTimeout;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(trackReadingProgress, 100);
});
})();
{{/post}}
</script>
Method 6: Comment Tracking
If using Ghost native comments:
<script>
// Track comment submission (requires Ghost Comments feature)
document.addEventListener('click', function(e) {
if (e.target && e.target.matches('.comment-submit-button')) {
if (typeof gtag !== 'undefined') {
gtag('event', 'comment_submit', {
event_category: 'engagement',
event_label: 'ghost_comment_posted',
{{#post}}
post_id: '{{id}}',
post_title: '{{title}}'
{{/post}}
});
}
}
});
</script>
Recommended Event Configuration
GA4 Custom Events Setup
Create these custom events in GA4 → Configure → Events → Create Event:
member_signup
- Event name:
sign_up - Match condition:
method = ghost_portal
- Event name:
member_content_access
- Event name:
member_content_access - Parameter:
content_visibility = members
- Event name:
premium_content_access
- Event name:
premium_content_access - Parameter:
content_visibility = paid
- Event name:
newsletter_signup
- Event name:
newsletter_signup - Parameter:
methodexists
- Event name:
reading_depth
- Event name:
scroll_depth - Parameter:
value >= 75
- Event name:
Custom Dimensions Configuration
Set up these custom dimensions in GA4 → Configure → Custom Definitions:
| Dimension Name | Parameter | Scope |
|---|---|---|
| Content Visibility | content_visibility | Event |
| Member Tier | member_tier | User |
| Subscription Tier | subscription_tier | User |
| Content ID | content_id | Event |
| Author Name | item_brand | Event |
| Primary Tag | item_category | Event |
| Viewer Type | viewer_type | Event |
Advanced Event Tracking
Track Member Journey
Complete member lifecycle tracking:
<script>
{{#member}}
// Set user ID for cross-session tracking
if (typeof gtag !== 'undefined') {
gtag('set', 'user_id', '{{uuid}}');
// Set member properties
gtag('set', 'user_properties', {
'member_status': '{{#if paid}}paid{{else}}free{{/if}}',
'member_created': '{{created_at}}',
{{#if subscriptions}}
'subscription_tier': '{{subscriptions.[0].tier.name}}',
'subscription_status': '{{subscriptions.[0].status}}',
{{/if}}
});
}
{{/member}}
</script>
Track Outbound Link Clicks
Monitor external links in Ghost content:
<script>
document.addEventListener('click', function(e) {
var link = e.target.closest('a');
if (!link) return;
var href = link.getAttribute('href');
var isExternal = href && (href.indexOf('http') === 0) && (href.indexOf(location.hostname) === -1);
if (isExternal && typeof gtag !== 'undefined') {
gtag('event', 'click', {
event_category: 'outbound',
event_label: href,
transport_type: 'beacon',
{{#post}}
post_id: '{{id}}',
post_title: '{{title}}'
{{/post}}
});
}
});
</script>
Track Search Usage
If Ghost search is enabled:
<script>
// Track Ghost search usage
document.querySelector('.search-input')?.addEventListener('keyup', function(e) {
if (e.key === 'Enter' && this.value.length > 0) {
if (typeof gtag !== 'undefined') {
gtag('event', 'search', {
search_term: this.value,
event_category: 'site_search'
});
}
}
});
</script>
Testing Event Tracking
Debug Mode
Enable GA4 debug mode to see events in console:
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
DebugView in GA4
- Navigate to Admin → DebugView in GA4
- Trigger events on your Ghost site
- Watch events appear in real-time
- Verify parameters and values
Browser Console Testing
Test individual events manually:
// Test member signup event
gtag('event', 'sign_up', {
method: 'ghost_portal',
event_category: 'member'
});
// Verify dataLayer
console.log(window.dataLayer);
Performance Considerations
Throttle Scroll Events
Prevent excessive scroll tracking:
var scrollTimeout;
var lastScrollEvent = 0;
var scrollThrottle = 1000; // 1 second
window.addEventListener('scroll', function() {
var now = Date.now();
if (now - lastScrollEvent < scrollThrottle) return;
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(function() {
trackReadingProgress();
lastScrollEvent = now;
}, 100);
});
Batch Events
For high-frequency events, batch before sending:
var eventQueue = [];
var eventBatchSize = 5;
function queueEvent(eventName, params) {
eventQueue.push({name: eventName, params: params});
if (eventQueue.length >= eventBatchSize) {
flushEvents();
}
}
function flushEvents() {
eventQueue.forEach(function(event) {
gtag('event', event.name, event.params);
});
eventQueue = [];
}
// Flush on page unload
window.addEventListener('beforeunload', flushEvents);
Common Issues
Events Not Appearing in GA4
- Delay - Events can take 24-48 hours to appear in standard reports (use DebugView for real-time)
- Filters - Check GA4 data filters aren't excluding events
- Ad Blockers - Test in incognito without extensions
- Undefined gtag - Ensure GA4 loads before event tracking code
Duplicate Events
- Multiple Listeners - Remove duplicate event listeners
- Code Injection + Theme - Consolidate tracking code in one location
- Portal Events Firing Twice - Use
{ once: true }option on event listeners
Member Context Missing
- Cache Issues - Ghost caches pages; member context may be stale
- Code Injection Limitation - Handlebars helpers limited in code injection
- Use Theme Integration - Move to custom theme for full member access
Performance Degradation
- Too Many Events - Limit scroll/interaction tracking
- Unthrottled Listeners - Add debounce/throttle to scroll and resize
- Large Payloads - Keep event parameters minimal
Next Steps
- Google Analytics E-commerce Tracking - Track Ghost memberships and subscriptions
- GTM Data Layer - Use GTM for event management
- Troubleshooting Events - Debug tracking issues