Carrd-Specific GA4 Event Tracking | OpsBlu Docs

Carrd-Specific GA4 Event Tracking

Comprehensive guide to tracking Carrd forms, link clicks, scroll depth, and custom events with Google Analytics 4.

This guide covers tracking Carrd-specific interactions with Google Analytics 4 (GA4), including forms, button clicks, scroll depth, and outbound links on your one-page Carrd sites.

Prerequisites

  • GA4 installed on your Carrd site via embed method
  • Carrd Pro account (Pro Lite, Pro Standard, or Pro Plus)
  • Published Carrd site (events only fire on published sites, not preview)
  • Basic JavaScript knowledge for implementing custom event tracking

Carrd Form Tracking

Carrd's built-in forms are the primary conversion point on most sites. Track form submissions effectively.

Basic Form Submission Tracking

Add this code in a separate Embed element placed after your form:

  1. Add a new Embed element immediately after your form
  2. Select Code as the embed type
  3. Paste this code:
<script>
document.addEventListener('DOMContentLoaded', function() {
  // Find all Carrd forms
  var forms = document.querySelectorAll('form');

  forms.forEach(function(form) {
    form.addEventListener('submit', function(e) {
      // Get form action to identify which form
      var formAction = form.getAttribute('action') || 'unknown';
      var formId = form.getAttribute('id') || 'unknown';

      // Send form submission event to GA4
      gtag('event', 'form_submit', {
        'form_id': formId,
        'form_location': window.location.href,
        'event_category': 'engagement'
      });
    });
  });
});
</script>

Track Successful Form Submissions

Carrd forms redirect or show a success message. Use sendBeacon to ensure tracking fires before redirect:

<script>
document.addEventListener('DOMContentLoaded', function() {
  var forms = document.querySelectorAll('form');

  forms.forEach(function(form) {
    form.addEventListener('submit', function(e) {
      var formId = form.getAttribute('id') || 'contact_form';

      // Use sendBeacon for guaranteed delivery before redirect
      gtag('event', 'generate_lead', {
        'event_category': 'form',
        'form_id': formId,
        'transport_type': 'beacon'
      });
    });
  });
});
</script>

Track Specific Form Fields (Without PII)

Track form interactions without capturing personal information:

<script>
document.addEventListener('DOMContentLoaded', function() {
  var forms = document.querySelectorAll('form');

  forms.forEach(function(form) {
    form.addEventListener('submit', function(e) {
      // Count filled fields (don't capture values)
      var inputs = form.querySelectorAll('input, textarea, select');
      var filledFields = 0;

      inputs.forEach(function(input) {
        if (input.value && input.value.trim() !== '') {
          filledFields++;
        }
      });

      // Track form completion level
      gtag('event', 'form_submit', {
        'form_id': form.getAttribute('id') || 'unknown',
        'fields_filled': filledFields,
        'total_fields': inputs.length,
        'completion_rate': Math.round((filledFields / inputs.length) * 100)
      });
    });
  });
});
</script>

Privacy note: Do NOT track actual form field values (emails, names, phone numbers) - this violates privacy regulations and GA4 terms of service.

Track Form Field Focus

Measure form engagement even without submission:

<script>
document.addEventListener('DOMContentLoaded', function() {
  var forms = document.querySelectorAll('form');

  forms.forEach(function(form) {
    var formInteracted = false;
    var inputs = form.querySelectorAll('input, textarea, select');

    inputs.forEach(function(input) {
      input.addEventListener('focus', function() {
        if (!formInteracted) {
          formInteracted = true;

          gtag('event', 'form_start', {
            'form_id': form.getAttribute('id') || 'unknown',
            'event_category': 'engagement'
          });
        }
      });
    });
  });
});
</script>

Track specific buttons and calls-to-action on your Carrd site.

Track All Buttons

