Ghost Troubleshooting: Common Issues and Fixes | OpsBlu Docs

Ghost Troubleshooting: Common Issues and Fixes

Diagnose and fix common Ghost tracking issues. Covers script loading failures, missing events, data discrepancies, integration conflicts, and...

Platform-specific guides for diagnosing and fixing analytics and tracking issues on Ghost.

Common Issues

Events Not Firing

Debug why analytics events aren't being captured on Ghost.

Overview of Ghost Tracking Issues

Ghost's modern architecture and headless CMS capabilities create unique tracking challenges. The platform's code injection system, theme structure, member authentication, and caching mechanisms can all impact analytics implementation. Understanding Ghost's routing, Handlebars templating, and content API is crucial for effective troubleshooting.

Ghost-Specific Tracking Challenges

Code Injection Limitations

Ghost provides specific code injection points with limitations:

  • Site header - Runs on all pages but loads before DOM ready
  • Site footer - Ideal for tracking but may fire before page fully rendered
  • Post/page headers - Content-specific code injection
  • Ghost Admin restrictions - No direct file system access on hosted Ghost
  • Handlebars context - Template variables not accessible in injected code
  • Member-only content - Tracking gated content requires special handling

Theme-Level Issues

Ghost theme architecture affects tracking:

  • Handlebars partials - Tracking code in wrong partial may not execute
  • Ghost helpers - {{ghost_head}} and {{ghost_foot}} placement critical
  • Asset loading - Theme JavaScript may conflict with tracking scripts
  • AMP pages - Accelerated Mobile Pages require different tracking approach
  • Custom routes - Dynamic routes may not trigger standard page views
  • Membership tiers - Paid vs free content tracking differentiation

Caching and Performance

Ghost's caching mechanisms can interfere with tracking:

  • Frontend caching - Full page cache serves static HTML
  • Member authentication - Signed-in state affects cache and tracking
  • CDN integration - Ghost(Pro) CDN caches pages aggressively
  • Service workers - PWA features may cache tracking requests
  • Image optimization - Lazy-loaded images affect scroll tracking
  • Accelerated Mobile Pages - AMP caching and tracking restrictions

Member and Subscription Tracking

Ghost's membership features require special consideration:

  • Member authentication state - Logged in vs logged out users
  • Subscription tiers - Free, paid, comp member identification
  • Gated content - Tracking access to member-only posts
  • Newsletter signups - Form submissions and conversions
  • Portal interactions - Member portal events not automatically tracked
  • Stripe integrations - Payment tracking requires additional setup

Diagnostic Checklist for Tracking Issues

Work through these steps systematically:

1. Verify Code Injection Location

// Check if tracking code is in DOM
console.log('GTM in head:', document.querySelector('head script[src*="googletagmanager.com/gtm.js"]'));
console.log('GTM in body:', document.querySelector('body script[src*="googletagmanager.com/gtm.js"]'));

// Check Ghost context
console.log('Ghost context:', typeof ghost !== 'undefined' ? ghost : 'Ghost object not available');

// Verify code injection placement
var injectedScripts = document.querySelectorAll('script:not([src])');
console.log('Inline scripts count:', injectedScripts.length);

2. Check Theme Files

Access theme files (local development or download from Admin > Design):

# Check for ghost_head and ghost_foot
grep -r "ghost_head\|ghost_foot" *.hbs

# Verify default.hbs structure
cat default.hbs | grep -A5 -B5 "ghost_head"

# Check if tracking code in theme vs code injection
grep -r "googletagmanager\|analytics" *.hbs

3. Verify Ghost Version and Features

// Check Ghost version (visible in Ghost Admin footer)
// Navigate to: /ghost/#/settings

// Check if members feature enabled
// Look for Portal button in frontend

// Test member authentication
if (typeof window.ghost !== 'undefined' && window.ghost.member) {
    console.log('Member authenticated:', window.ghost.member);
} else {
    console.log('No member authentication detected');
}

4. Test on Different Page Types

  • Home page
  • Post pages
  • Page (static pages)
  • Tag archives
  • Author archives
  • Member-only content
  • AMP pages (if enabled)

