Skip to content

A comprehensive TypeScript library for capturing, storing, and appending UTM tracking parameters. Framework-agnostic core with optional React integration.

License

Notifications You must be signed in to change notification settings

jackmisner/utm-toolkit

Repository files navigation

@jackmisner/utm-toolkit

CI

A comprehensive TypeScript library for capturing, storing, and appending UTM tracking parameters. Framework-agnostic core with optional React integration.

Features

Inbound (Receiving UTM-tagged traffic)

  • Capture UTM parameters from URLs
  • Sanitize parameter values to prevent XSS and injection
  • PII filtering to detect and reject/redact email addresses, phone numbers, and other PII
  • Store in sessionStorage or localStorage (with optional TTL)
  • Attribution — first-touch, last-touch, or both
  • Form population — inject UTM data into HTML forms (vanilla JS + React)

Outbound (Creating UTM-tagged links)

  • Append UTM parameters to share URLs
  • Build UTM-tagged URLs with validation and warnings
  • Link decoration — auto-append UTM params to links on a page (vanilla JS + React)

General

  • Event callbacks — lifecycle hooks for capture, store, clear, append, and expiry events
  • Configurable key format (snake_case or camelCase)
  • Platform-specific share context parameters
  • Fragment mode support (add params to #hash instead of ?query)
  • URL validation and normalization
  • React hook and context provider
  • Debug utilities for troubleshooting
  • SSR-safe with graceful fallbacks
  • Zero dependencies (peer dependency on React is optional)

Installation

npm install @jackmisner/utm-toolkit

Quick Start

Basic Usage (Framework-Agnostic)

import {
  captureUtmParameters,
  storeUtmParameters,
  getStoredUtmParameters,
  appendUtmParameters,
} from '@jackmisner/utm-toolkit';

// Capture UTM params from URL
// URL: https://example.com?utm_source=linkedin&utm_campaign=spring2025
const params = captureUtmParameters();
// { utm_source: 'linkedin', utm_campaign: 'spring2025' }

// Store for the session
storeUtmParameters(params);

// Later, retrieve stored params
const stored = getStoredUtmParameters();

// Append to a share URL
const shareUrl = appendUtmParameters('https://example.com/share', stored);
// https://example.com/share?utm_source=linkedin&utm_campaign=spring2025

React Usage

import { useUtmTracking } from '@jackmisner/utm-toolkit/react';

function ShareButton() {
  const { appendToUrl, hasParams } = useUtmTracking();

  const handleShare = () => {
    const shareUrl = appendToUrl('https://example.com/results', 'linkedin');
    window.open(`https://linkedin.com/share?url=${encodeURIComponent(shareUrl)}`);
  };

  return <button onClick={handleShare}>Share on LinkedIn</button>;
}

React with Provider (Shared State)

import { UtmProvider, useUtmContext } from '@jackmisner/utm-toolkit/react';

// Wrap your app
function App() {
  return (
    <UtmProvider config={{ storageKey: 'myapp_utm' }}>
      <MyComponent />
    </UtmProvider>
  );
}

// Access UTM state anywhere
function MyComponent() {
  const { utmParameters, appendToUrl } = useUtmContext();
  // ...
}

API Reference

Core Functions

captureUtmParameters(url?, options?)

Extract UTM parameters from a URL.

// Capture from current page URL
const params = captureUtmParameters();

// Capture from specific URL
const params = captureUtmParameters('https://example.com?utm_source=test');

// With options
const params = captureUtmParameters(url, {
  keyFormat: 'camelCase', // 'snake_case' (default) or 'camelCase'
  allowedParameters: ['utm_source', 'utm_campaign'], // Filter to specific params
});

// With sanitization (strips HTML, control chars)
const params = captureUtmParameters(url, {
  sanitize: {
    enabled: true,
    stripHtml: true,        // Remove < > " ' ` (default: true)
    stripControlChars: true, // Remove control characters (default: true)
    maxLength: 200,          // Truncate values (default: 200)
  },
});

storeUtmParameters(params, options?)

Store UTM parameters in browser storage.

storeUtmParameters({ utm_source: 'linkedin', utm_campaign: 'sale' });

// With custom storage key
storeUtmParameters(params, { storageKey: 'myapp_utm' });

// Store in localStorage (persists across sessions)
storeUtmParameters(params, { storageType: 'local' });

// Store in localStorage with 1-hour TTL
storeUtmParameters(params, { storageType: 'local', ttl: 3600000 });

// Store in camelCase format
storeUtmParameters(params, { keyFormat: 'camelCase' });

getStoredUtmParameters(options?)

Retrieve stored UTM parameters. Returns null if data has expired (when TTL was set).

const params = getStoredUtmParameters();

// Read from localStorage
const params = getStoredUtmParameters({ storageType: 'local' });

// With options
const params = getStoredUtmParameters({
  storageKey: 'myapp_utm',
  keyFormat: 'camelCase', // Convert to camelCase on retrieval
});

appendUtmParameters(url, params, options?)

Append UTM parameters to a URL.

// Basic usage
const url = appendUtmParameters('https://example.com', { utm_source: 'test' });

// With options
const url = appendUtmParameters(url, params, {
  toFragment: true, // Add to #hash instead of ?query
  preserveExisting: true, // Don't replace existing UTM params
});

clearStoredUtmParameters(options?)

Clear stored UTM parameters.

clearStoredUtmParameters();
clearStoredUtmParameters({ storageKey: 'myapp_utm' });
clearStoredUtmParameters({ storageType: 'local' });
clearStoredUtmParameters({ onClear: () => console.log('Cleared!') });

// Legacy positional args still work
clearStoredUtmParameters('myapp_utm');
clearStoredUtmParameters('utm_parameters', 'local');

Key Conversion

import {
  toSnakeCase,
  toCamelCase,
  convertParams,
} from '@jackmisner/utm-toolkit';

// Convert single keys
toSnakeCase('utmSource'); // 'utm_source'
toCamelCase('utm_source'); // 'utmSource'

// Convert entire objects
convertParams({ utmSource: 'test' }, 'snake_case');
// { utm_source: 'test' }

URL Validation

import {
  validateUrl,
  normalizeUrl,
  validateAndNormalize,
} from '@jackmisner/utm-toolkit';

// Validate URL
const result = validateUrl('https://example.com');
// { valid: true }

const result = validateUrl('ftp://example.com');
// { valid: false, error: 'invalid_protocol', message: '...' }

// Normalize URL (add protocol if missing)
normalizeUrl('example.com'); // 'https://example.com'

// Combined
validateAndNormalize('example.com');
// { valid: true, normalizedUrl: 'https://example.com' }

Value Sanitization

Sanitize UTM parameter values to prevent XSS when rendering in HTML or constructing URLs. Sanitization is disabled by default and runs at capture time only.

import { captureUtmParameters, sanitizeValue, sanitizeParams } from '@jackmisner/utm-toolkit';

// Enable sanitization during capture
const params = captureUtmParameters('https://example.com?utm_source=<script>bad</script>', {
  sanitize: { enabled: true },
});
// { utm_source: 'scriptbad/script' }

// Use standalone sanitization functions
sanitizeValue('<b>bold</b>', {
  enabled: true,
  stripHtml: true,
  stripControlChars: true,
  maxLength: 200,
});
// 'bbold/b'

// With a custom pattern
const params = captureUtmParameters(url, {
  sanitize: {
    enabled: true,
    customPattern: /[!@#$%^&*]/g, // Strip additional characters
  },
});

PII Filtering

Detect and filter personally identifiable information (email addresses, phone numbers) from UTM parameter values. Prevents PII from leaking into analytics via misconfigured tracking links. Disabled by default.

import { captureUtmParameters } from '@jackmisner/utm-toolkit';

// Reject mode (default) — discard values containing PII
const params = captureUtmParameters('https://example.com?utm_source=john@example.com&utm_medium=cpc', {
  piiFiltering: { enabled: true },
});
// { utm_medium: 'cpc' } — utm_source was rejected

// Redact mode — replace PII values with [REDACTED]
const params = captureUtmParameters('https://example.com?utm_source=john@example.com&utm_medium=cpc', {
  piiFiltering: { enabled: true, mode: 'redact' },
});
// { utm_source: '[REDACTED]', utm_medium: 'cpc' }

// Strict allowlist — only accept values matching a pattern
const params = captureUtmParameters(url, {
  piiFiltering: {
    enabled: true,
    allowlistPattern: /^[a-z0-9_-]+$/, // Only lowercase alphanumeric, hyphens, underscores
  },
});

// Callback for logging PII detections
const params = captureUtmParameters(url, {
  piiFiltering: {
    enabled: true,
    onPiiDetected: (param, value, patternName) => {
      console.warn(`PII detected in ${param}: matched ${patternName}`);
    },
  },
});

Built-in PII patterns detect: email addresses, international phone numbers, UK phone numbers, and US phone numbers.

Event Callbacks

Hook into UTM lifecycle events for logging, analytics, or custom behavior.

import {
  captureUtmParameters,
  storeUtmParameters,
  getStoredUtmParameters,
  clearStoredUtmParameters,
  appendUtmParameters,
} from '@jackmisner/utm-toolkit';

// onCapture — fired after UTM params are captured from a URL
captureUtmParameters(url, {
  onCapture: (params) => console.log('Captured:', params),
});

// onStore — fired after params are written to storage
storeUtmParameters(params, {
  onStore: (params, meta) => console.log(`Stored (${meta.storageType}):`, params),
});

// onClear — fired when stored params are cleared
clearStoredUtmParameters({
  onClear: () => analytics.track('utm_params_cleared'),
});

// onAppend — fired after UTM params are appended to a URL
appendUtmParameters(url, params, {
  onAppend: (finalUrl, params) => console.log('Appended:', finalUrl),
});

// onExpire — fired when TTL-expired data is auto-cleaned
getStoredUtmParameters({
  storageType: 'local',
  onExpire: (storageKey) => console.log(`Expired: ${storageKey}`),
});

All callbacks are wrapped in try-catch — a throwing callback will never break the main pipeline.

First-Touch / Last-Touch Attribution

Track how users first discovered your site vs. their most recent visit.

import { storeWithAttribution, getAttributedParams } from '@jackmisner/utm-toolkit';

// Mode: 'last' (default) — stores to main key, same as storeUtmParameters
storeWithAttribution(params, {
  attribution: { mode: 'last' },
  storageKey: 'utm_parameters',
  storageType: 'session',
  keyFormat: 'snake_case',
});

// Mode: 'first' — write-once; only stores on the first visit
storeWithAttribution(params, {
  attribution: { mode: 'first' },
  storageKey: 'utm_parameters',
  storageType: 'session',
  keyFormat: 'snake_case',
});

// Mode: 'both' — stores first-touch (write-once) and last-touch (always updates)
storeWithAttribution(params, {
  attribution: { mode: 'both', firstTouchSuffix: '_first', lastTouchSuffix: '_last' },
  storageKey: 'utm_parameters',
  storageType: 'session',
  keyFormat: 'snake_case',
});

// Read attributed params
const first = getAttributedParams({ ...opts, touch: 'first' });
const last = getAttributedParams({ ...opts, touch: 'last' });

Attribution with React

const {
  utmParameters,       // Current params (based on attribution mode)
  firstTouchParams,    // First-touch params (null when mode is 'last')
  lastTouchParams,     // Last-touch params (null when mode is 'first')
} = useUtmTracking({
  config: { attribution: { mode: 'both' } },
});

UTM Link Builder

Build UTM-tagged URLs from structured input with validation and warnings.

import { buildUtmUrl, validateUtmValues } from '@jackmisner/utm-toolkit';

const result = buildUtmUrl({
  url: 'https://example.com',
  source: 'google',
  medium: 'cpc',
  campaign: 'spring2025',
});
// result.valid === true
// result.url === 'https://example.com?utm_source=google&utm_medium=cpc&utm_campaign=spring2025'
// result.errors === []
// result.warnings === []

// With options
const result = buildUtmUrl(
  { url: 'example.com', source: 'Google', campaign: 'Spring' },
  { normalize: true, lowercaseValues: true },
);
// Normalizes URL, lowercases all values, no uppercase warnings

// Validation errors
const result = buildUtmUrl({ url: 'https://example.com', source: 'goo&gle' });
// result.valid === false
// result.errors === ['source contains unsafe characters (& = ? #)']

// Standalone validation
const { errors, warnings } = validateUtmValues({ source: 'Google', medium: 'cpc' });
// errors === [], warnings === ['source contains uppercase characters']

Form Field Population

Inject stored UTM params into HTML form fields. Works with vanilla JS or React.

Vanilla JS

import { populateFormFields, createUtmHiddenFields } from '@jackmisner/utm-toolkit';

// Strategy: 'name' — find <input name="utm_source"> etc. and set values
populateFormFields({ strategy: 'name' });

// Strategy: 'data-attribute' — find <input data-utm="source"> etc.
populateFormFields({ strategy: 'data-attribute' });

// Strategy: 'auto-create' (default) — create hidden inputs in matching forms
populateFormFields({ selector: '#signup-form' });

// createUtmHiddenFields is a shortcut for auto-create strategy
createUtmHiddenFields({ selector: 'form.track-utm' });

React

import { UtmHiddenFields, useUtmFormData } from '@jackmisner/utm-toolkit/react';

// Component — renders hidden <input> elements inside your form
function ContactForm() {
  return (
    <form action="/submit">
      <input name="email" type="email" />
      <UtmHiddenFields prefix="tracking_" />
      <button type="submit">Submit</button>
    </form>
  );
}

// Hook — returns UTM data as a flat Record for form libraries
function MyForm() {
  const utmData = useUtmFormData();
  // { utm_source: 'google', utm_medium: 'cpc', ... }

  return (
    <form>
      {Object.entries(utmData).map(([key, value]) => (
        <input key={key} type="hidden" name={key} value={value} />
      ))}
    </form>
  );
}

Automatic Link Decoration

Auto-append UTM params to links on a page. Useful for internal navigation tracking.

Vanilla JS

import { decorateLinks, observeAndDecorateLinks } from '@jackmisner/utm-toolkit';

// Decorate all internal links
decorateLinks();

// With options
decorateLinks({
  selector: 'a.track',            // Custom CSS selector
  internalOnly: true,              // Only same-host links (default)
  includeHosts: ['partner.com'],   // Additional hosts to decorate
  excludeHosts: ['cdn.example.com'], // Hosts to skip
  skipExisting: true,              // Don't re-decorate (default)
  extraParams: { utm_campaign: 'nav' }, // Additional static params
  onAppend: (url, params) => console.log('Decorated:', url),
});

// For SPAs — watch for new links via MutationObserver
const cleanup = observeAndDecorateLinks({ internalOnly: false });
// Later: cleanup() to disconnect the observer

React

import { UtmLinkDecorator, useUtmLinkDecorator } from '@jackmisner/utm-toolkit/react';

// Component wrapper — decorates child links
function Navigation() {
  return (
    <UtmLinkDecorator internalOnly={true}>
      <nav>
        <a href="/products">Products</a>
        <a href="/pricing">Pricing</a>
      </nav>
    </UtmLinkDecorator>
  );
}

// Hook — returns a ref to scope decoration to a container
function MySection() {
  const ref = useUtmLinkDecorator({ internalOnly: false });
  return (
    <div ref={ref}>
      <a href="https://partner.com">Partner Link</a>
    </div>
  );
}

Persistent Storage

By default, UTM parameters are stored in sessionStorage (cleared when the tab closes). For longer-lived storage, use localStorage with an optional TTL.

import { storeUtmParameters, getStoredUtmParameters, createConfig } from '@jackmisner/utm-toolkit';

// Ephemeral storage (default) — cleared when tab closes
storeUtmParameters(params);

// Persistent storage — survives browser restarts
storeUtmParameters(params, { storageType: 'local' });

// Persistent storage with 24-hour TTL — auto-expires
storeUtmParameters(params, { storageType: 'local', ttl: 86400000 });

// Expired data returns null and is auto-cleaned from storage
const stored = getStoredUtmParameters({ storageType: 'local' });

// Use with React hook
const { utmParameters } = useUtmTracking({
  config: {
    storageType: 'local',
    ttl: 86400000, // 24 hours
  },
});

Configuration

import { createConfig } from '@jackmisner/utm-toolkit';

const config = createConfig({
  enabled: true,
  keyFormat: 'snake_case',
  storageKey: 'utm_parameters',
  storageType: 'session',
  captureOnMount: true,
  appendToShares: true,
  allowedParameters: ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id'],
  defaultParams: {},
  shareContextParams: {
    default: { utm_medium: 'social_share' },
    linkedin: { utm_content: 'linkedin_share' },
    copy: { utm_content: 'link_copy' },
  },
  excludeFromShares: ['utm_team_id'],
});

React Hook

import { useUtmTracking } from '@jackmisner/utm-toolkit/react';

function MyComponent() {
  const {
    utmParameters,       // Current captured params (or null)
    isEnabled,           // Whether tracking is enabled
    hasParams,           // Whether any params exist
    capture,             // Manually capture from URL
    clear,               // Clear stored params
    appendToUrl,         // Append params to a URL
    firstTouchParams,    // First-touch params (null when attribution mode is 'last')
    lastTouchParams,     // Last-touch params (null when attribution mode is 'first')
  } = useUtmTracking({
    config: {
      keyFormat: 'camelCase',
      attribution: { mode: 'both' },
      shareContextParams: {
        linkedin: { utm_content: 'linkedin' },
      },
      onCapture: (params) => analytics.track('utm_captured', params),
      onStore: (params, meta) => analytics.track('utm_stored', { ...params, touch: meta.touch }),
    },
  });

  // Generate share URL with platform-specific params
  const linkedInUrl = appendToUrl('https://example.com', 'linkedin');
}

Debug Utilities

import {
  debugUtmState,
  checkUtmTracking,
  installDebugHelpers,
} from '@jackmisner/utm-toolkit';

// Log current state to console
debugUtmState();

// Check for issues
const messages = checkUtmTracking();
messages.forEach(msg => console.log(msg));

// Install browser console helpers (add ?debug_utm=true to URL)
installDebugHelpers();
// Then use: window.utmDebug.state(), window.utmDebug.check()

Configuration Options

Option Type Default Description
enabled boolean true Enable/disable UTM tracking
keyFormat 'snake_case' | 'camelCase' 'snake_case' Key format for returned params
storageKey string 'utm_parameters' Browser storage key
storageType 'session' | 'local' 'session' Storage backend (sessionStorage or localStorage)
ttl number undefined Time-to-live in ms for stored params (localStorage only)
captureOnMount boolean true Auto-capture on React hook mount
appendToShares boolean true Append UTM params to share URLs
allowedParameters string[] Standard UTM params Params to capture
defaultParams object {} Fallback params when none captured
shareContextParams object {} Platform-specific params
excludeFromShares string[] [] Params to exclude from shares
sanitize SanitizeConfig { enabled: false } Value sanitization settings
piiFiltering PiiFilterConfig { enabled: false } PII detection and filtering
attribution AttributionConfig { mode: 'last' } First-touch/last-touch attribution
onCapture function undefined Callback after UTM params are captured
onStore function undefined Callback after UTM params are stored
onClear function undefined Callback when stored params are cleared
onAppend function undefined Callback after UTM params are appended to a URL
onExpire function undefined Callback when stored params expire (TTL)

TypeScript Types

import type {
  UtmParameters,
  UtmConfig,
  StorageType,
  KeyFormat,
  SanitizeConfig,
  PiiFilterConfig,
  PiiPattern,
  SharePlatform,
  AttributionMode,
  AttributionConfig,
  TouchType,
  ValidationResult,
  UseUtmTrackingReturn,
} from '@jackmisner/utm-toolkit';

Browser Support

  • All modern browsers (Chrome, Firefox, Safari, Edge)
  • Requires sessionStorage or localStorage support
  • SSR-safe (returns empty/null values on server)

Migration from Existing Projects

If you're migrating from a custom UTM implementation:

  1. Install the package
  2. Replace custom capture/storage/append functions with the library equivalents
  3. Update storage key if needed via storageKey option
  4. Test that existing UTM tracking still works

License

MIT

About

A comprehensive TypeScript library for capturing, storing, and appending UTM tracking parameters. Framework-agnostic core with optional React integration.

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •