Skip to main content
Advanced SDK features and patterns for identity resolution, performance optimization, and complex tracking scenarios.

What It Covers

Advanced Topics:
  • Identity resolution and linking
  • Event batching and queuing
  • Offline support
  • Performance optimization
  • Error handling and retries
  • Plugin architecture
  • Custom integrations

Identity Resolution

Understanding Identity

DATALYR uses multiple identifiers to track users across sessions and devices: Identity Fields:
  • anonymousId: Persistent anonymous identifier (set by SDK)
  • visitorId: Browser/device visitor ID (Web/Mobile SDKs)
  • userId: Your application’s user ID (after login)
  • distinctId: Resolved ID (userId if available, else anonymousId)

Anonymous Tracking

Web SDK:
// Automatically creates anonymousId on first visit
datalyr.init({ workspaceId: 'abc123' });

// Get anonymous ID
const anonId = datalyr.getAnonymousId();
// Returns: "anon_abc123xyz"
Server SDK:
// SDK creates persistent anonymousId
const datalyr = new Datalyr('dk_your_api_key');

// Track with anonymous ID
await datalyr.track({
  anonymousId: datalyr.getAnonymousId(),
  event: 'page_view'
});
Mobile SDKs:
// iOS: Automatically creates anonymousId
let anonId = DatalyrSDK.shared.getAnonymousId()

// React Native
const anonId = Datalyr.getAnonymousId();

Linking Anonymous to Known Users

Scenario: User visits site anonymously, then signs up. Step 1: Anonymous Tracking (Web SDK)
// User visits site - Web SDK tracks anonymously
datalyr.track('product_view', {
  product_id: 'prod_123'
});
// anonymousId: "anon_abc123"
Step 2: User Signs Up
// User creates account
datalyr.identify('user_456', {
  email: '[email protected]',
  name: 'John Doe'
});
// Links anonymousId "anon_abc123" to userId "user_456"
Result: All previous anonymous activity now attributed to user_456.

Cross-Platform Identity

Web to Server:
// Client-side (Web SDK)
const anonymousId = datalyr.getAnonymousId();

// Send to server on signup
fetch('/api/signup', {
  method: 'POST',
  body: JSON.stringify({
    email: '[email protected]',
    anonymousId: anonymousId
  })
});

// Server-side (Server SDK)
app.post('/api/signup', async (req, res) => {
  const userId = await createUser(req.body.email);

  // Link anonymous to known user
  await datalyr.track({
    userId: userId,
    anonymousId: req.body.anonymousId,
    event: 'signup',
    properties: {
      email: req.body.email
    }
  });
});
Mobile to Server:
// Mobile app (React Native)
const anonymousId = Datalyr.getAnonymousId();

// Send to backend
const response = await fetch('/api/purchase', {
  method: 'POST',
  body: JSON.stringify({
    userId: currentUser.id,
    anonymousId: anonymousId,
    orderId: order.id
  })
});

// Server processes webhook
await datalyr.track({
  userId: req.body.userId,
  anonymousId: req.body.anonymousId,
  event: 'purchase',
  properties: {
    order_id: req.body.orderId
  }
});

Aliasing

Use Case: User has multiple IDs (email, phone, external ID).
// Web SDK: Connect new ID to existing
datalyr.alias('user_new_id', 'user_old_id');

// Server SDK
await datalyr.track({
  event: 'alias',
  userId: 'user_new_id',
  anonymousId: 'user_old_id'
});
Result: Both IDs resolve to same canonical user.

Event Batching

How Batching Works

SDKs batch events for efficiency instead of sending individually. Default Behavior:
  • Events queued in memory
  • Batch sent when flushAt size reached
  • Or when flushInterval elapsed
  • Automatically flushed on page unload/app background

Configuration

Web SDK:
datalyr.init({
  workspaceId: 'abc123',
  batchSize: 10,           // Events per batch
  flushInterval: 5000,     // Flush every 5 seconds
  flushAt: 10              // Auto-flush at 10 events
});
Server SDK:
const datalyr = new Datalyr({
  apiKey: 'dk_your_api_key',
  flushAt: 20,             // Larger batches for server
  flushInterval: 10000
});
Mobile SDKs:
await Datalyr.initialize({
  apiKey: 'dk_your_api_key',
  batchSize: 10,
  flushInterval: 30000
});