5. Check for JavaScript Errors

// Monitor errors that might break tracking
window.addEventListener('error', function(e) {
    console.error('JavaScript error:', e.message, e.filename, e.lineno);
});

// Check for Ghost-specific errors
if (typeof ghost !== 'undefined') {
    console.log('Ghost API available');
} else {
    console.warn('Ghost object not found');
}

Verifying Tracking Code Loads Correctly

Method 1: Ghost Admin Code Injection Check

  1. Go to Ghost Admin > Settings > Code injection
  2. Verify tracking code in "Site Header" or "Site Footer"
  3. Check that code is properly formatted (no syntax errors)
  4. Ensure code uses proper <script> tags

Example proper format:

<!-- Site Header Code Injection -->
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>

Method 2: Browser Console Verification

// Open Console (F12) and run:

// Check if dataLayer exists
if (typeof dataLayer !== 'undefined') {
    console.log('✓ dataLayer exists:', dataLayer);
    console.log('  Items in dataLayer:', dataLayer.length);
} else {
    console.error('✗ dataLayer not found');
}

// Check Google Analytics
if (typeof gtag !== 'undefined') {
    console.log('✓ gtag (GA4) found');
} else if (typeof ga !== 'undefined') {
    console.log('✓ ga (Universal Analytics) found');
} else {
    console.error('✗ No Google Analytics found');
}

// Check Ghost-specific context
if (typeof window.ghost !== 'undefined') {
    console.log('✓ Ghost context:', window.ghost);
    if (window.ghost.member) {
        console.log('  Member data:', window.ghost.member);
    }
}

Method 3: Network Tab Verification

  1. Open DevTools Network tab (F12)
  2. Filter by "gtm" or "analytics"
  3. Navigate to a page
  4. Look for:

Verify payload includes:

  • Correct page path
  • Page title from Ghost post
  • Custom dimensions (if configured)

Method 4: Check Theme Template

View page source and verify:

<!DOCTYPE html>
<html>
<head>
    <!-- GTM should be here if in Site Header -->
    <!-- Look for: -->
    <script>...</script>

    <!-- Or check for {{ghost_head}} output -->
    {{{ghost_head}}}
</head>
<body>

    <!-- GTM noscript if using GTM -->
    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXX"></iframe></noscript>

    <!-- Content -->

    <!-- Tracking code here if in Site Footer -->
    <!-- Look for {{ghost_foot}} output -->
    {{{ghost_foot}}}
</body>
</html>

Browser Developer Tools Debugging Guide

Console Debugging

// Enable GA4 debug mode
gtag('config', 'G-XXXXXXXXXX', {
    'debug_mode': true
});

// Monitor dataLayer pushes
(function() {
    if (typeof dataLayer === 'undefined') {
        console.error('dataLayer not initialized');
        return;
    }

    var originalPush = dataLayer.push;
    dataLayer.push = function() {
        console.log('[dataLayer.push]', arguments[0]);
        return originalPush.apply(dataLayer, arguments);
    };
})();

// Track Ghost-specific events
document.addEventListener('DOMContentLoaded', function() {
    console.log('DOM ready, checking tracking...');

    // Check if on post page
    if (document.querySelector('.post-content')) {
        console.log('On post page');
        var postTitle = document.querySelector('.post-title');
        if (postTitle) {
            console.log('Post title:', postTitle.textContent);
        }
    }

    // Check member portal
    var portalLinks = document.querySelectorAll('[data-portal]');
    if (portalLinks.length > 0) {
        console.log('Member portal links found:', portalLinks.length);
    }
});

// Monitor Ghost Portal events
if (typeof window.ghost !== 'undefined') {
    window.addEventListener('message', function(e) {
        if (e.data && e.data.type === 'portal') {
            console.log('Ghost Portal event:', e.data);
        }
    });
}

Tracking Member Events