<script>
document.addEventListener('DOMContentLoaded', function() {
  // Track all buttons
  var buttons = document.querySelectorAll('button, .button, [role="button"]');

  buttons.forEach(function(button) {
    button.addEventListener('click', function() {
      var buttonText = this.textContent.trim();
      var buttonHref = this.getAttribute('href') || 'no_link';

      gtag('event', 'button_click', {
        'button_text': buttonText,
        'button_url': buttonHref,
        'page_location': window.location.href
      });
    });
  });
});
</script>

Track Specific Buttons with Data Attributes

Add custom attributes to track specific buttons:

In Carrd Editor:

  1. Select your button element
  2. Click Settings (gear icon)
  3. Add custom attributes in the Attributes section
  4. Add: data-event="cta_click" or data-event="signup_button"

Tracking code:

<script>
document.addEventListener('DOMContentLoaded', function() {
  var trackableElements = document.querySelectorAll('[data-event]');

  trackableElements.forEach(function(element) {
    element.addEventListener('click', function() {
      var eventName = this.getAttribute('data-event');
      var elementText = this.textContent.trim();
      var elementHref = this.getAttribute('href') || 'no_link';

      gtag('event', eventName, {
        'link_text': elementText,
        'link_url': elementHref,
        'event_category': 'engagement'
      });
    });
  });
});
</script>

Automatically track clicks to external websites:

<script>
document.addEventListener('DOMContentLoaded', function() {
  var links = document.querySelectorAll('a');

  links.forEach(function(link) {
    link.addEventListener('click', function(e) {
      var href = this.getAttribute('href');
      var currentDomain = window.location.hostname;

      // Check if link is external
      if (href && href.startsWith('http') && !href.includes(currentDomain)) {
        gtag('event', 'click', {
          'event_category': 'outbound',
          'event_label': href,
          'transport_type': 'beacon'
        });
      }
    });
  });
});
</script>

Track clicks to social profiles (common on Carrd link-in-bio sites):

<script>
document.addEventListener('DOMContentLoaded', function() {
  // Define social media domains
  var socialDomains = [
    'twitter.com', 'x.com',
    'instagram.com',
    'facebook.com',
    'linkedin.com',
    'tiktok.com',
    'youtube.com',
    'github.com',
    'dribbble.com',
    'behance.net'
  ];

  var links = document.querySelectorAll('a');

  links.forEach(function(link) {
    link.addEventListener('click', function() {
      var href = this.getAttribute('href') || '';

      socialDomains.forEach(function(domain) {
        if (href.includes(domain)) {
          var platform = domain.split('.')[0];

          gtag('event', 'social_link_click', {
            'platform': platform,
            'link_url': href,
            'event_category': 'social'
          });
        }
      });
    });
  });
});
</script>

Scroll Depth Tracking

Since Carrd sites are one-page layouts, scroll depth is a crucial engagement metric.

Basic Scroll Depth Tracking

Track when users scroll to 25%, 50%, 75%, and 100% of page:

<script>
(function() {
  var scrollMarks = [25, 50, 75, 100];
  var trackedMarks = [];

  function checkScrollDepth() {
    var windowHeight = window.innerHeight;
    var documentHeight = document.documentElement.scrollHeight;
    var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    var scrollPercent = Math.round((scrollTop / (documentHeight - windowHeight)) * 100);

    scrollMarks.forEach(function(mark) {
      if (scrollPercent >= mark && trackedMarks.indexOf(mark) === -1) {
        trackedMarks.push(mark);

        gtag('event', 'scroll', {
          'percent_scrolled': mark,
          'event_category': 'engagement'
        });
      }
    });
  }

  // Throttle scroll events for performance
  var scrollTimeout;
  window.addEventListener('scroll', function() {
    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(checkScrollDepth, 100);
  });
})();
</script>

Section-Based Scroll Tracking

Track when users scroll to specific sections (useful for portfolio or multi-section landing pages):

