npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jackmisner/utm-toolkit

v0.2.0

Published

Capture, store, and append UTM tracking parameters

Readme

@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://[email protected]&utm_medium=cpc', {
  piiFiltering: { enabled: true },
});
// { utm_medium: 'cpc' } — utm_source was rejected

// Redact mode — replace PII values with [REDACTED]
const params = captureUtmParameters('https://[email protected]&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