// Track member signup/signin
(function() {
    var checkMember = function() {
        if (typeof window.ghost !== 'undefined' && window.ghost.member) {
            console.log('Member detected:', window.ghost.member);

            if (typeof dataLayer !== 'undefined') {
                dataLayer.push({
                    'event': 'member_authenticated',
                    'member_status': window.ghost.member.paid ? 'paid' : 'free',
                    'member_id': window.ghost.member.uuid
                });
            }
        }
    };

    // Check immediately
    checkMember();

    // Check after potential portal interactions
    window.addEventListener('message', function(e) {
        if (e.data && e.data.type === 'portal') {
            setTimeout(checkMember, 500);
        }
    });
})();

Network Tab Debugging

Filter for tracking requests:

gtm
analytics
collect
/g/collect

Check request headers for:

Inspect payload for:

  • Page location (dl parameter)
  • Document title (dt parameter)
  • Custom dimensions (cd parameters)
  • Events (en, ea, el parameters for UA; en parameter for GA4)

Application Tab (Cookies and Storage)

Check tracking cookies:

  • _ga - Google Analytics ID
  • _gid - Session ID
  • _gat - Throttle requests

Check Ghost cookies:

  • ghost-members-ssr - Member session

Verify cookie domain matches site domain.

Common Symptoms and Their Causes

Symptom Likely Cause Solution
No tracking on any page Code not injected or syntax error Check code injection in Ghost Admin, verify script tags
Tracking only on homepage Code injected on wrong page type Move code to Site Header/Footer, not post/page injection
Delayed tracking (5-10 seconds) Code in footer, slow theme JS Move to header or optimize theme scripts
Duplicate page views Code in both theme and code injection Audit theme files, remove duplicate code
Member-only pages not tracking Authentication state not considered Add member context to tracking code
Page views work, events don't fire Events defined in theme, not loaded Move event tracking to code injection
AMP pages not tracking Standard GA code doesn't work on AMP Use amp-analytics component
Tag archives missing title Ghost context not passed to dataLayer Extract title from DOM or Ghost helpers
Portal signup not tracked No listener for portal events Add message event listener for portal
Tracking works locally, not on Ghost(Pro) CDN caching or environment difference Purge CDN cache, check production code injection
Newsletter signups not tracked Form submission happens outside page Use Portal message events or redirect tracking
Stripe payments not tracked Payment happens in iframe Use Stripe webhooks or Ghost webhooks

Tag Manager Troubleshooting for Ghost

GTM Container Placement

Recommended setup in Ghost:

Site Header Code Injection:

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->

Site Footer Code Injection (for noscript):

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

DataLayer Configuration for Ghost

Push Ghost-specific data to dataLayer:

<script>
  // Initialize dataLayer before GTM
  window.dataLayer = window.dataLayer || [];

  // Push page-specific data
  dataLayer.push({
    'ghost_page_type': document.body.className.split(' ').find(c => c.startsWith('page-') || c.startsWith('post-') || c === 'home'),
    'ghost_visibility': document.querySelector('[data-visibility]')?.getAttribute('data-visibility') || 'public',
    'content_type': document.querySelector('.post-content') ? 'post' : 'page'
  });

  // Push member data if available
  if (typeof window.ghost !== 'undefined' && window.ghost.member) {
    dataLayer.push({
      'member_status': window.ghost.member.paid ? 'paid' : 'free',
      'member_uuid': window.ghost.member.uuid
    });
  }
</script>

GTM Preview Mode Issues

Problem: Preview mode doesn't connect

Solutions:

  1. Ensure code injection saved and published
  2. Clear browser cache and Ghost cache
  3. Check browser extensions aren't blocking GTM debugger
  4. Verify GTM container ID matches

Debug script:

// Check if GTM loaded correctly
console.log('GTM Container:', window.google_tag_manager);
if (window.google_tag_manager && window.google_tag_manager['GTM-XXXXXX']) {
    console.log('✓ GTM container loaded');
} else {
    console.error('✗ GTM container not found');
}

Variable Configuration for Ghost

Useful GTM variables for Ghost:

  1. Post Title - Data Layer Variable or DOM scraping

    • Variable Type: Data Layer Variable or DOM Element
    • Name: postTitle
    • Selector: .post-title or .article-title
  2. Author Name - DOM scraping

    • Variable Type: DOM Element
    • Selector: .author-name or [rel="author"]
  3. Member Status - Data Layer Variable

    • Variable Type: Data Layer Variable
    • Name: member_status
  4. Content Visibility - Data Layer Variable

    • Variable Type: Data Layer Variable
    • Name: ghost_visibility