<script>
document.addEventListener('DOMContentLoaded', function() {
  // Track when sections come into view
  var sections = document.querySelectorAll('section, .section');
  var trackedSections = [];

  function checkSectionVisibility() {
    sections.forEach(function(section) {
      var sectionId = section.getAttribute('id') || section.className;

      if (trackedSections.indexOf(sectionId) === -1) {
        var rect = section.getBoundingClientRect();
        var windowHeight = window.innerHeight;

        // Check if section is at least 50% visible
        if (rect.top < windowHeight * 0.5 && rect.bottom > 0) {
          trackedSections.push(sectionId);

          gtag('event', 'section_view', {
            'section_id': sectionId,
            'event_category': 'engagement'
          });
        }
      }
    });
  }

  var scrollTimeout;
  window.addEventListener('scroll', function() {
    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(checkSectionVisibility, 200);
  });

  // Check on load
  checkSectionVisibility();
});
</script>

File Download Tracking

Track downloads of PDFs, resumes, portfolios, etc.:

<script>
document.addEventListener('DOMContentLoaded', function() {
  var downloadLinks = document.querySelectorAll('a[href$=".pdf"], a[href$=".zip"], a[href$=".doc"], a[href$=".docx"], a[href$=".ppt"], a[href$=".pptx"]');

  downloadLinks.forEach(function(link) {
    link.addEventListener('click', function() {
      var fileUrl = this.getAttribute('href');
      var fileName = fileUrl.split('/').pop();
      var fileExtension = fileName.split('.').pop();

      gtag('event', 'file_download', {
        'file_name': fileName,
        'file_extension': fileExtension,
        'file_url': fileUrl,
        'link_text': this.textContent.trim()
      });
    });
  });
});
</script>

Track clicks on email addresses (common on contact pages):

<script>
document.addEventListener('DOMContentLoaded', function() {
  var emailLinks = document.querySelectorAll('a[href^="mailto:"]');

  emailLinks.forEach(function(link) {
    link.addEventListener('click', function() {
      var email = this.getAttribute('href').replace('mailto:', '');

      gtag('event', 'email_click', {
        'event_category': 'contact',
        'event_label': 'email_link_clicked',
        'transport_type': 'beacon'
      });
    });
  });
});
</script>

Privacy note: Don't send the actual email address to GA4.

Track clicks on phone numbers:

<script>
document.addEventListener('DOMContentLoaded', function() {
  var phoneLinks = document.querySelectorAll('a[href^="tel:"]');

  phoneLinks.forEach(function(link) {
    link.addEventListener('click', function() {
      gtag('event', 'phone_click', {
        'event_category': 'contact',
        'event_label': 'phone_link_clicked',
        'transport_type': 'beacon'
      });
    });
  });
});
</script>

Video Tracking

Track Embedded YouTube Videos

<script>
// Load YouTube IFrame API
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

// Track video events
function onYouTubeIframeAPIReady() {
  var videos = document.querySelectorAll('iframe[src*="youtube.com"], iframe[src*="youtu.be"]');

  videos.forEach(function(video, index) {
    new YT.Player(video, {
      events: {
        'onStateChange': function(event) {
          var videoUrl = event.target.getVideoUrl();

          if (event.data == YT.PlayerState.PLAYING) {
            gtag('event', 'video_start', {
              'video_url': videoUrl,
              'video_provider': 'youtube',
              'event_category': 'video'
            });
          } else if (event.data == YT.PlayerState.ENDED) {
            gtag('event', 'video_complete', {
              'video_url': videoUrl,
              'video_provider': 'youtube',
              'event_category': 'video'
            });
          }
        }
      }
    });
  });
}
</script>

Track Vimeo Videos

<script>
// Load Vimeo Player API
var script = document.createElement('script');
script.src = 'https://player.vimeo.com/api/player.js';
document.head.appendChild(script);

