# API Overview
Source: https://dataglue.io/api-reference/overview
Complete reference for DataGlue's JavaScript API methods and properties.
# API Reference
DataGlue provides a comprehensive JavaScript API for interacting with visitor data and customizing behavior.
## Global Object
DataGlue is available globally as `window.glue`:
```javascript theme={null}
// Check if DataGlue is loaded
if (window.glue) {
console.log('DataGlue is ready');
}
```
## Core Methods
### `glue.storage`
Storage management methods:
```javascript theme={null}
// Set data
window.glue.storage.set('key', 'value');
window.glue.storage.set('key', 'value', 30); // With 30-day expiration
// Get data
const value = window.glue.storage.get('key');
// Remove data
window.glue.storage.remove('key');
// Clear all data
window.glue.storage.clear();
```
### `glue.getVisitorContext()`
Get complete visitor information:
```javascript theme={null}
const context = window.glue.getVisitorContext();
console.log(context);
// Returns: user agent, language, attribution data, etc.
```
### `glue.getAttribution()`
Get attribution data only:
```javascript theme={null}
const attribution = window.glue.getAttribution();
console.log(attribution);
// Returns: UTM parameters, click IDs, referrer data
```
### `glue.dynamicContent()`
Configure conditional content:
```javascript theme={null}
glue.dynamicContent({
selector: '#premium-content',
key: 'utm_source',
value: 'email',
access_granted: '
Welcome email subscriber!
',
access_denied: '
Subscribe to see this content.
'
});
```
## Configuration
### Script Attributes
Configure DataGlue via script tag attributes:
```html theme={null}
```
### JavaScript Configuration
```javascript theme={null}
// Set attribution manually
window.glue.setAttribution({
utm_source: 'newsletter',
utm_campaign: 'summer_sale'
});
```
## Events
DataGlue emits custom events:
```javascript theme={null}
// Listen for initialization
window.addEventListener('dataglue:ready', function() {
console.log('DataGlue initialized');
});
// Listen for data updates
window.addEventListener('dataglue:update', function(event) {
console.log('Data updated:', event.detail);
});
```
## Error Handling
```javascript theme={null}
try {
const data = window.glue.storage.get('key');
if (!data) {
console.warn('No data found for key');
}
} catch (error) {
console.error('DataGlue error:', error);
}
```
# Architecture
Source: https://dataglue.io/concepts/architecture
Understanding DataGlue's system architecture and data flow patterns.
# Architecture
DataGlue follows a modular architecture designed for flexibility, performance, and privacy compliance.
## System Overview
```mermaid theme={null}
graph TB
subgraph "Browser Environment"
A[DataGlue Core]
B[Storage Layer]
C[Attribution Engine]
D[Form Integration]
E[Dynamic Content]
end
subgraph "Data Sources"
F[URL Parameters]
G[User Interactions]
H[Third-party APIs]
end
subgraph "Output Destinations"
I[Forms]
J[Analytics]
K[CRM Systems]
end
F --> A
G --> A
H --> A
A --> B
A --> C
A --> D
A --> E
D --> I
C --> J
B --> K
```
## Core Components
### Storage Layer
* **localStorage**: Persistent user data
* **sessionStorage**: Session-specific data
* **Cookies**: Cross-tab synchronization
### Attribution Engine
* Multi-touch attribution tracking
* UTM parameter management
* Third-party platform integration
### Form Integration
* Automatic field population
* Element selector patterns
* Fillout form enhancement
### Dynamic Content
* Conditional content display
* User attribute evaluation
* Real-time personalization
## Data Flow
1. **Collection**: Gather data from URLs, interactions, and APIs
2. **Processing**: Normalize and validate collected data
3. **Storage**: Persist data across multiple storage mechanisms
4. **Application**: Use data to enhance user experience
## Security & Privacy
DataGlue is built with privacy-first principles:
* Client-side data processing
* Configurable data collection
* GDPR/CCPA compliance
* No server-side data storage by default
# Attribution
Source: https://dataglue.io/concepts/attribution
Understanding DataGlue's multi-touch attribution system and how it tracks customer journeys.
# Attribution
DataGlue provides comprehensive multi-touch attribution tracking that captures both first-touch and last-touch interactions.
## Attribution Model
Unlike traditional analytics that only track last-touch attribution, DataGlue preserves the complete customer journey:
### First-Touch Attribution
Records the initial source that brought the visitor to your site:
* `glue_initial_utm_source`
* `glue_initial_utm_campaign`
* `glue_initial_utm_medium`
### Last-Touch Attribution
Tracks the most recent source before conversion:
* `glue_last_utm_source`
* `glue_last_utm_campaign`
* `glue_last_utm_medium`
## Customer Journey Example
```mermaid theme={null}
journey
title Multi-Touch Attribution Journey
section Week 1
LinkedIn Ad Click: 5: Visitor
Initial UTM Captured: 5: DataGlue
Browses Site: 4: Visitor
Leaves Without Converting: 3: Visitor
section Week 2
Email Campaign Click: 4: Visitor
Last UTM Updated: 5: DataGlue
Initial UTM Preserved: 5: DataGlue
Form Submission: 5: Visitor
Both Attributions Sent: 5: DataGlue
```
## Platform-Specific Attribution
DataGlue automatically captures platform-specific identifiers:
### Google Ads
* **gclid**: Google click identifier
* Automatic capture from URL parameters
* Stored as `glue_initial_gclid` and `glue_last_gclid`
### Facebook Ads
* **fbclid**: Facebook click identifier
* **\_fbp**: Facebook browser pixel
* **\_fbc**: Facebook click ID from cookies
### TikTok Ads
* **ttid**: TikTok tracking identifier
* Session storage synchronization
* Enhanced attribution data
## Implementation
DataGlue attribution works automatically once installed:
```html theme={null}
```
## Accessing Attribution Data
```javascript theme={null}
// Get all attribution data
const attribution = window.glue.getAttribution();
// Get specific touchpoints
const firstTouch = window.glue.storage.get('glue_initial_utm_source');
const lastTouch = window.glue.storage.get('glue_last_utm_source');
```
## Benefits
1. **Complete Journey Mapping**: See the full path to conversion
2. **Accurate ROI Calculation**: Credit all contributing touchpoints
3. **Campaign Optimization**: Understand which combinations drive results
4. **Customer Insights**: Identify high-value user paths
# Form Integration
Source: https://dataglue.io/features/form-integration
How to integrate DataGlue with forms for automatic field population and data capture.
# Form Integration
DataGlue automatically populates form fields with stored visitor data, reducing friction and improving conversion rates.
## Basic Setup
Add `glue` attributes to your form fields:
```html theme={null}
```
## Selector Patterns
DataGlue recognizes multiple selector patterns:
### Direct Glue Attributes
```html theme={null}
```
### ID-based Selectors
```html theme={null}
```
### Class-based Selectors
```html theme={null}
```
## Fillout Integration
For Fillout forms, DataGlue automatically adds data attributes:
```html theme={null}
```
## Advanced Configuration
### Multiple Forms
```html theme={null}
```
### Conditional Fields
```javascript theme={null}
// Show/hide fields based on visitor data
if (window.glue.storage.get('glue_user_tier') === 'premium') {
document.getElementById('premium-fields').style.display = 'block';
}
```
## Best Practices
1. **Use Consistent Naming**: Stick to `glue_` prefix for custom attributes
2. **Test Selectors**: Verify form fields populate correctly
3. **Handle Missing Data**: Provide fallbacks for empty fields
4. **Privacy Compliance**: Only collect necessary information
# The Powerhouse of Behavioural Web Data Creation
Source: https://dataglue.io/index
DataGlue is the powerhouse of behavioural web data creation, providing comprehensive visitor tracking and attribution.
# DataGlue
# The Powerhouse of Behavioural Web Data Creation
DataGlue is a cutting-edge tracking solution that transcends the capabilities of traditional analytics tools. It is designed to create and track behavioural data of website visitors, offering businesses a comprehensive view of the customer journey.
Capture both first-touch and last-touch attribution to understand the complete customer journey.
Automatically populate forms with visitor data and track form interactions seamlessly.
Show personalized content based on visitor attributes and behavioral data.
Built-in GDPR and CCPA compliance with configurable data collection.
## The Digital Challenge
The digital landscape is a complex web of customer interactions across multiple touchpoints. Traditional analytics tools often yield fragmented or superficial data, leaving businesses grappling with:
Most analytics platforms only track last-touch attribution, missing crucial touchpoints in the customer journey that influence conversions.
Customer data gets trapped in different systems, making it impossible to create a unified view of the customer journey.
Users abandon forms because they have to re-enter information they've already provided, leading to lost conversions.
Without proper visitor context, websites can't provide personalized experiences that drive engagement and conversions.
## The DataGlue Solution
DataGlue addresses these challenges by providing:
### 360-Degree Visitor Tracking
```mermaid theme={null}
graph LR
subgraph "Data Sources"
A[Website Visitors]
B[UTM Parameters]
C[Form Submissions]
D[Social Media Clicks]
end
subgraph "DataGlue Processing"
E[Attribution Tracking]
F[Behavioral Analysis]
G[Identity Resolution]
H[Data Enrichment]
end
subgraph "Output Destinations"
I[CRM Systems]
J[Google Analytics]
K[Marketing Platforms]
L[Custom Webhooks]
end
A --> E
B --> E
C --> F
D --> F
E --> G
F --> G
G --> H
H --> I
H --> J
H --> K
H --> L
```
### Key Features
DataGlue captures **both first-touch and last-touch attribution**, preserving the complete customer journey across sessions and devices.
```javascript theme={null}
// Example: User's complete attribution history
{
"initial_utm_source": "linkedin",
"initial_utm_campaign": "product_launch",
"last_utm_source": "email",
"last_utm_campaign": "nurture_sequence",
"user_id": "01234567-89ab-cdef-0123-456789abcdef"
}
```
Forms automatically populate with stored visitor data, reducing friction and improving conversion rates.
```html theme={null}
```
Show different content based on visitor attributes, creating personalized experiences.
```javascript theme={null}
glue.dynamicContent({
selector: '#premium-content',
key: 'utm_source',
value: 'email',
access_granted: '
Exclusive Content for Email Subscribers!
'
});
```
## Real-World Impact
**Attribution Recovery**: Businesses using DataGlue typically recover 30-40% of attribution data that was previously lost to last-touch-only tracking.
**Privacy Compliance**: DataGlue is designed with privacy-first principles, ensuring GDPR and CCPA compliance out of the box.
### Customer Journey Example
Consider this typical user journey that DataGlue automatically tracks:
User clicks LinkedIn ad → `glue_initial_utm_source: linkedin`
Browses product pages → Session data and engagement tracked
Leaves without converting → Attribution data preserved
Returns via email campaign → `glue_last_utm_source: email`
Submits contact form → Both LinkedIn AND email attribution included
**Result**: Your CRM receives a lead with complete multi-touch attribution, enabling accurate ROI calculation and personalized follow-up.
## Getting Started
Ready to unlock the power of behavioral data? Get started with DataGlue in minutes:
Get DataGlue running on your website in under 5 minutes.
Detailed installation instructions for different platforms.
Complete API documentation and method references.
Real-world implementation examples and use cases.
## What's Next?
Our upcoming SaaS platform will provide real-time identity resolution, advanced funnel visualization, and AI-powered insights for predictive customer behavior.
# Installation
Source: https://dataglue.io/installation
How to install and configure DataGlue on your website with different deployment options.
# Installation
DataGlue can be installed in multiple ways depending on your project setup and requirements.
## CDN Installation (Recommended)
The easiest way to get started with DataGlue is to include it directly from our CDN:
```html theme={null}
Your Website
```
## NPM Installation
For projects using a build system, install DataGlue via npm:
```bash theme={null}
npm install dataglue
```
Then import and initialize:
```javascript theme={null}
import DataGlue from 'dataglue';
// Initialize DataGlue
const glue = new DataGlue();
```
## Configuration Options
DataGlue can be configured through HTML attributes or JavaScript:
### HTML Configuration
```html theme={null}
```
### JavaScript Configuration
```javascript theme={null}
// Configure after loading
window.addEventListener('DOMContentLoaded', function() {
glue.configure({
modules: ['formSearch'],
endpoint: 'https://api.example.com/data'
});
});
```
## Verification
After installation, verify DataGlue is working:
1. Open browser developer tools
2. Check for initialization message in console
3. Verify data collection in localStorage
```javascript theme={null}
// Check if DataGlue is loaded
console.log(window.glue ? 'DataGlue loaded' : 'DataGlue not found');
```
## Next Steps
* [Quick Start Guide](/quickstart) - Get up and running in 5 minutes
* [Form Integration](/features/form-integration) - Connect your forms
* [Dynamic Content](/features/dynamic-content) - Personalize experiences
# Journey Tracking
Source: https://dataglue.io/journey-tracking
Track complete user journeys from first visit to conversion
# Journey Tracking
DataGlue now tracks **complete user journeys** - every session, every event, every step from first visit to conversion. All data is stored in `localStorage` as a backup, and optionally synced to your server.
## Features
* ✅ **Complete history** - Every session and event from first visit
* ✅ **Offline-first** - Works without server, data stored in localStorage
* ✅ **JSON format** - Two simple collections: `users` and `events`
* ✅ **Auto-identification** - Detects email from forms or URL params
* ✅ **First & last touch** - Attribution tracking built-in
* ✅ **Form-friendly** - Works with native forms, Fillout, Typeform, etc.
## Quick Start
### Step 1: Add Profile Endpoint
```html theme={null}
```
### Step 2: That's It!
DataGlue automatically:
* Tracks page views, clicks, form submits
* Stores everything in `localStorage` (3 keys):
* `glue_user_profile` - User info
* `glue_sessions` - All sessions
* `glue_events` - All events
* Auto-identifies users when they fill email fields
* Sends data to your API endpoint
## How It Works
### User Journey Flow
```
Day 1:
→ User lands from Google (utm_source=google)
→ DataGlue generates glue_user_id: "abc-123"
→ Tracks: page_view (/landing)
→ User leaves
Day 3:
→ User returns directly
→ Same glue_user_id: "abc-123" (from localStorage)
→ Tracks: page_view (/blog)
→ User leaves again
Day 5:
→ User clicks email link (utm_source=email)
→ Lands on /promo
→ Fills form with email: john@example.com
→ DataGlue auto-identifies: links "abc-123" → "john@example.com"
→ Form redirects to /thank-you?email=john@example.com&glue_user_id=abc-123
→ Your server receives identify() call
→ Journey complete!
```
## Data Structure
### localStorage Keys
#### `glue_user_profile`
```json theme={null}
{
"glue_user_id": "abc-123-uuid",
"email": "john@example.com",
"traits": {
"fname": "John",
"lname": "Doe"
},
"first_seen": "2024-01-15T10:30:00Z",
"last_seen": "2024-01-20T16:20:00Z",
"identified": true
}
```
#### `glue_sessions`
```json theme={null}
[
{
"session_id": "session-001",
"session_start": "2024-01-15T10:30:00Z",
"landing_page": "/landing",
"referrer": "https://google.com",
"utm": {
"utm_source": "google",
"utm_campaign": "summer"
},
"pages_viewed": 3,
"events_count": 5,
"converted": false
},
{
"session_id": "session-002",
"session_start": "2024-01-20T16:00:00Z",
"landing_page": "/promo",
"utm": {
"utm_source": "email"
},
"pages_viewed": 4,
"converted": true
}
]
```
#### `glue_events`
```json theme={null}
[
{
"event_id": "evt-001",
"session_id": "session-001",
"event": "page_view",
"timestamp": "2024-01-15T10:30:00Z",
"properties": {
"url": "/landing",
"title": "Landing Page"
}
},
{
"event_id": "evt-002",
"session_id": "session-001",
"event": "button_click",
"timestamp": "2024-01-15T10:32:15Z",
"properties": {
"button_text": "Learn More"
}
},
{
"event_id": "evt-003",
"session_id": "session-002",
"event": "form_submit",
"timestamp": "2024-01-20T16:05:00Z",
"properties": {
"form_id": "signup"
}
}
]
```
## JavaScript API
### View Journey Data
```javascript theme={null}
// Get complete user journey
const data = glue.journey.getCompleteData();
console.log(data);
/* Returns:
{
profile: {...},
sessions: [...],
events: [...],
summary: {
total_sessions: 3,
total_events: 16,
converted_sessions: 1,
attribution: {
first_touch: { utm_source: "google" },
last_touch: { utm_source: "email" }
}
}
}
*/
// Get just the profile
const profile = glue.journey.getProfile();
// Get all sessions
const sessions = glue.journey.getSessions();
// Get all events
const events = glue.journey.getEvents();
// Export as JSON (for download/debugging)
const json = glue.journey.exportData();
console.log(json); // Pretty-printed JSON string
```
### Manual Tracking
```javascript theme={null}
// Track custom event
glue.journey.track('video_played', {
video_id: '123',
duration: 45
});
// Manually identify user
glue.journey.identify('user@example.com', {
fname: 'John',
lname: 'Doe',
plan: 'premium'
});
// Clear all journey data
glue.journey.clear();
```
### Server Sync
```javascript theme={null}
// Manually send identify to server
await glue.sync.identify('user@example.com', {
fname: 'John',
subscription: 'pro'
});
// Manually track event to server
await glue.sync.track('purchase', {
product_id: '123',
amount: 99.00
});
// Sync all local data to server
await glue.sync.syncAll();
// Configure sync settings
glue.sync.config({
autoSync: true,
syncInterval: 30000 // 30 seconds
});
```
## Server-Side API
Your server needs to handle these endpoints:
### POST /glue/identify
Links `glue_user_id` to email address.
**Request:**
```json theme={null}
{
"glue_user_id": "abc-123-uuid",
"email": "user@example.com",
"traits": {
"fname": "John",
"lname": "Doe"
},
"context": {
"session_id": "session-456",
"utm_source": "google",
"utm_campaign": "summer",
"page": {
"url": "https://example.com/landing",
"title": "Landing Page"
},
"visitor": {
"user_agent": "Mozilla/5.0...",
"geolocation": "US | New York",
"timezone": "America/New_York"
}
}
}
```
**Example Handler (Node.js):**
```javascript theme={null}
app.post('/glue/identify', async (req, res) => {
const { glue_user_id, email, traits, context } = req.body;
// Save to database (MongoDB example)
await db.users.updateOne(
{ glue_user_id },
{
$set: {
email,
traits,
last_seen: new Date(),
'attribution.last_touch': {
utm_source: context.utm_source,
utm_campaign: context.utm_campaign,
timestamp: new Date()
}
},
$setOnInsert: {
glue_user_id,
first_seen: new Date(),
'attribution.first_touch': {
utm_source: context.utm_source,
utm_campaign: context.utm_campaign,
timestamp: new Date()
}
}
},
{ upsert: true }
);
res.json({ success: true });
});
```
### POST /glue/track
Records individual events.
**Request:**
```json theme={null}
{
"glue_user_id": "abc-123-uuid",
"session_id": "session-456",
"email": "user@example.com",
"event": "button_click",
"properties": {
"button_text": "Get Started",
"page": "/landing"
},
"timestamp": 1704376800000,
"context": {
"utm_source": "google"
}
}
```
**Example Handler:**
```javascript theme={null}
app.post('/glue/track', async (req, res) => {
const { glue_user_id, event, properties, timestamp } = req.body;
// Insert event
await db.events.insert({
glue_user_id,
event,
properties,
timestamp: new Date(timestamp)
});
// Update user last_seen
await db.users.updateOne(
{ glue_user_id },
{ $set: { last_seen: new Date() } }
);
res.json({ success: true });
});
```
### POST /glue/sync
Receives complete user data (all sessions + events).
**Request:**
```json theme={null}
{
"glue_user_id": "abc-123-uuid",
"data": {
"profile": {...},
"sessions": [...],
"events": [...],
"summary": {...}
},
"synced_at": "2024-01-20T16:30:00Z"
}
```
**Example Handler:**
```javascript theme={null}
app.post('/glue/sync', async (req, res) => {
const { glue_user_id, data } = req.body;
// Save complete user data
await db.users.updateOne(
{ glue_user_id },
{ $set: { ...data.profile, last_synced: new Date() } },
{ upsert: true }
);
// Bulk insert events
if (data.events.length > 0) {
await db.events.insertMany(data.events, { ordered: false });
}
res.json({ success: true });
});
```
## Configuration Options
### Script Tag Attributes
```html theme={null}
```
| Attribute | Default | Description |
| ------------------ | ---------------------- | ------------------------------------------ |
| `profile-endpoint` | - | Your API base URL |
| `auto-identify` | `true` | Auto-identify from email fields/URL params |
| `auto-sync` | `false` | Automatically sync data to server |
| `track-events` | `pageview,form_submit` | Events to auto-track (comma-separated) |
### Event Types
Auto-trackable events:
* `pageview` - Page views
* `click` - Link and button clicks
* `form_submit` - Form submissions
## Query Examples
### Get User Journey
```javascript theme={null}
// Client-side
const journey = glue.journey.getCompleteData();
// Server-side (MongoDB)
const user = await db.users.findOne({ email: "john@example.com" });
const events = await db.events.find({ glue_user_id: user.glue_user_id }).sort({ timestamp: 1 });
```
### Conversion Funnel
```javascript theme={null}
// Server-side
const landed = await db.events.countDocuments({
event: "page_view",
"properties.path": "/landing"
});
const submitted = await db.events.countDocuments({
event: "form_submit"
});
const converted = await db.events.countDocuments({
event: "page_view",
"properties.path": "/thank-you"
});
console.log(`Funnel: ${landed} → ${submitted} → ${converted}`);
```
### Attribution Report
```javascript theme={null}
// Server-side
const sources = await db.users.aggregate([
{
$group: {
_id: "$attribution.first_touch.utm_source",
count: { $sum: 1 }
}
}
]);
```
## Best Practices
1. **Let forms redirect with query params** - Easiest way to pass data between pages
2. **Use meaningful event names** - `video_played` not `event_1`
3. **Keep properties simple** - JSON-serializable values only
4. **Implement server endpoints** - Don't rely solely on localStorage
5. **Test with different form providers** - Fillout, Typeform, native HTML
## Troubleshooting
### Check localStorage Data
```javascript theme={null}
// Open browser console
console.log(JSON.parse(localStorage.getItem('glue_user_profile')));
console.log(JSON.parse(localStorage.getItem('glue_sessions')));
console.log(JSON.parse(localStorage.getItem('glue_events')));
```
### Debug Mode
```javascript theme={null}
// Enable detailed logging
localStorage.setItem('glue_dev_mode', 'true');
location.reload();
```
### Export Journey
```javascript theme={null}
// Download complete journey as JSON file
const data = glue.journey.exportData();
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'user-journey.json';
a.click();
```
## FAQ
**Q: Does this work with iframes?**
A: Yes! For embedded forms, they redirect with query params. DataGlue on the next page auto-identifies from URL.
**Q: What if my server is down?**
A: All data stays in localStorage. When server comes back up, call `glue.sync.syncAll()`.
**Q: How much localStorage space does this use?**
A: \~10-50KB per user. DataGlue limits to 1000 events and 100 sessions automatically.
**Q: Can I use this with Segment/Mixpanel?**
A: Yes! Use `glue.journey.track()` and send to both DataGlue and your analytics tool.
**Q: How do I handle multiple devices?**
A: Each device has its own `glue_user_id`. When email is captured, your server links all IDs to one user.
# Glue Attributes Reference
Source: https://dataglue.io/reference/glue-attributes
Complete reference guide for all DataGlue attributes, their purposes, storage mechanisms, and usage patterns.
This comprehensive reference covers all attributes captured and managed by DataGlue, including their storage locations, purposes, and practical usage examples.
## Attribute Overview
DataGlue captures and manages visitor attributes using a consistent `glue_` prefix for easy identification. All attributes are stored across multiple storage mechanisms for redundancy and accessibility.
```mermaid theme={null}
graph TB
subgraph "Data Sources"
URL[URL Parameters]
COOK[Browser Cookies]
TT[TikTok SessionStorage]
FB[Facebook Cookies]
GEO[Geolocation API]
UA[User Agent]
end
subgraph "DataGlue Core"
INIT[Initialization]
ATTR[Attribution Module]
STOR[Storage Module]
FORM[Form Integration]
DYN[Dynamic Content]
end
subgraph "Storage Mechanisms"
LS[localStorage]
SS[sessionStorage]
CK[Cookies]
end
URL --> INIT
COOK --> ATTR
TT --> ATTR
FB --> ATTR
GEO --> ATTR
UA --> ATTR
INIT --> ATTR
ATTR --> STOR
STOR --> LS
STOR --> SS
STOR --> CK
```
### Storage Strategy
**Persistent storage** - Survives browser restarts
* User identification data
* Attribution history
* First-visit information
* Geolocation data
**Session-only storage** - Cleared when tab closes
* Current session data
* URL tracking
* Browser information
* Temporary values
**Cross-tab persistent** - Available to server-side code
* User ID with expiration
* Attribution data with TTL
* Third-party integration data
## User Identification Attributes
### `glue_user_id`
Unique identifier for each visitor using UUID v7 format
**Storage**: localStorage, cookies (365 days)
**Auto-generated**: Yes, on first visit
**Example**: `01234567-89ab-cdef-0123-456789abcdef`
```javascript theme={null}
// Access user ID
const userId = window.glue.storage.get('glue_user_id');
console.log('Visitor ID:', userId);
```
### `glue_user_firstseen`
ISO timestamp recording when the user first visited the site
**Storage**: localStorage
**Auto-generated**: Yes, on first visit only
**Example**: `2024-01-15T10:30:00.000Z`
### `glue_session_start`
ISO timestamp recording when the current session started
**Storage**: sessionStorage
**Auto-generated**: Yes, updates on each page load
**Example**: `2024-01-15T14:20:00.000Z`
## Attribution Tracking Attributes
DataGlue captures both initial and last-touch attribution for comprehensive tracking.
**First vs Last Touch**: DataGlue preserves both the first touchpoint (how they originally found you) and the last touchpoint (what brought them back) for accurate attribution.
### UTM Parameters
#### `glue_initial_utm_source` / `glue_last_utm_source`
Tracks traffic source (google, facebook, newsletter, etc.)
**Storage**: localStorage, cookies (365 days)
**Examples**: `google`, `facebook`, `newsletter`, `linkedin`
```html theme={null}
```
#### `glue_initial_utm_medium` / `glue_last_utm_medium`
Tracks marketing medium (cpc, email, social, etc.)
**Storage**: localStorage, cookies (365 days)
**Examples**: `cpc`, `email`, `social`, `organic`
#### `glue_initial_utm_campaign` / `glue_last_utm_campaign`
Tracks campaign name
**Storage**: localStorage, cookies (365 days)
**Examples**: `summer_sale`, `product_launch`, `retargeting_q1`
#### `glue_initial_utm_term` / `glue_last_utm_term`
Tracks search keywords or ad targeting
**Storage**: localStorage, cookies (365 days)
**Examples**: `data analytics`, `tracking software`, `crm integration`
#### `glue_initial_utm_content` / `glue_last_utm_content`
Tracks content variation or A/B test version
**Storage**: localStorage, cookies (365 days)
**Examples**: `header_cta`, `sidebar_ad`, `banner_v2`
#### `glue_initial_utm_id` / `glue_last_utm_id`
Unique campaign identifier for advanced tracking
**Storage**: localStorage, cookies (365 days)
**Examples**: `camp_12345`, `promo_summer_2024`
### Platform-Specific Attribution
#### Facebook Click ID
Facebook click identifier for ad attribution
**Storage**: localStorage, cookies (365 days)
**Example**: `IwAR0Kg4h2k3j5l6m7n8o9p0q1r2s3t4u5v6w7x8y9z`
#### Google Click ID
Google Ads click identifier for conversion tracking
**Storage**: localStorage, cookies (365 days)
**Example**: `CjwKCAjw_b-WBhAb123Example`
#### TikTok Tracking ID
TikTok tracking identifier for ad attribution
**Storage**: localStorage, cookies (365 days)
**Example**: `tt_abc123def456`
## Session Tracking Attributes
### URL Tracking
#### `glue_url_current`
Current page URL
**Storage**: sessionStorage
**Auto-generated**: Yes, updates on each page load
**Example**: `https://example.com/product/details`
#### `glue_url_last`
Previous page URL (referrer)
**Storage**: sessionStorage
**Auto-generated**: Yes, if referrer exists
**Example**: `https://google.com/search?q=example`
#### `glue_url_first`
First landing page URL on the domain
**Storage**: localStorage
**Auto-generated**: Yes, stored only once
**Example**: `https://example.com/landing-page`
#### `glue_user_referrer`
Domain of the referring site
**Storage**: localStorage
**Auto-generated**: Yes, if referrer exists and not set
**Example**: `google.com`, `facebook.com`, `linkedin.com`
### Browser Information
#### `glue_user_browser`
Browser detection results in JSON format
**Storage**: sessionStorage
**Auto-generated**: Yes
**Example**:
```json theme={null}
{
"browser": "Chrome",
"version": "120",
"os": "macOS",
"device": "desktop"
}
```
## Form Data Attributes
### Personal Information
#### `glue_fname`
First name from forms or URL parameters
**Storage**: localStorage, sessionStorage, cookies (30 days)
**Sources**: URL params (`?fname=John`), form inputs, manual API calls
```html theme={null}
```
#### `glue_lname`
Last name from forms or URL parameters
**Storage**: localStorage, sessionStorage, cookies (30 days)
**Sources**: URL params (`?lname=Doe`), form inputs, manual API calls
#### `glue_email`
Email address from forms or URL parameters
**Storage**: localStorage, sessionStorage, cookies (30 days)
**Sources**: URL params (`?email=john@example.com`), form inputs, manual API calls
```html theme={null}
```
#### `glue_mobile`
Phone number from forms or URL parameters
**Storage**: localStorage, sessionStorage, cookies (30 days)
**Sources**: URL params (`?mobile=+1234567890`), form inputs, manual API calls
```html theme={null}
```
### Special Parameters
#### Calendly Integration
Calendly meeting or scheduling parameter
**Storage**: localStorage, sessionStorage, cookies (30 days)
**Example**: `scheduled`, `completed`, `cancelled`
```html theme={null}
```
## Third-Party Integration Attributes
### TikTok Attribution
DataGlue automatically captures TikTok attribution data from sessionStorage:
#### `glue_tt_appInfo`
TikTok application information
**Source**: TikTok sessionStorage (`tt_appInfo`)
**Storage**: localStorage, sessionStorage, cookies (30 days)
#### `glue_tt_pixel_session_index`
TikTok pixel session tracking index
**Source**: TikTok sessionStorage (`tt_pixel_session_index`)
**Storage**: localStorage, sessionStorage, cookies (30 days)
#### `glue_tt_sessionId`
TikTok session identifier
**Source**: TikTok sessionStorage (`tt_sessionId`)
**Storage**: localStorage, sessionStorage, cookies (30 days)
### Facebook Attribution
DataGlue automatically captures Facebook attribution data from cookies:
#### `glue_fb_fbp`
Facebook browser pixel identifier
**Source**: Facebook cookie (`_fbp`)
**Storage**: localStorage, sessionStorage, cookies (30 days)
**Example**: `fb.1.1640995200000.1234567890`
#### `glue_fb_fbc`
Facebook click identifier
**Source**: Facebook cookie (`_fbc`)
**Storage**: localStorage, sessionStorage, cookies (30 days)
**Example**: `fb.1.1640995200000.AbCdEf123456`
## Geolocation Attributes
### `glue_user_geolocation`
IP-based geolocation data in formatted string
**Storage**: localStorage
**Source**: geojs.io API (asynchronous)
**Format**: `{country_code} | {country} | {city} | {latitude}, {longitude}`
**Example**: `US | United States | New York | 40.7128, -74.0060`
Geolocation data is fetched asynchronously and may not be immediately available on the first page load.
```javascript theme={null}
// Check if geolocation data is available
const geoData = window.glue.storage.get('glue_user_geolocation');
if (geoData) {
const [countryCode, country, city, coords] = geoData.split(' | ');
console.log('User location:', { countryCode, country, city, coords });
}
```
## Element Selectors & Form Integration
DataGlue uses sophisticated selector patterns to find and populate elements:
### Selector Patterns
```html theme={null}
```
```html theme={null}
```
```html theme={null}
Content
```
```html theme={null}
Content
```
```html theme={null}
```
### Fillout Form Integration
For Fillout forms, DataGlue automatically adds data attributes:
```html theme={null}
```
#### Valid Prefixes for Fillout
All attributes starting with `glue_` are automatically added:
* `data-glue_user_id`
* `data-glue_email`
* `data-glue_fname`
* etc.
All UTM parameters are added with `data-utm_` prefix:
* `data-utm_source`
* `data-utm_campaign`
* `data-utm_medium`
* etc.
All URL query parameters are added as data attributes:
* `?custom_param=value` → `data-custom_param="value"`
* `?lead_source=webinar` → `data-lead_source="webinar"`
## API Usage Examples
### Reading Attributes
```javascript Basic Access theme={null}
// Get specific attribute
const userEmail = window.glue.storage.get('glue_email');
const utmSource = window.glue.storage.get('glue_initial_utm_source');
// Check if attribute exists
if (userEmail) {
console.log('User email:', userEmail);
}
```
```javascript Visitor Context theme={null}
// Get complete visitor context
const context = window.glue.getVisitorContext();
console.log('Complete context:', context);
// Get attribution data only
const attribution = window.glue.getAttribution();
console.log('Attribution:', attribution);
```
```javascript Multiple Attributes theme={null}
// Get all glue attributes
const allGlueData = Object.keys(localStorage)
.filter(key => key.startsWith('glue_'))
.reduce((acc, key) => {
acc[key] = localStorage.getItem(key);
return acc;
}, {});
console.log('All DataGlue attributes:', allGlueData);
```
### Setting Attributes
```javascript Basic Setting theme={null}
// Set with localStorage only
window.glue.storage.set('glue_custom_attr', 'value');
// Set with cookie expiration (30 days)
window.glue.storage.set('glue_user_tier', 'premium', 30);
```
```javascript Attribution Override theme={null}
// Manual attribution setting
window.glue.setAttribution({
utm_source: 'manual_override',
utm_campaign: 'special_promotion'
});
```
```javascript Bulk Setting theme={null}
// Set multiple attributes
const userData = {
'glue_company': 'Acme Corp',
'glue_title': 'Marketing Manager',
'glue_industry': 'Technology'
};
Object.entries(userData).forEach(([key, value]) => {
window.glue.storage.set(key, value, 30);
});
```
### Clearing Attributes
```javascript Single Attribute theme={null}
// Clear specific attribute
window.glue.storage.remove('glue_custom_attr');
```
```javascript All Attributes theme={null}
// Clear all DataGlue storage
window.glue.storage.clear();
```
```javascript Selective Clearing theme={null}
// Clear only UTM data
Object.keys(localStorage)
.filter(key => key.includes('utm_'))
.forEach(key => localStorage.removeItem(key));
```
## Browser Compatibility
| Feature | Chrome | Firefox | Safari | Edge | IE11 |
| -------------- | ------ | ------- | ------ | ---- | ---- |
| localStorage | ✅ | ✅ | ✅ | ✅ | ✅ |
| sessionStorage | ✅ | ✅ | ✅ | ✅ | ✅ |
| Cookies | ✅ | ✅ | ✅ | ✅ | ✅ |
| Feature | Chrome | Firefox | Safari | Edge | IE11 |
| ---------------- | ------ | ------- | ------ | ---- | ---- |
| URL SearchParams | ✅ | ✅ | ✅ | ✅ | ❌\* |
| UUID v7 | ✅ | ✅ | ✅ | ✅ | ❌\* |
| Geolocation API | ✅ | ✅ | ✅ | ✅ | ✅ |
| CSS.escape() | ✅ | ✅ | ✅ | ✅ | ❌\* |
\*Requires polyfill for IE11 support
## Best Practices
**Naming Convention**: Use descriptive names for custom attributes and maintain the `glue_` prefix for consistency.
**Data Privacy**: Ensure compliance with privacy regulations when collecting personal information. DataGlue respects `doNotTrack` browser settings.
### Recommended Patterns
1. **Consistent Prefixing**: Always use `glue_` prefix for custom attributes
2. **Descriptive Names**: Use clear, meaningful names like `glue_lead_source` instead of `glue_ls`
3. **Appropriate Expiration**: Set reasonable cookie expiration times based on data sensitivity
4. **Graceful Fallbacks**: Always check if data exists before using it
### Security Considerations
* DataGlue automatically sanitizes stored values to prevent XSS
* No sensitive data is stored without explicit configuration
* All data is stored client-side unless explicitly sent to servers
* Uses CSS.escape() to safely handle special characters in selectors
# Server Implementation
Source: https://dataglue.io/server-implementation
Complete guide to implementing DataGlue server-side endpoints
# Server Implementation Guide
This guide shows you exactly how to build the server-side API to receive and store DataGlue journey data.
## Database Setup
You need **2 collections/tables**:
### MongoDB Collections
```javascript theme={null}
// Collection: users
{
glue_user_id: String (indexed, unique),
email: String (indexed),
traits: Object,
attribution: {
first_touch: Object,
last_touch: Object
},
first_seen: Date,
last_seen: Date,
identified: Boolean,
identified_at: Date
}
// Collection: events
{
event_id: String (indexed, unique),
glue_user_id: String (indexed),
session_id: String (indexed),
event: String (indexed),
timestamp: Date (indexed),
properties: Object,
email: String (indexed, optional)
}
```
### PostgreSQL Tables
```sql theme={null}
-- Table: users
CREATE TABLE users (
id SERIAL PRIMARY KEY,
glue_user_id VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255),
traits JSONB,
attribution JSONB,
first_seen TIMESTAMP,
last_seen TIMESTAMP,
identified BOOLEAN DEFAULT FALSE,
identified_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_users_glue_user_id ON users(glue_user_id);
CREATE INDEX idx_users_email ON users(email);
-- Table: events
CREATE TABLE events (
id SERIAL PRIMARY KEY,
event_id VARCHAR(255) UNIQUE NOT NULL,
glue_user_id VARCHAR(255) NOT NULL,
session_id VARCHAR(255),
event VARCHAR(100) NOT NULL,
timestamp TIMESTAMP NOT NULL,
properties JSONB,
email VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_events_glue_user_id ON events(glue_user_id);
CREATE INDEX idx_events_session_id ON events(session_id);
CREATE INDEX idx_events_event ON events(event);
CREATE INDEX idx_events_timestamp ON events(timestamp);
CREATE INDEX idx_events_email ON events(email);
```
***
## Complete Server Implementation
### Node.js + Express + MongoDB
```javascript theme={null}
const express = require('express');
const { MongoClient } = require('mongodb');
const app = express();
app.use(express.json());
// MongoDB connection
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('dataglue');
const users = db.collection('users');
const events = db.collection('events');
// Create indexes
async function createIndexes() {
await users.createIndex({ glue_user_id: 1 }, { unique: true });
await users.createIndex({ email: 1 });
await events.createIndex({ event_id: 1 }, { unique: true });
await events.createIndex({ glue_user_id: 1 });
await events.createIndex({ session_id: 1 });
await events.createIndex({ event: 1 });
await events.createIndex({ timestamp: -1 });
}
/**
* POST /glue/identify
* Links glue_user_id to email address
*/
app.post('/glue/identify', async (req, res) => {
try {
const { glue_user_id, email, traits, context } = req.body;
// Validate required fields
if (!glue_user_id || !email) {
return res.status(400).json({
success: false,
error: 'glue_user_id and email are required'
});
}
// Upsert user profile
const result = await users.updateOne(
{ glue_user_id },
{
$set: {
email,
traits: traits || {},
last_seen: new Date(),
identified: true,
identified_at: new Date(),
'attribution.last_touch': {
utm_source: context?.utm_source,
utm_medium: context?.utm_medium,
utm_campaign: context?.utm_campaign,
referrer: context?.referrer,
landing_page: context?.page?.url,
timestamp: new Date()
}
},
$setOnInsert: {
glue_user_id,
first_seen: new Date(),
'attribution.first_touch': {
utm_source: context?.utm_source,
utm_medium: context?.utm_medium,
utm_campaign: context?.utm_campaign,
referrer: context?.referrer,
landing_page: context?.page?.url,
timestamp: new Date()
}
}
},
{ upsert: true }
);
console.log(`User identified: ${email} (${glue_user_id})`);
res.json({
success: true,
user_id: glue_user_id,
new_user: result.upsertedCount > 0
});
} catch (error) {
console.error('Identify error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
/**
* POST /glue/track
* Records individual events
*/
app.post('/glue/track', async (req, res) => {
try {
const {
glue_user_id,
session_id,
email,
event,
properties,
timestamp,
context
} = req.body;
// Validate required fields
if (!glue_user_id || !event) {
return res.status(400).json({
success: false,
error: 'glue_user_id and event are required'
});
}
// Insert event
const eventDoc = {
event_id: `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
glue_user_id,
session_id,
email,
event,
properties: properties || {},
timestamp: new Date(timestamp || Date.now()),
context: context || {},
created_at: new Date()
};
await events.insertOne(eventDoc);
// Update user last_seen
await users.updateOne(
{ glue_user_id },
{
$set: { last_seen: new Date() },
$inc: { total_events: 1 }
}
);
console.log(`Event tracked: ${event} for ${glue_user_id}`);
res.json({
success: true,
event_id: eventDoc.event_id
});
} catch (error) {
console.error('Track error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
/**
* POST /glue/sync
* Receives complete user data (bulk sync)
*/
app.post('/glue/sync', async (req, res) => {
try {
const { glue_user_id, data, synced_at } = req.body;
if (!glue_user_id || !data) {
return res.status(400).json({
success: false,
error: 'glue_user_id and data are required'
});
}
// Update user profile
await users.updateOne(
{ glue_user_id },
{
$set: {
...data.profile,
last_synced: new Date(synced_at),
total_sessions: data.sessions?.length || 0,
total_events: data.events?.length || 0
}
},
{ upsert: true }
);
// Bulk insert events (ignore duplicates)
if (data.events && data.events.length > 0) {
try {
await events.insertMany(data.events, { ordered: false });
} catch (error) {
// Ignore duplicate key errors (events already synced)
if (error.code !== 11000) {
throw error;
}
}
}
console.log(`Data synced for ${glue_user_id}: ${data.events?.length || 0} events`);
res.json({
success: true,
events_synced: data.events?.length || 0,
sessions_synced: data.sessions?.length || 0
});
} catch (error) {
console.error('Sync error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
/**
* GET /glue/user/:glue_user_id
* Retrieve complete user journey
*/
app.get('/glue/user/:glue_user_id', async (req, res) => {
try {
const { glue_user_id } = req.params;
// Get user profile
const user = await users.findOne({ glue_user_id });
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
// Get all events for this user
const userEvents = await events
.find({ glue_user_id })
.sort({ timestamp: 1 })
.toArray();
res.json({
success: true,
user,
events: userEvents,
summary: {
total_events: userEvents.length,
first_event: userEvents[0]?.timestamp,
last_event: userEvents[userEvents.length - 1]?.timestamp
}
});
} catch (error) {
console.error('Get user error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
/**
* GET /glue/user/email/:email
* Retrieve user by email
*/
app.get('/glue/user/email/:email', async (req, res) => {
try {
const { email } = req.params;
const user = await users.findOne({ email });
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
// Get all events
const userEvents = await events
.find({ glue_user_id: user.glue_user_id })
.sort({ timestamp: 1 })
.toArray();
res.json({
success: true,
user,
events: userEvents
});
} catch (error) {
console.error('Get user by email error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
await createIndexes();
console.log(`DataGlue API server running on port ${PORT}`);
});
```
***
## Deploy to Production
### Option 1: Vercel/Netlify Functions
Create `api/glue/identify.js`:
```javascript theme={null}
const { MongoClient } = require('mongodb');
let cachedDb = null;
async function connectToDatabase() {
if (cachedDb) return cachedDb;
const client = await MongoClient.connect(process.env.MONGODB_URI);
cachedDb = client.db('dataglue');
return cachedDb;
}
module.exports = async (req, res) => {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const db = await connectToDatabase();
const { glue_user_id, email, traits, context } = req.body;
await db.collection('users').updateOne(
{ glue_user_id },
{
$set: {
email,
traits,
identified: true,
last_seen: new Date()
}
},
{ upsert: true }
);
res.json({ success: true });
};
```
### Option 2: Railway/Render
```bash theme={null}
# Deploy Express app directly
npm install express mongodb
node server.js
```
### Option 3: Cloudflare Workers
```javascript theme={null}
export default {
async fetch(request, env) {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const data = await request.json();
// Store in D1 (Cloudflare's database)
await env.DB.prepare(
'INSERT INTO users (glue_user_id, email, traits) VALUES (?, ?, ?)'
).bind(data.glue_user_id, data.email, JSON.stringify(data.traits)).run();
return Response.json({ success: true });
}
};
```
***
## Environment Variables
```bash theme={null}
# .env file
MONGODB_URI=mongodb://localhost:27017/dataglue
PORT=3000
# Production (Vercel/Netlify)
MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/dataglue
```
***
## Testing Your Server
### Test Identify Endpoint
```bash theme={null}
curl -X POST http://localhost:3000/glue/identify \
-H "Content-Type: application/json" \
-d '{
"glue_user_id": "test-123",
"email": "test@example.com",
"traits": {
"fname": "Test",
"lname": "User"
},
"context": {
"utm_source": "google",
"session_id": "session-456"
}
}'
```
### Test Track Endpoint
```bash theme={null}
curl -X POST http://localhost:3000/glue/track \
-H "Content-Type: application/json" \
-d '{
"glue_user_id": "test-123",
"session_id": "session-456",
"event": "page_view",
"properties": {
"url": "/landing",
"title": "Landing Page"
},
"timestamp": 1704376800000
}'
```
### Test Get User
```bash theme={null}
curl http://localhost:3000/glue/user/test-123
```
***
## Query Examples
### Get User Journey
```javascript theme={null}
// Get complete user journey
const user = await users.findOne({ email: 'user@example.com' });
const journey = await events
.find({ glue_user_id: user.glue_user_id })
.sort({ timestamp: 1 })
.toArray();
console.log(`${user.email} journey:`, journey);
```
### Conversion Funnel
```javascript theme={null}
const funnel = await events.aggregate([
{
$match: {
event: { $in: ['page_view', 'form_submit', 'purchase'] }
}
},
{
$group: {
_id: '$event',
count: { $sum: 1 }
}
}
]).toArray();
```
### Attribution Report
```javascript theme={null}
const sources = await users.aggregate([
{
$group: {
_id: '$attribution.first_touch.utm_source',
count: { $sum: 1 },
conversions: {
$sum: { $cond: ['$identified', 1, 0] }
}
}
},
{ $sort: { count: -1 } }
]).toArray();
```
***
## Security Best Practices
1. **Validate requests**
```javascript theme={null}
if (!glue_user_id || !email) {
return res.status(400).json({ error: 'Invalid request' });
}
```
2. **Rate limiting**
```javascript theme={null}
const rateLimit = require('express-rate-limit');
app.use('/glue', rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100 // 100 requests per minute
}));
```
3. **CORS configuration**
```javascript theme={null}
app.use(cors({
origin: ['https://yoursite.com'],
methods: ['POST']
}));
```
4. **API key authentication (optional)**
```javascript theme={null}
app.use('/glue', (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (apiKey !== process.env.API_KEY) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
});
```
***
## Next Steps
1. ✅ Deploy server to production
2. ✅ Add `profile-endpoint` to your DataGlue script tag
3. ✅ Test with a real form submission
4. ✅ Query your database to see user journeys
5. ✅ Build analytics dashboards on top of this data
# Server-Side Setup Guide
Source: https://dataglue.io/server-setup
Complete guide to setting up a backend for DataGlue journey tracking
# Server-Side Setup Guide
This guide shows you how to set up a backend to collect and manage DataGlue attribution and journey data. Choose the approach that best fits your stack.
## Architecture Overview
```mermaid theme={null}
graph TB
subgraph "User's Browser"
A[User visits site] --> B[DataGlue tracks]
B --> C[localStorage: sessions, events, profile]
C --> D[User fills form]
D --> E[Form submits with journey data]
end
subgraph "Server Options"
E --> F{Choose Backend}
F -->|Option 1| G[N8N Webhooks]
F -->|Option 2| H[Serverless Functions]
F -->|Option 3| I[Custom Server]
end
subgraph "Database"
G --> J[(Supabase/PostgreSQL)]
H --> J
I --> J
J --> K[users table]
J --> L[events table]
end
subgraph "Query & Analytics"
K --> M[User Journeys]
L --> M
M --> N[Analytics Dashboard]
M --> O[Attribution Reports]
M --> P[Conversion Funnels]
end
```
## Data Flow Diagram
```mermaid theme={null}
sequenceDiagram
participant Browser
participant DataGlue
participant Form
participant Server
participant Database
Browser->>DataGlue: Page load
DataGlue->>DataGlue: Generate glue_user_id
DataGlue->>DataGlue: Store in localStorage
Note over DataGlue: Tracks sessions & events
Browser->>Form: User fills email
DataGlue->>DataGlue: Detects email
DataGlue->>DataGlue: Updates profile
Browser->>Form: User submits
DataGlue->>Form: Inject hidden fields
Note over Form: glue_user_id, session_id, total_sessions, attribution, etc.
Form->>Server: POST with journey data
Server->>Database: Check if email exists
alt User Exists (Cross-Device)
Database->>Server: User found
Server->>Database: Merge glue_user_ids
Server->>Database: Link devices
else New User
Database->>Server: Not found
Server->>Database: Create new user
end
Server->>Database: Insert events
Server->>Browser: Success response
```
***
## Option 1: N8N Webhooks + Supabase (Easiest)
**Best for:** Non-developers, rapid prototyping, visual workflows
### Architecture
```mermaid theme={null}
graph LR
A[DataGlue] -->|POST /identify| B[N8N Webhook]
A -->|POST /track| C[N8N Webhook]
A -->|POST /sync| D[N8N Webhook]
B --> E{User Exists?}
E -->|Yes| F[Merge Devices]
E -->|No| G[Create User]
F --> H[(Supabase)]
G --> H
C --> H
D --> H
H --> I[users table]
H --> J[events table]
```
### Setup Steps
#### 1. Create Supabase Project
```sql theme={null}
-- Create users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
glue_user_id TEXT UNIQUE NOT NULL,
email TEXT,
traits JSONB DEFAULT '{}'::jsonb,
linked_ids TEXT[] DEFAULT ARRAY[]::TEXT[],
first_seen TIMESTAMPTZ DEFAULT NOW(),
last_seen TIMESTAMPTZ DEFAULT NOW(),
first_touch_source TEXT,
first_touch_medium TEXT,
first_touch_campaign TEXT,
last_touch_source TEXT,
last_touch_medium TEXT,
last_touch_campaign TEXT,
total_sessions INTEGER DEFAULT 0,
total_events INTEGER DEFAULT 0,
identified BOOLEAN DEFAULT FALSE,
identified_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_glue_user_id ON users(glue_user_id);
-- Create events table
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
event_id TEXT UNIQUE NOT NULL,
glue_user_id TEXT NOT NULL,
session_id TEXT,
event TEXT NOT NULL,
properties JSONB DEFAULT '{}'::jsonb,
timestamp TIMESTAMPTZ NOT NULL,
email TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_events_glue_user_id ON events(glue_user_id);
CREATE INDEX idx_events_session_id ON events(session_id);
CREATE INDEX idx_events_event ON events(event);
CREATE INDEX idx_events_timestamp ON events(timestamp DESC);
```
#### 2. Create N8N Workflows
**Workflow 1: Identify User**
```
Webhook (POST /webhook/glue/identify)
↓
Supabase: UPSERT users
SET email, traits, last_seen, identified = true
ON CONFLICT (glue_user_id) DO UPDATE
↓
Check for Email Duplicates
SELECT * FROM users WHERE email = {{ email }} AND glue_user_id != {{ glue_user_id }}
↓
IF duplicates found:
Supabase: UPDATE users
SET linked_ids = array_append(linked_ids, {{ new_glue_user_id }})
↓
Respond: { success: true }
```
**Workflow 2: Track Event**
```
Webhook (POST /webhook/glue/track)
↓
Supabase: INSERT INTO events
VALUES (event_id, glue_user_id, event, properties, timestamp)
↓
Supabase: UPDATE users
SET last_seen = NOW(), total_events = total_events + 1
↓
Respond: { success: true }
```
**Workflow 3: Sync Complete Journey**
```
Webhook (POST /webhook/glue/sync)
↓
Parse journey_data JSON
↓
Loop through events array
↓
Supabase: INSERT INTO events (ON CONFLICT DO NOTHING)
↓
Supabase: UPSERT user profile
↓
Respond: { success: true, events_synced: X }
```
#### 3. Configure DataGlue
```html theme={null}
```
### Pros & Cons
**Pros:**
* ✅ No code required (visual workflows)
* ✅ Built-in error handling & retries
* ✅ Easy debugging (see every request)
* ✅ Can add multiple outputs (Slack, email, etc.)
* ✅ Supabase has generous free tier
**Cons:**
* ❌ N8N costs \$20/month (or self-host)
* ❌ Slightly slower than custom server
* ❌ Limited to N8N's capabilities
***
## Option 2: Serverless Functions (Vercel/Netlify)
**Best for:** Next.js/React apps, developers who want simplicity
### Architecture
```mermaid theme={null}
graph LR
A[DataGlue] -->|POST| B[API Route]
B --> C{Function Type}
C -->|/api/identify| D[Identify Function]
C -->|/api/track| E[Track Function]
C -->|/api/sync| F[Sync Function]
D --> G[(Supabase)]
E --> G
F --> G
G --> H[PostgreSQL]
```
### Setup Steps
#### 1. Install Dependencies
```bash theme={null}
npm install @supabase/supabase-js
```
#### 2. Create API Routes
**File: `pages/api/glue/identify.js` (Next.js)**
```javascript theme={null}
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_KEY
)
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
const { glue_user_id, email, traits, context } = req.body
if (!glue_user_id || !email) {
return res.status(400).json({ error: 'Missing required fields' })
}
try {
// Check if user with this email already exists
const { data: existingUser } = await supabase
.from('users')
.select('*')
.eq('email', email)
.single()
if (existingUser && existingUser.glue_user_id !== glue_user_id) {
// Cross-device: merge the IDs
await supabase
.from('users')
.update({
linked_ids: [...(existingUser.linked_ids || []), glue_user_id]
})
.eq('glue_user_id', existingUser.glue_user_id)
// Update all events to point to primary ID
await supabase
.from('events')
.update({ glue_user_id: existingUser.glue_user_id })
.eq('glue_user_id', glue_user_id)
} else {
// Upsert user
await supabase
.from('users')
.upsert({
glue_user_id,
email,
traits,
identified: true,
identified_at: new Date().toISOString(),
last_seen: new Date().toISOString(),
last_touch_source: context?.utm_source,
last_touch_medium: context?.utm_medium,
last_touch_campaign: context?.utm_campaign,
}, {
onConflict: 'glue_user_id'
})
}
res.json({ success: true, glue_user_id })
} catch (error) {
console.error('Identify error:', error)
res.status(500).json({ error: error.message })
}
}
```
**File: `pages/api/glue/track.js`**
```javascript theme={null}
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_KEY
)
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
const { glue_user_id, session_id, event, properties, timestamp } = req.body
try {
// Insert event
await supabase
.from('events')
.insert({
event_id: `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
glue_user_id,
session_id,
event,
properties,
timestamp: new Date(timestamp)
})
// Update user stats
await supabase
.from('users')
.update({
last_seen: new Date().toISOString(),
total_events: supabase.raw('total_events + 1')
})
.eq('glue_user_id', glue_user_id)
res.json({ success: true })
} catch (error) {
console.error('Track error:', error)
res.status(500).json({ error: error.message })
}
}
```
#### 3. Configure DataGlue
```html theme={null}
```
### Pros & Cons
**Pros:**
* ✅ No server management
* ✅ Auto-scaling
* ✅ Generous free tiers (Vercel: 100GB bandwidth)
* ✅ Fast cold starts
* ✅ Easy to deploy
**Cons:**
* ❌ Requires coding
* ❌ Limited execution time (10-60 seconds)
* ❌ Cold starts can add latency
***
## Option 3: Custom Server (Node.js + Express)
**Best for:** Developers who want full control, high-volume apps
### Architecture
```mermaid theme={null}
graph TB
A[DataGlue] -->|HTTPS| B[Load Balancer]
B --> C[Express Server]
C --> D{Route}
D -->|POST /identify| E[Identify Handler]
D -->|POST /track| F[Track Handler]
D -->|POST /sync| G[Sync Handler]
E --> H[Business Logic]
F --> H
G --> H
H --> I[(PostgreSQL)]
H --> J[(Redis Cache)]
I --> K[users]
I --> L[events]
```
### Setup Steps
#### 1. Install Dependencies
```bash theme={null}
npm install express pg redis cors dotenv
```
#### 2. Create Server
**File: `server.js`**
```javascript theme={null}
const express = require('express')
const { Pool } = require('pg')
const cors = require('cors')
const app = express()
app.use(express.json())
app.use(cors())
// PostgreSQL connection
const pool = new Pool({
connectionString: process.env.DATABASE_URL
})
// POST /glue/identify
app.post('/glue/identify', async (req, res) => {
const { glue_user_id, email, traits, context } = req.body
if (!glue_user_id || !email) {
return res.status(400).json({ error: 'Missing required fields' })
}
try {
// Check for existing user with this email
const existing = await pool.query(
'SELECT * FROM users WHERE email = $1 AND glue_user_id != $2',
[email, glue_user_id]
)
if (existing.rows.length > 0) {
// Cross-device merge
const primaryUser = existing.rows[0]
// Add to linked_ids
await pool.query(
'UPDATE users SET linked_ids = array_append(linked_ids, $1) WHERE glue_user_id = $2',
[glue_user_id, primaryUser.glue_user_id]
)
// Merge events
await pool.query(
'UPDATE events SET glue_user_id = $1 WHERE glue_user_id = $2',
[primaryUser.glue_user_id, glue_user_id]
)
} else {
// Upsert user
await pool.query(`
INSERT INTO users (
glue_user_id, email, traits, identified, identified_at,
last_seen, last_touch_source, last_touch_medium
) VALUES ($1, $2, $3, true, NOW(), NOW(), $4, $5)
ON CONFLICT (glue_user_id) DO UPDATE SET
email = $2,
traits = $3,
identified = true,
identified_at = COALESCE(users.identified_at, NOW()),
last_seen = NOW(),
last_touch_source = $4,
last_touch_medium = $5
`, [
glue_user_id,
email,
JSON.stringify(traits),
context?.utm_source,
context?.utm_medium
])
}
res.json({ success: true, glue_user_id })
} catch (error) {
console.error('Identify error:', error)
res.status(500).json({ error: error.message })
}
})
// POST /glue/track
app.post('/glue/track', async (req, res) => {
const { glue_user_id, session_id, event, properties, timestamp } = req.body
try {
// Insert event
await pool.query(`
INSERT INTO events (event_id, glue_user_id, session_id, event, properties, timestamp)
VALUES ($1, $2, $3, $4, $5, $6)
`, [
`evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
glue_user_id,
session_id,
event,
JSON.stringify(properties),
new Date(timestamp)
])
// Update user
await pool.query(
'UPDATE users SET last_seen = NOW(), total_events = total_events + 1 WHERE glue_user_id = $1',
[glue_user_id]
)
res.json({ success: true })
} catch (error) {
console.error('Track error:', error)
res.status(500).json({ error: error.message })
}
})
// POST /glue/sync
app.post('/glue/sync', async (req, res) => {
const { glue_user_id, data } = req.body
try {
// Upsert user
await pool.query(`
INSERT INTO users (glue_user_id, email, traits, first_seen, last_seen)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (glue_user_id) DO UPDATE SET
email = COALESCE(users.email, $2),
traits = $3,
last_seen = NOW()
`, [
glue_user_id,
data.profile.email,
JSON.stringify(data.profile.traits),
data.profile.first_seen
])
// Bulk insert events
for (const event of data.events) {
await pool.query(`
INSERT INTO events (event_id, glue_user_id, session_id, event, properties, timestamp)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (event_id) DO NOTHING
`, [
event.event_id,
glue_user_id,
event.session_id,
event.event,
JSON.stringify(event.properties),
event.timestamp
])
}
res.json({ success: true, events_synced: data.events.length })
} catch (error) {
console.error('Sync error:', error)
res.status(500).json({ error: error.message })
}
})
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`DataGlue server running on port ${PORT}`)
})
```
#### 3. Deploy
```bash theme={null}
# Railway
railway up
# Or Render
render deploy
# Or Docker
docker build -t dataglue-server .
docker run -p 3000:3000 dataglue-server
```
### Pros & Cons
**Pros:**
* ✅ Full control over logic
* ✅ Can handle high volumes
* ✅ No cold starts
* ✅ Custom optimizations
* ✅ Direct database access
**Cons:**
* ❌ Requires server management
* ❌ Need to handle scaling
* ❌ More code to maintain
* ❌ Security considerations
***
## Cross-Device Identity Resolution
### The Challenge
```mermaid theme={null}
graph TB
A[iPhone Safari] -->|glue_user_id: ABC-123| B[localStorage]
C[Laptop Chrome] -->|glue_user_id: XYZ-789| D[localStorage]
E[Android Firefox] -->|glue_user_id: DEF-456| F[localStorage]
B --> G[Session 1, 2, 3]
D --> H[Session 4, 5]
F --> I[Session 6]
G --> J{Same Person?}
H --> J
I --> J
J -->|Email: john@example.com| K[SERVER MERGES]
K --> L[Primary ID: XYZ-789]
L --> M[Linked IDs: ABC-123, DEF-456]
```
### Solution: Email-Based Merging
```sql theme={null}
-- When user identifies on any device
-- Step 1: Check if email exists
SELECT * FROM users WHERE email = 'john@example.com';
-- Step 2a: If exists, merge devices
UPDATE users
SET linked_ids = array_append(linked_ids, 'NEW-DEVICE-ID')
WHERE email = 'john@example.com';
-- Step 2b: Unify events under primary ID
UPDATE events
SET glue_user_id = 'PRIMARY-ID'
WHERE glue_user_id = 'NEW-DEVICE-ID';
-- Step 3: Query complete journey
SELECT * FROM events
WHERE glue_user_id IN (
SELECT glue_user_id FROM users WHERE email = 'john@example.com'
UNION
SELECT unnest(linked_ids) FROM users WHERE email = 'john@example.com'
)
ORDER BY timestamp ASC;
```
***
## Analytics Queries
### User Journey
```sql theme={null}
-- Get complete user journey
WITH user_profile AS (
SELECT * FROM users WHERE email = 'user@example.com'
),
all_ids AS (
SELECT glue_user_id FROM user_profile
UNION
SELECT unnest(linked_ids) FROM user_profile
)
SELECT
e.timestamp,
e.event,
e.properties->>'url' as page,
e.session_id
FROM events e
WHERE e.glue_user_id IN (SELECT * FROM all_ids)
ORDER BY e.timestamp ASC;
```
### Attribution Report
```sql theme={null}
-- Users by first touch source
SELECT
first_touch_source,
COUNT(*) as users,
COUNT(*) FILTER (WHERE identified = true) as converted
FROM users
GROUP BY first_touch_source
ORDER BY users DESC;
```
### Conversion Funnel
```sql theme={null}
-- Funnel analysis
WITH funnel AS (
SELECT
COUNT(DISTINCT CASE WHEN event = 'page_view' AND properties->>'path' = '/landing' THEN glue_user_id END) as landed,
COUNT(DISTINCT CASE WHEN event = 'form_submit' THEN glue_user_id END) as submitted,
COUNT(DISTINCT CASE WHEN event = 'page_view' AND properties->>'path' = '/thank-you' THEN glue_user_id END) as converted
FROM events
)
SELECT
landed,
submitted,
converted,
ROUND(100.0 * submitted / landed, 2) as submit_rate,
ROUND(100.0 * converted / submitted, 2) as conversion_rate
FROM funnel;
```
***
## Comparison Table
| Feature | N8N + Supabase | Serverless | Custom Server |
| ------------------- | -------------- | ----------- | ------------- |
| **Setup Time** | 1 hour | 2-3 hours | 4-8 hours |
| **Coding Required** | None | Moderate | High |
| **Cost (monthly)** | $0-$45 | $0-$20 | $10-$100 |
| **Scalability** | Good | Excellent | Excellent |
| **Performance** | Good | Good | Best |
| **Debugging** | Visual | Logs | Full control |
| **Maintenance** | Low | Medium | High |
| **Best For** | Quick start | Modern apps | High volume |
***
## Recommended Approach
```mermaid theme={null}
graph TD
A[Start] --> B{What's your priority?}
B -->|Speed to market| C[N8N + Supabase]
B -->|Modern stack| D[Serverless Functions]
B -->|Full control| E[Custom Server]
C --> F[Perfect for: - Non-developers - MVP/Testing - Visual workflows]
D --> G[Perfect for: - Next.js/React apps - Moderate traffic - Developer-friendly]
E --> H[Perfect for: - High volume - Custom logic - Enterprise apps]
F --> I[Deploy in 1 hour]
G --> I[Deploy in 3 hours]
H --> I[Deploy in 1 day]
```
**Start simple, scale later:**
1. Begin with N8N + Supabase (fastest)
2. As you grow, migrate to Serverless Functions
3. At scale, move to Custom Server
4. Your data stays in Supabase/PostgreSQL throughout
***
## Next Steps
1. **Choose your approach** based on the comparison table
2. **Create database tables** in Supabase/PostgreSQL
3. **Set up your webhook/API endpoints**
4. **Configure DataGlue** with your endpoint URL
5. **Test with a form submission**
6. **Query your data** to see user journeys
7. **Build analytics dashboards** on top of the data
Need help? Check out:
* [Journey Tracking Guide](/docs/journey-tracking)
* [Form Injection Guide](/docs/form-injection)
* [Server Implementation Examples](/docs/server-implementation)