Single Page Application Behavior

While Ghost is not a true SPA, certain features behave similarly:

Infinite Scroll Tracking

Many Ghost themes use infinite scroll:

// Track infinite scroll page views
(function() {
    var observedPosts = new Set();

    var observer = new IntersectionObserver(function(entries) {
        entries.forEach(function(entry) {
            if (entry.isIntersecting) {
                var post = entry.target;
                var postUrl = post.querySelector('a.post-link')?.href;

                if (postUrl && !observedPosts.has(postUrl)) {
                    observedPosts.add(postUrl);

                    if (typeof dataLayer !== 'undefined') {
                        dataLayer.push({
                            'event': 'virtual_pageview',
                            'page_path': new URL(postUrl).pathname,
                            'page_title': post.querySelector('.post-title')?.textContent
                        });
                    }

                    console.log('Infinite scroll: new post viewed', postUrl);
                }
            }
        });
    }, { threshold: 0.5 });

    // Observe all post cards
    document.querySelectorAll('.post-card').forEach(function(post) {
        observer.observe(post);
    });
})();

Portal Navigation Tracking

Track Ghost Portal interactions:

// Track portal open/close and actions
window.addEventListener('message', function(event) {
    if (event.data && event.data.type && event.data.type.startsWith('portal-')) {
        console.log('Portal event:', event.data);

        if (typeof dataLayer !== 'undefined') {
            dataLayer.push({
                'event': 'portal_interaction',
                'portal_action': event.data.type,
                'portal_data': event.data
            });
        }
    }
});

Ghost Privacy Settings

Ghost has built-in privacy features:

  1. Check Ghost privacy settings: Admin > Settings > Privacy
  2. Verify tracking scripts not blocked by content security policy
  3. Check if members-only content affects tracking

Example consent banner integration:

<!-- In Site Header Code Injection -->
<script>
  // Simple cookie consent check
  function hasTrackingConsent() {
    return localStorage.getItem('cookie_consent') === 'accepted';
  }

  function initializeTracking() {
    if (hasTrackingConsent()) {
      // Initialize GTM or GA
      console.log('Tracking consent granted, initializing...');

      if (typeof gtag !== 'undefined') {
        gtag('consent', 'update', {
          'analytics_storage': 'granted'
        });
      }
    } else {
      console.log('No tracking consent');
    }
  }

  // Check on page load
  document.addEventListener('DOMContentLoaded', initializeTracking);

  // Listen for consent changes
  window.addEventListener('cookie_consent_changed', function(e) {
    if (e.detail === 'accepted') {
      initializeTracking();
    }
  });
</script>

GDPR Compliance for Members

// Don't track member email or PII by default
if (window.ghost && window.ghost.member) {
    // Only push anonymized member ID
    dataLayer.push({
        'member_id_hash': window.ghost.member.uuid, // UUID is already anonymized
        'member_tier': window.ghost.member.paid ? 'paid' : 'free'
        // Don't include: email, name, or other PII
    });
}

Ghost-Specific Integration Conflicts

Common Theme Conflicts

jQuery version conflicts:

// Check jQuery version
console.log('jQuery version:', typeof jQuery !== 'undefined' ? jQuery.fn.jquery : 'not loaded');

// Some themes load old jQuery that conflicts with tracking
// Use noConflict if needed
var $j = jQuery.noConflict();

Theme JavaScript errors:

// Wrap tracking code in try-catch to prevent theme errors from breaking it
try {
    // Your tracking code here
    dataLayer.push({ /* ... */ });
} catch(e) {
    console.error('Tracking error:', e);
}

Known Problematic Integrations

Ghost Search Plugins:

  • Ghost Search or Sodo Search may interfere with page view tracking
  • Add event listeners for search result clicks

Member Portal Customizations:

  • Custom portal scripts may conflict
  • Test portal interactions thoroughly