script.onload = function() {
  var vimeoVideos = document.querySelectorAll('iframe[src*="vimeo.com"]');

  vimeoVideos.forEach(function(video) {
    var player = new Vimeo.Player(video);

    player.on('play', function() {
      player.getVideoTitle().then(function(title) {
        gtag('event', 'video_start', {
          'video_title': title,
          'video_provider': 'vimeo',
          'event_category': 'video'
        });
      });
    });

    player.on('ended', function() {
      player.getVideoTitle().then(function(title) {
        gtag('event', 'video_complete', {
          'video_title': title,
          'video_provider': 'vimeo',
          'event_category': 'video'
        });
      });
    });
  });
};
</script>

Time on Page Tracking

Track engaged time on your Carrd page:

<script>
(function() {
  var startTime = new Date().getTime();
  var engaged = true;
  var engagedTime = 0;
  var lastCheck = startTime;

  // Track active engagement
  function trackEngagement() {
    if (engaged) {
      var now = new Date().getTime();
      engagedTime += (now - lastCheck);
      lastCheck = now;
    }
  }

  // User is engaged
  window.addEventListener('mousemove', function() { engaged = true; });
  window.addEventListener('scroll', function() { engaged = true; });
  window.addEventListener('keydown', function() { engaged = true; });

  // User is disengaged
  window.addEventListener('blur', function() { engaged = false; });

  // Track every 5 seconds
  setInterval(trackEngagement, 5000);

  // Send on page unload
  window.addEventListener('beforeunload', function() {
    trackEngagement();
    var totalSeconds = Math.round(engagedTime / 1000);

    if (totalSeconds > 5) { // Only track if engaged for more than 5 seconds
      gtag('event', 'engaged_time', {
        'value': totalSeconds,
        'event_category': 'engagement',
        'transport_type': 'beacon'
      });
    }
  });
})();
</script>

Custom Embed Tracking

If you embed third-party widgets (Calendly, Gumroad, etc.), track interactions:

Calendly Event Tracking

<script>
window.addEventListener('message', function(e) {
  if (e.data.event && e.data.event.indexOf('calendly') === 0) {
    gtag('event', e.data.event, {
      'event_category': 'calendly',
      'event_label': e.data.event
    });
  }
});
</script>

Gumroad Purchase Tracking

<script>
window.addEventListener('message', function(e) {
  if (e.data && e.data.action === 'gumroad:purchase') {
    gtag('event', 'purchase', {
      'event_category': 'ecommerce',
      'event_label': 'gumroad',
      'currency': 'USD',
      'value': e.data.amount || 0
    });
  }
});
</script>

Custom Dimensions and Parameters

Setting Up Custom Dimensions

  1. Go to Admin > Custom Definitions in GA4
  2. Create custom dimensions:
    • page_type - Type of Carrd page (landing, portfolio, link-in-bio)
    • visitor_type - New vs returning
    • campaign_source - UTM source if present

Sending Custom Dimensions

<script>
// Get UTM parameters from URL
var urlParams = new URLSearchParams(window.location.search);
var utmSource = urlParams.get('utm_source') || 'direct';
var utmMedium = urlParams.get('utm_medium') || 'none';
var utmCampaign = urlParams.get('utm_campaign') || 'none';

// Send with page view
gtag('event', 'page_view', {
  'page_type': 'landing_page',
  'campaign_source': utmSource,
  'campaign_medium': utmMedium,
  'campaign_name': utmCampaign
});
</script>

Event Naming Best Practices

When possible, use GA4's recommended event names:

  • generate_lead - Form submission
  • sign_up - Newsletter or account signup
  • login - User login (if applicable)
  • search - Site search
  • select_content - Content selection
  • share - Social sharing
  • view_item - Product or portfolio item view

Custom Event Naming

For Carrd-specific events:

  • Use lowercase with underscores: section_view not sectionView
  • Be descriptive: portfolio_item_click not click1
  • Use consistent prefixes: form_, link_, scroll_
  • Keep under 40 characters