Manual Flushing

Force Immediate Send:
// Web SDK
await datalyr.flush();

// Server SDK
await datalyr.flush();

// Mobile SDKs
await Datalyr.flush();
Use Cases:
  • Before page navigation
  • Before app closes
  • After critical events
  • Before serverless function exits

Batch Size Optimization

Small Batches (1-5 events):
  • Real-time tracking
  • Low traffic sites
  • Critical events
Medium Batches (10-20 events):
  • Default for most sites
  • Balanced latency/performance
Large Batches (50-100 events):
  • High traffic applications
  • Bulk imports
  • Non-time-sensitive tracking

Offline Support

How Offline Queue Works

SDKs queue events when offline and send when connection restored. Features:
  • Automatic network detection
  • Persistent queue (survives app restart)
  • Automatic retry with backoff
  • Max queue size limit

Configuration

Web SDK:
datalyr.init({
  workspaceId: 'abc123',
  maxOfflineQueueSize: 100,  // Max queued events
  maxRetries: 5,              // Retry attempts
  retryDelay: 1000            // Initial retry delay (ms)
});
Server SDK:
const datalyr = new Datalyr({
  apiKey: 'dk_your_api_key',
  maxQueueSize: 1000,
  retryLimit: 3
});
Mobile SDKs:
await Datalyr.initialize({
  apiKey: 'dk_your_api_key',
  maxQueueSize: 100,
  maxRetries: 3
});

Handling Offline State

Check Network Status:
// Web SDK
const status = datalyr.getNetworkStatus();
console.log('Online:', status.online);
console.log('Pending events:', status.queueSize);

// Mobile SDKs
const status = Datalyr.getStatus();
console.log('Queue size:', status.queueStats.queueSize);
Queue Overflow: When queue exceeds maxQueueSize, oldest events dropped first.

Performance Optimization

Lazy Loading (Web SDK)

Defer Script Loading:
<script defer src="https://track.datalyr.com/dl.js"
  data-workspace-id="abc123">
</script>
Uses defer attribute to load script without blocking page render.

Reduce Event Volume

Track Selectively:
// Bad: Track every mousemove
document.addEventListener('mousemove', () => {
  datalyr.track('mouse_move');  // Thousands of events
});

// Good: Track meaningful interactions
button.addEventListener('click', () => {
  datalyr.track('button_click');  // Few events
});

Debounce Frequent Events

import { debounce } from 'lodash';

const trackScroll = debounce(() => {
  datalyr.track('scroll', {
    depth: window.scrollY
  });
}, 1000);

window.addEventListener('scroll', trackScroll);

Use Super Properties

Instead of Repeating Properties:
// Bad
datalyr.track('page_view', { version: '2.0', env: 'prod' });
datalyr.track('button_click', { version: '2.0', env: 'prod' });
datalyr.track('form_submit', { version: '2.0', env: 'prod' });

// Good
datalyr.setSuperProperties({
  version: '2.0',
  env: 'prod'
});

datalyr.track('page_view');
datalyr.track('button_click');
datalyr.track('form_submit');

Increase Batch Size

// For high-volume tracking
datalyr.init({
  workspaceId: 'abc123',
  flushAt: 50,           // Larger batches
  flushInterval: 30000   // Less frequent flushes
});

Error Handling

Automatic Retries

All SDKs retry failed requests automatically. Retry Logic:
  • Exponential backoff (1s, 2s, 4s, 8s…)
  • Max retry attempts configurable
  • Client errors (4xx) not retried
  • Server errors (5xx) retried
Configuration:
datalyr.init({
  workspaceId: 'abc123',
  maxRetries: 5,
  retryDelay: 1000
});

Error Handling

Web SDK:
try {
  await datalyr.track('event_name');
} catch (error) {
  console.error('Tracking failed:', error);
}

// View recent errors
const errors = datalyr.getErrors();
Server SDK:
try {
  await datalyr.track({
    userId: 'user_123',
    event: 'purchase'
  });
} catch (error) {
  console.error('Tracking failed:', error.message);
  // Handle error (log, alert, etc.)
}
Mobile SDKs:
do {
  try await DatalyrSDK.shared.track("event_name")
} catch {
  print("Tracking error: \(error)")
}