Comment Systems (Disqus, Commento):

  • Comments load in iframe, won't trigger events automatically
  • Use iframe communication or comment system callbacks

Event Tracking Validation Steps

1. Create Event Tracking Code

Add to Site Footer code injection:

<script>
(function() {
    'use strict';

    // Wait for DOM ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    function init() {
        // Track outbound links
        document.addEventListener('click', function(e) {
            var link = e.target.closest('a');
            if (!link) return;

            var href = link.href;
            if (href && href.startsWith('http') && !href.includes(window.location.hostname)) {
                console.log('Outbound click:', href);

                if (typeof dataLayer !== 'undefined') {
                    dataLayer.push({
                        'event': 'outbound_click',
                        'outbound_url': href,
                        'outbound_text': link.textContent.trim()
                    });
                }
            }
        });

        // Track newsletter signup
        var portalTriggers = document.querySelectorAll('[data-portal="signup"]');
        portalTriggers.forEach(function(trigger) {
            trigger.addEventListener('click', function() {
                console.log('Portal signup clicked');

                if (typeof dataLayer !== 'undefined') {
                    dataLayer.push({
                        'event': 'newsletter_signup_intent',
                        'signup_location': trigger.getAttribute('data-location') || 'unknown'
                    });
                }
            });
        });

        // Track scroll depth
        var scrollDepths = [25, 50, 75, 90];
        var triggered = {};

        window.addEventListener('scroll', function() {
            var scrollPercent = (window.scrollY + window.innerHeight) / document.body.scrollHeight * 100;

            scrollDepths.forEach(function(depth) {
                if (scrollPercent >= depth && !triggered[depth]) {
                    triggered[depth] = true;

                    if (typeof dataLayer !== 'undefined') {
                        dataLayer.push({
                            'event': 'scroll_depth',
                            'scroll_depth_threshold': depth,
                            'page_path': window.location.pathname
                        });
                    }

                    console.log('Scroll depth:', depth + '%');
                }
            });
        });

        console.log('Ghost event tracking initialized');
    }
})();
</script>

2. Validate Events Fire Correctly

// Monitor all events for 30 seconds
(function() {
    var events = [];
    var originalPush = dataLayer.push;

    dataLayer.push = function(obj) {
        if (obj.event) {
            events.push({
                time: new Date().toISOString(),
                event: obj.event,
                data: obj
            });
            console.log('Event captured:', obj.event);
        }
        return originalPush.apply(dataLayer, arguments);
    };

    setTimeout(function() {
        dataLayer.push = originalPush;
        console.log('Event monitoring complete. Total events:', events.length);
        console.table(events);
    }, 30000);

    console.log('Monitoring dataLayer events for 30 seconds...');
})();

3. Test Member-Specific Events

// Test member authentication event
if (window.ghost && window.ghost.member) {
    console.log('Testing member event...');

    dataLayer.push({
        'event': 'test_member_event',
        'member_status': window.ghost.member.paid ? 'paid' : 'free',
        'test': true
    });

    console.log('Check GTM Preview or GA4 DebugView for event');
}

Membership and Subscription Tracking

Track Member Signups

// Monitor portal messages for successful signup
window.addEventListener('message', function(e) {
    if (e.data.type === 'portal-action' && e.data.action === 'signup') {
        console.log('Member signup detected:', e.data);

        if (typeof dataLayer !== 'undefined') {
            dataLayer.push({
                'event': 'member_signup',
                'signup_type': e.data.plan || 'free'
            });
        }
    }
});

Track Content Upgrades

// Track when free members hit paywalls
document.addEventListener('DOMContentLoaded', function() {
    var paywall = document.querySelector('.gh-post-upgrade-cta');

    if (paywall) {
        console.log('Paywall detected on page');

        if (typeof dataLayer !== 'undefined') {
            dataLayer.push({
                'event': 'paywall_view',
                'post_title': document.querySelector('.post-title')?.textContent,
                'post_url': window.location.pathname
            });
        }

        // Track upgrade button clicks
        var upgradeBtn = paywall.querySelector('a[data-portal="signup"]');
        if (upgradeBtn) {
            upgradeBtn.addEventListener('click', function() {
                dataLayer.push({
                    'event': 'paywall_upgrade_click',
                    'post_title': document.querySelector('.post-title')?.textContent
                });
            });
        }
    }
});