Testing Events

Use GA4 DebugView

  1. Enable debug mode in your GA4 config:
<script>
  gtag('config', 'G-XXXXXXXXXX', {
    'debug_mode': true
  });
</script>
  1. Go to Admin > DebugView in GA4
  2. Publish your Carrd site
  3. Visit the site and trigger events
  4. See events in real-time with parameters

Browser Console Testing

Test events directly in console:

// Test an event
gtag('event', 'test_event', {
  'test_parameter': 'test_value'
});

// Verify gtag exists
console.log(typeof gtag); // Should be "function"

// Check dataLayer
console.log(window.dataLayer);

Common Issues

Events Not Firing

Problem: Custom events don't appear in GA4.

Solutions:

  1. Verify GA4 is loaded: Check typeof gtag in console (should be "function")
  2. Test on published site: Events don't fire in Carrd preview mode
  3. Check embed order: Event tracking embeds must come AFTER GA4 installation embed
  4. Wait for processing: Events can take a few minutes to appear
  5. Use DebugView: Enable debug mode for immediate feedback
  6. Check JavaScript errors: Open console (F12) and look for errors

Events Fire Multiple Times

Problem: Same event fires multiple times per action.

Solutions:

  1. Check for duplicate embeds: Remove duplicate tracking codes
  2. Use event flags:
var eventFired = false;
button.addEventListener('click', function() {
  if (!eventFired) {
    eventFired = true;
    gtag('event', 'button_click', {...});
  }
});
  1. Use once option:
element.addEventListener('click', handler, { once: true });

Form Tracking Not Working

Problem: Form submissions don't track.

Solutions:

  1. Use sendBeacon: Add transport_type: 'beacon' to event
  2. Place embed after form: Embed must be after form in page layout
  3. Check form redirect: Carrd forms redirect quickly
  4. Add small delay: Test with a brief setTimeout
  5. Verify form selector: Ensure querySelector('form') finds your form

Complete Tracking Setup

Here's a complete event tracking setup for a typical Carrd site:

<script>
document.addEventListener('DOMContentLoaded', function() {

  // 1. Form Tracking
  var forms = document.querySelectorAll('form');
  forms.forEach(function(form) {
    form.addEventListener('submit', function() {
      gtag('event', 'generate_lead', {
        'form_id': form.getAttribute('id') || 'contact_form',
        'transport_type': 'beacon'
      });
    });
  });

  // 2. Button Tracking
  var buttons = document.querySelectorAll('[data-event]');
  buttons.forEach(function(button) {
    button.addEventListener('click', function() {
      gtag('event', this.getAttribute('data-event'), {
        'button_text': this.textContent.trim()
      });
    });
  });

  // 3. Outbound Links
  var links = document.querySelectorAll('a');
  links.forEach(function(link) {
    link.addEventListener('click', function() {
      var href = this.getAttribute('href') || '';
      if (href.startsWith('http') && !href.includes(window.location.hostname)) {
        gtag('event', 'click', {
          'event_category': 'outbound',
          'event_label': href,
          'transport_type': 'beacon'
        });
      }
    });
  });

  // 4. Scroll Depth
  var scrollMarks = [25, 50, 75, 100];
  var tracked = [];
  var checkScroll = function() {
    var percent = Math.round((window.pageYOffset / (document.documentElement.scrollHeight - window.innerHeight)) * 100);
    scrollMarks.forEach(function(mark) {
      if (percent >= mark && tracked.indexOf(mark) === -1) {
        tracked.push(mark);
        gtag('event', 'scroll', { 'percent_scrolled': mark });
      }
    });
  };
  window.addEventListener('scroll', function() {
    setTimeout(checkScroll, 100);
  });

});
</script>

Add this single embed to your Carrd site for comprehensive tracking.

Next Steps

Additional Resources