Graceful Degradation

SDKs designed to fail silently without breaking your application.
// Even if SDK fails, app continues
datalyr.track('button_click');
handleButtonClick();  // Always executes

Plugin Architecture (Web SDK)

Custom Plugins

Create Plugin:
const myPlugin = {
  name: 'MyPlugin',

  initialize: (datalyr) => {
    console.log('Plugin initialized');
  },

  track: (eventName, properties) => {
    console.log('Event tracked:', eventName);
  },

  identify: (userId, traits) => {
    console.log('User identified:', userId);
  },

  page: (properties) => {
    console.log('Page viewed:', properties.url);
  }
};

// Use plugin
datalyr.init({
  workspaceId: 'abc123',
  plugins: [myPlugin]
});

Plugin Use Cases

Google Analytics Integration:
const googleAnalyticsPlugin = {
  name: 'GoogleAnalytics',

  initialize: (datalyr) => {
    // Initialize GA
    gtag('config', 'GA_MEASUREMENT_ID');
  },

  track: (eventName, properties) => {
    // Send to GA
    gtag('event', eventName, properties);
  }
};
Console Logger:
const consoleLoggerPlugin = {
  name: 'ConsoleLogger',

  track: (eventName, properties) => {
    console.log(`[Event] ${eventName}`, properties);
  },

  identify: (userId, traits) => {
    console.log(`[Identify] ${userId}`, traits);
  }
};
Custom Destination:
const customDestinationPlugin = {
  name: 'CustomDestination',

  track: async (eventName, properties) => {
    await fetch('https://my-api.com/track', {
      method: 'POST',
      body: JSON.stringify({ eventName, properties })
    });
  }
};

Advanced Patterns

Conditional Tracking

// Only track in production
if (process.env.NODE_ENV === 'production') {
  datalyr.track('event_name');
}

// Track for specific users
if (user.plan === 'enterprise') {
  datalyr.track('feature_usage');
}

Event Validation

function trackValidated(eventName, properties) {
  // Validate event name
  if (!/^[a-z_]+$/.test(eventName)) {
    console.error('Invalid event name:', eventName);
    return;
  }

  // Validate properties
  if (properties && typeof properties !== 'object') {
    console.error('Properties must be object');
    return;
  }

  datalyr.track(eventName, properties);
}

Rate Limiting

import { throttle } from 'lodash';

const trackThrottled = throttle((eventName, properties) => {
  datalyr.track(eventName, properties);
}, 1000);

// Max 1 event per second
trackThrottled('rapid_event');

Context Enrichment

// Add context to all events
datalyr.setSuperProperties({
  app_version: '2.1.0',
  environment: 'production',
  user_role: currentUser.role,
  experiment_variant: getExperimentVariant()
});

Testing SDKs

Debug Mode

Enable Debug Logs:
datalyr.init({
  workspaceId: 'abc123',
  debug: true
});
Console Output:
[Datalyr] SDK initialized
[Datalyr] Event tracked: button_click
[Datalyr] Flushing 5 events
[Datalyr] Events sent successfully

Testing Environment

Use Test Workspace:
const workspaceId = process.env.NODE_ENV === 'production'
  ? 'prod_workspace_id'
  : 'test_workspace_id';

datalyr.init({ workspaceId });

Mock SDK for Tests

// __mocks__/@datalyr/web-sdk.js
export default {
  init: jest.fn(),
  track: jest.fn(),
  identify: jest.fn(),
  flush: jest.fn()
};

// test.js
import datalyr from '@datalyr/web-sdk';

test('tracks button click', () => {
  handleClick();
  expect(datalyr.track).toHaveBeenCalledWith('button_click');
});

Best Practices

Initialize Once: Create single SDK instance and reuse throughout application. Flush Before Exit: Call flush() before page navigation, app close, or function termination. Use Super Properties: Set common properties once instead of repeating in every event. Batch Appropriately: Balance real-time needs with performance (larger batches = better performance). Handle Errors: Wrap tracking in try/catch for critical paths. Respect Privacy: Enable Do Not Track, respect user consent, anonymize sensitive data. Test Thoroughly: Use debug mode and test workspace during development. Monitor Queue Size: Check queue size in high-traffic scenarios to prevent memory issues.

Next Steps