Track Subscription Tiers

// Differentiate tracking by member tier
function getMemberTier() {
    if (!window.ghost || !window.ghost.member) {
        return 'anonymous';
    }

    if (window.ghost.member.paid) {
        return 'paid_member';
    }

    return 'free_member';
}

// Push to dataLayer on each page
if (typeof dataLayer !== 'undefined') {
    dataLayer.push({
        'user_tier': getMemberTier()
    });
}

CDN and Caching Troubleshooting

Ghost(Pro) CDN Issues

Problem: Tracking code changes not appearing

Solutions:

  1. Purge Ghost cache: Settings > Labs > Delete all content (for testing only!)
  2. Wait for cache expiration: Changes may take up to 10 minutes
  3. Use cache-busting query parameters for testing:
    • Visit: yoursite.com/?nocache=1

Check if page is cached:

// Check response headers in Network tab
// Look for: X-Cache, CF-Cache-Status, Age
fetch(window.location.href)
    .then(r => {
        console.log('Cache headers:', {
            'X-Cache': r.headers.get('X-Cache'),
            'Age': r.headers.get('Age'),
            'Cache-Control': r.headers.get('Cache-Control')
        });
    });

Custom CDN Configuration

If using Cloudflare or similar:

  1. Bypass cache for query parameters:

    • Configure cache rules to bypass for ?utm_*, ?gclid=, etc.
  2. Ensure HTML is not cached too aggressively:

    • Set reasonable cache TTL for HTML
    • Respect origin cache headers from Ghost
  3. Purge CDN after code injection changes:

    • Purge entire cache or specific URLs
    • Wait 2-3 minutes for global propagation

Debugging Code Examples

Complete Ghost Diagnostic Script

(function() {
    console.log('=== Ghost Analytics Diagnostic ===');

    // Ghost environment
    console.log('Ghost version:', document.querySelector('meta[name="generator"]')?.content || 'Unknown');
    console.log('Ghost context available:', typeof window.ghost !== 'undefined');

    if (window.ghost) {
        console.log('Ghost config:', window.ghost);
        if (window.ghost.member) {
            console.log('Member authenticated:', {
                uuid: window.ghost.member.uuid,
                paid: window.ghost.member.paid
            });
        }
    }

    // Tracking setup checks
    var checks = {
        'dataLayer exists': typeof dataLayer !== 'undefined',
        'dataLayer items': typeof dataLayer !== 'undefined' ? dataLayer.length : 0,
        'GTM loaded': !!document.querySelector('script[src*="googletagmanager.com/gtm.js"]'),
        'GA4 loaded': !!document.querySelector('script[src*="googletagmanager.com/gtag/js"]'),
        'UA loaded': !!document.querySelector('script[src*="google-analytics.com/analytics.js"]'),
        'jQuery available': typeof jQuery !== 'undefined',
        'jQuery version': typeof jQuery !== 'undefined' ? jQuery.fn.jquery : 'N/A'
    };

    console.table(checks);

    // Page context
    var pageType = 'unknown';
    if (document.body.classList.contains('home')) pageType = 'home';
    else if (document.body.classList.contains('post-template')) pageType = 'post';
    else if (document.body.classList.contains('page-template')) pageType = 'page';
    else if (document.body.classList.contains('tag-template')) pageType = 'tag';
    else if (document.body.classList.contains('author-template')) pageType = 'author';

    console.log('Page type:', pageType);
    console.log('Page classes:', document.body.className);

    // Check for common issues
    var issues = [];

    if (typeof dataLayer === 'undefined') {
        issues.push('dataLayer not initialized - tracking likely not working');
    }

    if (document.querySelectorAll('script[src*="gtm.js"]').length > 1) {
        issues.push('Multiple GTM scripts detected - possible duplicate tracking');
    }

    var inlineScripts = Array.from(document.querySelectorAll('script:not([src])')).filter(s =>
        s.textContent.includes('googletagmanager') || s.textContent.includes('analytics')
    );
    if (inlineScripts.length > 2) {
        issues.push('Multiple inline tracking scripts - check for duplicates');
    }

    if (issues.length > 0) {
        console.warn('Issues detected:');
        issues.forEach(issue => console.warn('- ' + issue));
    } else {
        console.log('✓ No obvious issues detected');
    }

    console.log('=== Diagnostic Complete ===');
})();

