@anishwij/meta-cookies
v0.1.2
Published
Lightweight server-side Meta (Facebook) Pixel cookie management for Next.js middleware
Maintainers
Readme
@anishwij/meta-cookies
A lightweight, server-side implementation for handling Meta (Facebook) Pixel cookies (_fbc and _fbp) in Next.js applications. Fully aligned with Meta's official CAPI Param Builder implementation, ensuring maximum compatibility with the Conversions API and Pixel events.
Features
Core Functionality
- 5-Segment Cookie Format: Implements Meta's official format with language/version token appendix
- Automatic Cookie Management: Sets/updates
_fbcwhenfbclidis detected in URL or referrer - Smart FBP Generation: Creates
_fbpcookies with unique identifiers following Meta's spec - Legacy Cookie Migration: Automatically upgrades 4-segment cookies to new 5-segment format
Advanced Features
- Enhanced Domain Resolution:
- IP address support (IPv4/IPv6 with bracketing)
- Multi-part TLD handling (e.g.,
.co.uk,.com.au) - Custom ETLD+1 resolver interface
- Browser Compatibility: Chrome detection for optimal SameSite attribute handling
- Smart Update Logic: Only updates timestamps when payloads change (not on every request)
- Version Tracking: Encodes package version in cookie appendix for debugging
- TypeScript-First: Full type safety with comprehensive interfaces
Installation
npm install @anishwij/meta-cookiesyarn add @anishwij/meta-cookiespnpm add @anishwij/meta-cookiesbun add @anishwij/meta-cookiesRequires Next.js >= 13 (for middleware support).
Usage
Basic Integration in Next.js Middleware
Import and use in your middleware.ts file:
import { NextRequest, NextResponse } from 'next/server';
import { addMetaCookies } from '@anishwij/meta-cookies';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
return addMetaCookies(request, response);
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};This will automatically handle Meta cookies on all routes (excluding static assets).
With Configuration
Pass options to customize behavior:
import { addMetaCookies } from '@anishwij/meta-cookies';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
return addMetaCookies(request, response, {
constants: { MAX_AGE: 3600 * 24 * 90 }, // 90 days in seconds
isSecure: true, // Force secure flag
etldPlus1Resolver: ['example.com', 'subdomain.example.com'], // Custom domain list
packageVersion: '0.1.0', // Optional: override version for appendix
});
}Standalone Helpers
For custom logic (e.g., in API routes or server components):
import { NextRequest } from 'next/server';
import { extractFbclid, formatFbc, getRootDomainFromHost } from '@anishwij/meta-cookies';
export async function GET(request: NextRequest) {
const fbclid = extractFbclid(request);
if (fbclid) {
const domain = getRootDomainFromHost(request.headers.get('host') || '');
const subdomainIndex = domain.split('.').length - 1;
const fbc = formatFbc(fbclid, subdomainIndex);
// Use in your response or Conversions API payload
}
// ...
}Extracting Cookies Server-Side
import { getMetaCookies } from '@anishwij/meta-cookies';
const { fbc, fbp } = getMetaCookies(request);
// Send to Meta Conversions APIPreparing Event Params
import { prepareMetaEventParams } from '@anishwij/meta-cookies';
const params = prepareMetaEventParams(request);
// Use in fetch to Meta API: { ...eventData, ...params }Deleting Cookies
import { deleteMetaCookies } from '@anishwij/meta-cookies';
deleteMetaCookies(response);
// e.g., in a logout routeRefreshing Cookies
import { refreshMetaCookies } from '@anishwij/meta-cookies';
refreshMetaCookies(request, response);
// Regenerates if invalid or missingAPI Reference
Note: All modules are re-exported from the package root (e.g., import { ParsedCookie } from '@anishwij/meta-cookies').
addMetaCookies(request: NextRequest, response: NextResponse, options?: MetaCookiesOptions): NextResponse
The main function to add/update Meta cookies. Mutates and returns the response.
- Parameters:
request: The incoming Next.js request.response: The Next.js response object (e.g., fromNextResponse.next()).options(optional): Customization options (see below).
MetaCookiesOptions
interface MetaCookiesOptions {
constants?: Partial<MetaCookieConstants>; // Override defaults like cookie names or max age
isSecure?: boolean; // Override protocol-based secure flag
etldPlus1Resolver?: ETLDPlus1Resolver | string[]; // Advanced domain resolution
packageVersion?: string; // Version for appendix encoding
}MetaCookieConstants (Defaults)
const META_COOKIE_DEFAULTS = {
FBC_NAME: '_fbc',
FBP_NAME: '_fbp',
MAX_AGE: 7776000, // 90 days in seconds
PREFIX: 'fb',
FBCLID_MAX_LENGTH: 500, // Max length for fbclid param
} as const;
const COOKIE_FORMAT = {
MIN_SEGMENTS: 4, // Legacy format
MAX_SEGMENTS: 5, // New format with appendix
DEFAULT_FORMAT: 0x01,
LANGUAGE_TOKEN_INDEX: 0x04, // Node.js
APPENDIX_LENGTH_V1: 2, // Legacy 2-char tokens
APPENDIX_LENGTH_V2: 8, // New 8-char appendix
} as const;Exported Helpers
Cookie Management
parseCookie(value: string, prefix?: string): ParsedCookie- Validates and parses a cookie value (supports 4-5 segments, configurable prefix).extractFbclid(request: NextRequest, maxLength?: number): string | null- Getsfbclidfrom URL or referrer.formatFbc(fbclid: string, subdomainIndex: number, prefix?: string, version?: string): string- Formats_fbcvalue with appendix.formatFbp(subdomainIndex: number, prefix?: string, version?: string): string- Formats_fbpvalue with appendix.updateCookieWithLanguageToken(cookieValue: string, version?: string): string- Adds appendix to 4-segment cookies.preprocessCookie(value: string | null | undefined, subdomainIndex: number, version?: string, prefix?: string): CookieData | null- Processes and validates existing cookies.getMetaCookies(request: NextRequest, constants?: MetaCookieConstants): { fbc: string | null; fbp: string | null }- Extracts and validates Meta cookies.prepareMetaEventParams(request: NextRequest, constants?: MetaCookieConstants): { fbc?: string; fbp?: string }- Prepares params for Meta API events.deleteMetaCookies(response: NextResponse, constants?: MetaCookieConstants): NextResponse- Deletes Meta cookies.refreshMetaCookies(request: NextRequest, response: NextResponse, options?: MetaCookiesOptions): NextResponse- Refreshes/repairs invalid or missing cookies.
Domain Resolution
getRootDomainFromHost(host: string): string- Enhanced root domain extraction with IP and multi-TLD support.getRootDomainFromHostEnhanced(host: string): string- Advanced version with extended TLD list.getSubdomainIndex(domain: string): number- Calculates the subdomain index.computeETLDPlus1ForHost(host: string, resolver?: ETLDPlus1Resolver | string[]): { etldPlus1: string; subdomainIndex: number }- Advanced ETLD+1 computation.DomainListResolver- Class implementing ETLDPlus1Resolver interface for custom domain lists.getSimpleETLDPlus1(hostname: string): string- Basic ETLD+1 calculation.
Utilities
isIPAddress(value: string): boolean- Detects IPv4 or IPv6 addresses.isIPv4Address(value: string): boolean- Validates IPv4 format.isIPv6Address(value: string): boolean- Validates IPv6 format.maybeBracketIPv6(value: string): string- Adds brackets to IPv6 addresses for cookies.extractHostFromHttpHost(value: string): string | null- Extracts hostname from HTTP host header.isDigit(str: string): boolean- Checks if string is all digits.getAppendixInfo(isNew: boolean, version?: string): string- Generates version appendix for cookies.
Browser Detection
detectBrowser(userAgent?: string | null): BrowserInfo- Analyzes user agent for browser detection.getSameSiteAttribute(userAgent?: string | null): 'lax' | 'none' | undefined- Determines appropriate SameSite attribute.
Types
interface ParsedCookie {
valid: boolean
parts: string[]
fbclid?: string
subdomainIndex?: number
timestamp?: number
languageToken?: string
prefix?: string // Cookie prefix (e.g., 'fb')
}
interface CookieData {
creationTime: number
payload: string
subdomainIndex: number
languageToken?: string
}
interface BrowserInfo {
isChrome: boolean
isEdge: boolean
isOpera: boolean
isCriOS: boolean
shouldUseSameSiteLax: boolean
}
interface ETLDPlus1Resolver {
resolveETLDPlus1(hostname: string): string
}See src/types.ts for complete type definitions.
Cookie Format
Meta cookies follow a specific format:
fb.{subdomain_index}.{timestamp}.{payload}.{appendix}- fb: Fixed prefix
- subdomain_index: Number based on domain depth (e.g., 1 for
.example.com) - timestamp: Unix timestamp in milliseconds
- payload: The fbclid value for
_fbc, random ID for_fbp - appendix: 8-character base64url string encoding version and metadata
Example cookies:
_fbc=fb.1.1704067200000.IwAR1a2b3c.AQQBAAEA
_fbp=fb.1.1704067200000.1234567890.AQQAAAEAExamples
Custom Domain Logic
For advanced TLD handling (e.g., integrating a public suffix library):
import { addMetaCookies } from '@anishwij/meta-cookies';
import psl from 'psl'; // Optional external lib
const customResolver: ETLDPlus1Resolver = {
resolveETLDPlus1: (hostname: string) => {
const parsed = psl.parse(hostname);
return parsed.domain || hostname;
}
};
addMetaCookies(request, response, { etldPlus1Resolver: customResolver });Using Domain List Resolver
For known domains without external dependencies:
import { addMetaCookies } from '@anishwij/meta-cookies';
const knownDomains = ['example.com', 'app.example.com', 'staging.example.com'];
export function middleware(request: NextRequest) {
const response = NextResponse.next();
return addMetaCookies(request, response, {
etldPlus1Resolver: knownDomains // Uses built-in DomainListResolver
});
}IP Address Support
The package automatically handles IP addresses:
// IPv4: Sets cookies on the IP directly
// Host: 192.168.1.1 → Cookie domain: 192.168.1.1
// IPv6: Properly brackets for cookie domain
// Host: ::1 → Cookie domain: [::1]Custom Cookie Prefix
For specialized use cases, you can use a custom prefix instead of 'fb':
import { parseCookie, formatFbc } from '@anishwij/meta-cookies';
// Parse cookie with custom prefix
const parsed = parseCookie('custom.1.1704067200000.abc123.AQQBAAEA', 'custom');
// Format cookies with custom prefix
const fbc = formatFbc('abc123', 1, 'custom');
// Use with middleware
addMetaCookies(request, response, {
constants: { PREFIX: 'custom' }
});Testing in Development
Run your Next.js app locally. Check cookies in browser dev tools after visiting a page with ?fbclid=example in the URL. The cookies will now include the appendix segment.
Gotchas & Best Practices
- Cookie Format: The package now uses Meta's official 5-segment format with appendix. Legacy cookies are auto-migrated.
- Production vs. Dev: Secure flag is auto-detected via protocol (
https:). Override withisSecureif needed. - Domain Sharing: Cookies are set on the root domain (e.g.,
.example.com) for subdomain access. Test on custom domains. - IP Address Handling: IPv6 addresses are automatically bracketed. IPv4 addresses work as-is.
- Browser Compatibility: Chrome gets
SameSite=Lax, other browsers get no SameSite attribute for maximum compatibility. - Update Logic: Timestamps only update when payloads change, reducing unnecessary cookie writes.
- Meta Compatibility: Fully aligned with Meta's CAPI Param Builder for perfect Conversions API integration.
- Ad Blockers: Server-side setting helps bypass blockers, but client-side Pixel may still be needed for full functionality.
- Privacy: Ensure compliance with GDPR/CCPA; these cookies track user behavior.
Acknowledgments
This package is a TypeScript implementation fully aligned with Meta's official CAPI Param Builder, implementing the exact cookie format and validation logic from Meta's reference implementation. It follows the specifications in Meta's Conversions API documentation including the 5-segment format with appendix tokens.
This is an independent implementation designed specifically for Next.js middleware, providing a clean, type-safe API while maintaining complete compatibility with Meta's standards. Not affiliated with or endorsed by Meta Platforms, Inc.
License
MIT License. See LICENSE for details.
