A comprehensive data layer enables GTM to access Storyblok-specific information like story metadata, components, and Visual Editor state. This guide shows how to build a complete Storyblok data layer.
Storyblok Data Layer Overview
The data layer stores information about Storyblok stories, components, and user context. GTM tags access this data via variables.
Storyblok Data Categories
- Story Data - Story ID, name, slug, publication dates
- Component Data - Component types, UIDs, properties
- Visual Editor Data - Editor mode status, preview state
- Content Data - Titles, descriptions, metadata
- Relationship Data - Linked stories and content relationships
Basic Data Layer Implementation
Nuxt 3
Create plugins/storyblok-data-layer.client.ts:
// plugins/storyblok-data-layer.client.ts
export default defineNuxtPlugin(() => {
const route = useRoute();
// Initialize dataLayer
window.dataLayer = window.dataLayer || [];
// Push comprehensive Storyblok data
const pushStoryData = (story: any) => {
window.dataLayer.push({
event: 'storyblok_data_ready',
// Page metadata
page: {
path: route.path,
title: story.content.meta_title || story.name,
description: story.content.meta_description,
},
// Storyblok story data
storyblok: {
// Core story info
id: story.id,
uuid: story.uuid,
name: story.name,
slug: story.slug,
fullSlug: story.full_slug,
component: story.content.component,
// Publication metadata
createdAt: story.created_at,
publishedAt: story.published_at,
firstPublishedAt: story.first_published_at,
// Tags and categorization
tagList: story.tag_list || [],
// Language and localization
lang: story.lang,
alternates: story.alternates?.map((alt: any) => ({
id: alt.id,
name: alt.name,
slug: alt.slug,
lang: alt.lang,
})) || [],
// Visual Editor state
isEditorMode: route.query._storyblok !== undefined,
},
});
};
return {
provide: {
pushStoryData,
},
};
});
Usage in page:
<!-- pages/[...slug].vue -->
<script setup>
const { slug } = useRoute().params;
const story = await useAsyncStoryblok(slug.join('/'), { version: 'draft' });
const { $pushStoryData } = useNuxtApp();
onMounted(() => {
if (story.value) {
$pushStoryData(story.value);
}
});
</script>
Advanced Data Layer Patterns
Component-Level Data Layer
Track individual component data:
<!-- components/HeroSection.vue -->
<script setup>
const { blok } = defineProps(['blok']);
onMounted(() => {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'component_loaded',
component: {
type: blok.component,
uid: blok._uid,
// Extract simple field values
hasImage: !!blok.image,
hasVideo: !!blok.video,
ctaCount: blok.cta_buttons?.length || 0,
},
});
});
</script>
Visual Editor Detection
// composables/useEditorTracking.ts
export const useEditorTracking = () => {
const route = useRoute();
const isEditor = computed(() => route.query._storyblok !== undefined);
watch(isEditor, (inEditor) => {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'editor_mode_status',
editor: {
active: inEditor,
timestamp: new Date().toISOString(),
},
});
}, { immediate: true });
return { isEditor };
};
Story Relationships Data Layer
const pushRelationships = (story: any) => {
const relationships = {};
// Extract relationship fields
Object.keys(story.content).forEach(key => {
const field = story.content[key];
if (field && typeof field === 'object' && field.id && field.component) {
relationships[key] = {
id: field.id,
name: field.name,
component: field.component,
slug: field.slug,
};
}
});
if (Object.keys(relationships).length > 0) {
window.dataLayer.push({
event: 'storyblok_relationships',
relationships,
});
}
};
GTM Variable Configuration
Create Data Layer Variables in GTM
Navigate to Variables → New → User-Defined Variables:
Story Variables
| Variable Name | Type | Data Layer Variable Name |
|---|---|---|
| Storyblok Story ID | Data Layer Variable | storyblok.id |
| Storyblok Story Name | Data Layer Variable | storyblok.name |
| Storyblok Component | Data Layer Variable | storyblok.component |
| Storyblok Language | Data Layer Variable | storyblok.lang |
| Storyblok Tags | Data Layer Variable | storyblok.tagList |
Editor Variables
| Variable Name | Type | Data Layer Variable Name |
|---|---|---|
| Editor Mode Active | Data Layer Variable | storyblok.isEditorMode |
| Editor Timestamp | Data Layer Variable | editor.timestamp |
GTM Trigger Configuration
Story Type Triggers
Trigger: Blog Posts
- Type: Custom Event
- Event name:
storyblok_data_ready - Condition:
storyblok.componentequalsblog_post
Trigger: Landing Pages
- Type: Custom Event
- Event name:
storyblok_data_ready - Condition:
storyblok.componentequalslanding_page
Editor Mode Trigger
Trigger: Editor Active
- Type: Custom Event
- Event name:
editor_mode_status - Condition:
storyblok.isEditorModeequalstrue
Using Data Layer in GTM Tags
GA4 Configuration with Storyblok Data
Tag: GA4 Configuration
- Tag Type: Google Analytics: GA4 Configuration
- Measurement ID:
G-XXXXXXXXXX - Configuration Parameters:
story_component:\{\{Storyblok Component\}\}story_name:\{\{Storyblok Story Name\}\}content_language:\{\{Storyblok Language\}\}editor_mode:\{\{Editor Mode Active\}\}
Testing and Debugging
Preview Data Layer in Console
// In browser console
console.log(window.dataLayer);
// Find Storyblok data
console.log(window.dataLayer.find(item => item.event === 'storyblok_data_ready'));
GTM Preview Mode
- In GTM, click Preview
- Enter your Storyblok site URL
- In Tag Assistant:
- Verify
storyblok_data_readyevent fires - Check Data Layer tab
- Verify all Storyblok variables populate
- Verify
Performance Considerations
Minimize Data Layer Size
// Good: Only metadata
window.dataLayer.push({
storyblok: {
id: story.id,
component: story.content.component,
name: story.name,
},
});
// Bad: Too much data
window.dataLayer.push({
storyblok: {
fullContent: story.content, // Don't include full content
},
});