Test Event Function

function testGhostEvent(eventName, eventData) {
    console.log('Testing Ghost event:', eventName);

    if (typeof dataLayer === 'undefined') {
        console.error('dataLayer not available - tracking not initialized');
        return false;
    }

    var eventObj = {
        'event': eventName,
        'timestamp': new Date().toISOString(),
        'test': true,
        'page_type': document.body.className.split(' ').find(c => c.includes('template')) || 'unknown'
    };

    // Merge custom data
    Object.assign(eventObj, eventData);

    dataLayer.push(eventObj);
    console.log('✓ Event pushed:', eventObj);

    // Verify it's in dataLayer
    setTimeout(function() {
        var found = dataLayer.some(item => item.event === eventName && item.test === true);
        console.log(found ? '✓ Event confirmed in dataLayer' : 'Event not found in dataLayer');
    }, 100);

    return true;
}

// Usage examples:
testGhostEvent('test_page_view', { page_title: document.title });
testGhostEvent('test_button_click', { button_id: 'test-button' });

When to Contact Support

Contact Ghost support or your analytics vendor when:

1. Code Injection Not Working

Contact Ghost support if:

  • Code saved in Admin but not appearing in page source
  • Code injection randomly disappearing
  • Different behavior on Ghost(Pro) vs self-hosted

Provide:

  • Ghost version (from Admin footer)
  • Hosting type (Ghost(Pro), Digital Ocean, self-hosted)
  • Screenshot of code injection settings
  • Page source showing missing code

2. Theme-Specific Issues

Contact theme developer if:

  • Tracking works with default Casper theme but not custom theme
  • Theme JavaScript errors breaking tracking
  • Theme doesn't include {{ghost_head}} or {{ghost_foot}}

Provide:

  • Theme name and version
  • Browser console errors
  • Comparison with default theme

3. Member Portal Tracking Fails

Contact if:

  • Portal events not accessible
  • Member data not available in window.ghost
  • Stripe integration tracking issues

Provide:

  • Members feature configuration
  • Subscription tiers setup
  • Console log of window.ghost object

4. Performance Issues

Contact if:

  • Tracking code significantly slowing page load
  • Conflicts with Ghost's performance features
  • Service worker caching issues

Provide:

  • Lighthouse or WebPageTest results
  • Network waterfall screenshots
  • Tracking code used

Information to Gather Before Contacting Support

// Run this diagnostic and share output
(function() {
    var diagnostics = {
        ghostVersion: document.querySelector('meta[name="generator"]')?.content,
        url: window.location.href,
        ghostContext: typeof window.ghost !== 'undefined' ? {
            apiUrl: window.ghost.apiUrl,
            memberAvailable: !!window.ghost.member
        } : 'not available',
        trackingSetup: {
            dataLayerExists: typeof dataLayer !== 'undefined',
            gtmLoaded: !!document.querySelector('script[src*="gtm.js"]'),
            ga4Loaded: !!document.querySelector('script[src*="gtag/js"]')
        },
        pageContext: {
            bodyClasses: document.body.className,
            title: document.title,
            postContent: !!document.querySelector('.post-content')
        },
        errors: []
    };

    // Check for JS errors
    var errors = [];
    window.addEventListener('error', function(e) {
        errors.push({
            message: e.message,
            source: e.filename,
            line: e.lineno
        });
    });

    setTimeout(function() {
        diagnostics.errors = errors;
        console.log('=== Ghost Diagnostics for Support ===');
        console.log(JSON.stringify(diagnostics, null, 2));
        console.log('Copy the above JSON and provide to support');
    }, 3000);
})();

General Fixes

For universal tracking concepts, see the Global Tracking Issues Hub.