Track user interactions and content engagement on your Directus-powered site. Since Directus is headless, all event tracking is implemented in your frontend application code.
Directus Content Events
Content View Events
Track when users view Directus content:
// utils/analytics.ts
export function trackContentView(item: any) {
if (typeof window === 'undefined' || !window.gtag) return;
window.gtag('event', 'content_view', {
content_type: item.__collection || 'unknown',
content_id: item.id,
title: item.title || item.name,
status: item.status,
date_created: item.date_created,
user_created: item.user_created?.id || 'unknown',
});
}
// Usage in Next.js component
'use client';
import { useEffect } from 'react';
import { trackContentView } from '@/utils/analytics';
export function ArticlePage({ article }) {
useEffect(() => {
trackContentView(article);
}, [article]);
return <article>{/* Content */}</article>;
}
Content Engagement Tracking
Track scroll depth and time on page:
// components/ContentEngagementTracker.tsx
'use client';
import { useEffect, useState } from 'react';
export function ContentEngagementTracker({ contentId, contentType }) {
const [maxScroll, setMaxScroll] = useState(0);
const [startTime] = useState(Date.now());
useEffect(() => {
const handleScroll = () => {
const scrollPercentage = Math.round(
(window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
);
if (scrollPercentage > maxScroll) {
setMaxScroll(scrollPercentage);
// Track scroll milestones
if ([25, 50, 75, 90].includes(scrollPercentage)) {
window.gtag?.('event', 'scroll', {
content_id: contentId,
content_type: contentType,
percent_scrolled: scrollPercentage,
});
}
}
};
window.addEventListener('scroll', handleScroll, { passive: true });
// Track time on page when leaving
const handleBeforeUnload = () => {
const timeOnPage = Math.round((Date.now() - startTime) / 1000);
window.gtag?.('event', 'content_engagement', {
content_id: contentId,
content_type: contentType,
time_on_page: timeOnPage,
max_scroll_depth: maxScroll,
});
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [contentId, contentType, maxScroll, startTime]);
return null;
}
Collection Filtering Events
Track when users filter Directus collections:
// Track collection filters
export function trackCollectionFilter(collection: string, filters: any) {
window.gtag?.('event', 'filter_content', {
collection: collection,
filter_count: Object.keys(filters).length,
filters: JSON.stringify(filters),
});
}
// Usage example
const [filters, setFilters] = useState({});
const handleFilterChange = (newFilters) => {
setFilters(newFilters);
trackCollectionFilter('articles', newFilters);
};
User Interaction Events
Search Events
Track Directus content searches:
// components/SearchBar.tsx
'use client';
import { useState } from 'react';
import { createDirectus, rest, readItems } from '@directus/sdk';
const directus = createDirectus(process.env.NEXT_PUBLIC_DIRECTUS_URL!).with(rest());
export function SearchBar() {
const [query, setQuery] = useState('');
const handleSearch = async (e: React.FormEvent) => {
e.preventDefault();
// Track search event
window.gtag?.('event', 'search', {
search_term: query,
search_type: 'directus_content',
});
// Perform search
const results = await directus.request(
readItems('articles', {
search: query,
})
);
// Track search results
window.gtag?.('event', 'view_search_results', {
search_term: query,
results_count: results.length,
});
};
return (
<form
<input
value={query} => setQuery(e.target.value)}
placeholder="Search..."
/>
<button type="submit">Search</button>
</form>
);
}
File Download Tracking
Track downloads from Directus assets:
// components/DownloadButton.tsx
'use client';
export function DownloadButton({ asset, label }) {
const handleDownload = () => {
window.gtag?.('event', 'file_download', {
file_name: asset.filename_download,
file_type: asset.type,
file_size: asset.filesize,
link_text: label,
link_url: `${process.env.NEXT_PUBLIC_DIRECTUS_URL}/assets/${asset.id}`,
});
};
return (
<a
href={`${process.env.NEXT_PUBLIC_DIRECTUS_URL}/assets/${asset.id}`}
download={asset.filename_download}
>
{label || `Download ${asset.title}`}
</a>
);
}
Relation Tracking
Track when users navigate related Directus items:
export function trackRelationClick(fromCollection: string, toCollection: string, itemId: string) {
window.gtag?.('event', 'click_relation', {
from_collection: fromCollection,
to_collection: toCollection,
item_id: itemId,
});
}
// Usage
<a
href={`/authors/${article.author.id}`} => trackRelationClick('articles', 'authors', article.author.id)}
>
{article.author.name}
</a>
Form Events
Directus Form Submissions
Track form submissions that create Directus items:
// components/ContactForm.tsx
'use client';
import { createDirectus, rest, createItem } from '@directus/sdk';
const directus = createDirectus(process.env.NEXT_PUBLIC_DIRECTUS_URL!).with(rest());
export function ContactForm() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
window.gtag?.('event', 'generate_lead', {
value: 0,
currency: 'USD',
form_name: 'contact_form',
});
try {
await directus.request(
createItem('contacts', {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
})
);
window.gtag?.('event', 'form_submit', {
form_name: 'contact_form',
success: true,
});
} catch (error) {
window.gtag?.('event', 'form_submit', {
form_name: 'contact_form',
success: false,
error_message: error.message,
});
}
};
return (
<form
{/* Form fields */}
</form>
);
}
Custom Dimensions & Metrics
Set Up Custom Dimensions
Track Directus-specific data as custom dimensions:
// Set user properties based on Directus data
export function setUserPreferences(user: any) {
window.gtag?.('set', 'user_properties', {
user_role: user.role?.name || 'guest',
user_status: user.status,
preferred_language: user.language,
});
}
// Set custom dimensions on page view
export function trackPageWithDimensions(item: any) {
window.gtag?.('event', 'page_view', {
// Custom dimensions
collection_name: item.__collection,
item_status: item.status,
date_created: item.date_created,
date_updated: item.date_updated,
user_created: item.user_created?.id,
translations_available: item.translations?.length || 0,
});
}
E-commerce Events (Directus Commerce)
If using Directus for e-commerce:
View Item
export function trackProductView(product: any) {
window.gtag?.('event', 'view_item', {
currency: 'USD',
value: product.price,
items: [
{
item_id: product.id || product.sku,
item_name: product.name,
item_brand: product.brand,
item_category: product.category?.name,
price: product.price,
quantity: 1,
},
],
});
}
// Usage in product page
'use client';
export function ProductPage({ product }) {
useEffect(() => {
trackProductView(product);
}, [product]);
return <div>{/* Product details */}</div>;
}
Add to Cart
export function AddToCartButton({ product, quantity = 1 }) {
const handleAddToCart = async () => {
window.gtag?.('event', 'add_to_cart', {
currency: 'USD',
value: product.price * quantity,
items: [
{
item_id: product.id || product.sku,
item_name: product.name,
item_brand: product.brand,
item_category: product.category?.name,
price: product.price,
quantity: quantity,
},
],
});
// Add to cart logic...
};
return <button to Cart</button>;
}
Framework-Specific Implementations
Next.js Event Wrapper
Create a reusable event tracking hook:
// hooks/useAnalytics.ts
import { useCallback } from 'react';
export function useAnalytics() {
const trackEvent = useCallback((
eventName: string,
eventParams?: Record<string, any>
) => {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', eventName, eventParams);
}
}, []);
const trackDirectusItem = useCallback((item: any) => {
trackEvent('content_view', {
collection: item.__collection,
item_id: item.id,
title: item.title || item.name,
status: item.status,
});
}, [trackEvent]);
return { trackEvent, trackDirectusItem };
}
// Usage
const { trackEvent, trackDirectusItem } = useAnalytics();
useEffect(() => {
trackDirectusItem(directusItem);
}, [directusItem, trackDirectusItem]);
Vue/Nuxt Composable
// composables/useAnalytics.ts
export const useAnalytics = () => {
const trackEvent = (eventName: string, params?: any) => {
if (process.client && window.gtag) {
window.gtag('event', eventName, params);
}
};
const trackDirectusItem = (item: any) => {
trackEvent('content_view', {
collection: item.__collection,
item_id: item.id,
title: item.title,
});
};
return {
trackEvent,
trackDirectusItem,
};
};
Usage in component:
<script setup>
const { trackDirectusItem } = useAnalytics();
onMounted(() => {
trackDirectusItem(item.value);
});
</script>
Testing Events
GA4 DebugView
Enable debug mode to test events:
// Enable debug mode in development
const debugMode = process.env.NODE_ENV === 'development';
gtag('config', GA_MEASUREMENT_ID, {
debug_mode: debugMode,
});
View events:
- Go to GA4 Admin → DebugView
- Navigate your site
- See events appear in real-time with full parameters
Console Logging
Log events during development:
function trackEvent(eventName: string, params?: any) {
if (process.env.NODE_ENV === 'development') {
console.log('GA4 Event:', eventName, params);
}
if (window.gtag) {
window.gtag('event', eventName, params);
}
}
Best Practices
1. Consistent Event Naming
Use standard GA4 event names when possible:
page_view- Page viewssearch- Searchessign_up- User signupsgenerate_lead- Lead generationview_item- Content viewsfile_download- Downloads
2. Avoid PII
Never send personally identifiable information:
// Bad
window.gtag('event', 'sign_up', {
email: user.email, // DON'T
name: user.name, // DON'T
});
// Good
window.gtag('event', 'sign_up', {
user_id: hashUserId(user.id), // Hashed
user_role: user.role.name,
});
3. Track Directus Status Changes
export function trackStatusChange(collection: string, itemId: string, oldStatus: string, newStatus: string) {
window.gtag?.('event', 'status_change', {
collection: collection,
item_id: itemId,
old_status: oldStatus,
new_status: newStatus,
});
}
Next Steps
- Set up GTM - Manage events via Tag Manager
- Create Data Layer - Custom data layer for Directus
- Troubleshoot Events - Debug tracking issues
For general GA4 event concepts, see GA4 Event Tracking Guide.