CookieStore API: The Async Future of Cookie Management in JavaScript

CookieStore API: The Async Future of Cookie Management in JavaScript

For decades, web developers have depended on the old and limited document.cookie interface to handle cookies in the browser. This synchronous API, with its peculiar string syntax, has been a source of frustration and errors. But that’s changing with the arrival of CookieStore API, a modern and asynchronous interface that promises to revolutionize how we interact with cookies.

The Problem with document.cookie

Before diving into CookieStore, let’s recall the headaches document.cookie has caused us:

Archaic and Error-Prone Syntax

// The "traditional" way - complex and error-prone
document.cookie = "username=john_doe; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/; SameSite=Strict; Secure";

// Reading cookies requires manual parsing
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

Fundamental Limitations

  • Synchronous interface: Blocks the main thread
  • Only available in window context: Doesn’t work in Service Workers
  • Manual parsing: Requires custom code to read values
  • No native events: Impossible to listen for cookie changes
  • Error-prone: Complex string syntax

Introducing CookieStore API

CookieStore API is the modern answer to these problems, offering:

  • Async interface based on Promises
  • Available in Service Workers
  • Simple and clear methods
  • ✅Native change events**
  • Better error handling
  • Typed options

Availability and Compatibility

⚠️ Important: CookieStore API is available only in secure contexts (HTTPS) and its browser support is still limited. Currently compatible with:

  • Chrome/Edge 87+
  • Opera 73+
  • Safari (experimental)
  • Firefox (in development)

Main CookieStore Methods

1. cookieStore.set() - Set Cookies

// Simple syntax: name and value
await cookieStore.set('username', 'john_doe');

// Complete syntax with options
await cookieStore.set({
    name: 'sessionToken',
    value: 'abc123xyz789',
    expires: Date.now() + (24 * 60 * 60 * 1000), // 24 hours
    domain: 'example.com',
    path: '/',
    sameSite: 'strict',
    secure: true,
    partitioned: true // For third-party cookies
});

// With error handling
try {
    await cookieStore.set('preferences', JSON.stringify(userPrefs));
    console.log('Cookie set successfully');
} catch (error) {
    console.error('Error setting cookie:', error);
}
// Get cookie by name
const userCookie = await cookieStore.get('username');
console.log(userCookie);
// Output: { name: 'username', value: 'john_doe', domain: 'example.com', ... }

// Get with specific options
const sessionCookie = await cookieStore.get({
    name: 'sessionToken',
    path: '/admin'
});

// Check if exists
if (userCookie) {
    console.log(`User: ${userCookie.value}`);
} else {
    console.log('User not identified');
}

3. cookieStore.getAll() - Get Multiple Cookies

// Get all cookies
const allCookies = await cookieStore.getAll();
console.log(`Found ${allCookies.length} cookies`);

// Filter by options
const sessionCookies = await cookieStore.getAll({
    path: '/session'
});

// Process cookies
allCookies.forEach(cookie => {
    console.log(`${cookie.name}: ${cookie.value}`);
});

4. cookieStore.delete() - Delete Cookies

// Delete by name
await cookieStore.delete('username');

// Delete with specific options
await cookieStore.delete({
    name: 'sessionToken',
    path: '/admin'
});

// With confirmation
try {
    await cookieStore.delete('tempData');
    console.log('Cookie deleted successfully');
} catch (error) {
    console.error('Error deleting cookie:', error);
}

Change Events - The Revolution

One of the most powerful features of CookieStore API is the ability to listen for cookie changes:

// Listen for cookie changes
cookieStore.addEventListener('change', (event) => {
    console.log('Cookie change detected:', event);

    // Cookies that changed
    event.changed.forEach(cookie => {
        console.log(`Cookie modified: ${cookie.name} = ${cookie.value}`);
    });

    // Cookies that were deleted
    event.deleted.forEach(cookie => {
        console.log(`Cookie deleted: ${cookie.name}`);
    });
});

// Practical example: Sync user state
cookieStore.addEventListener('change', (event) => {
    const authChange = event.changed.find(c => c.name === 'authToken') ||
                      event.deleted.find(c => c.name === 'authToken');

    if (authChange) {
        // Update UI based on auth change
        updateAuthUI();
    }
});

Practical Use Cases

1. Advanced Session Management

class SessionManager {
    async createSession(userId, permissions) {
        const sessionData = {
            userId,
            permissions,
            createdAt: Date.now(),
            lastActive: Date.now()
        };

        await cookieStore.set({
            name: 'session',
            value: JSON.stringify(sessionData),
            expires: Date.now() + (8 * 60 * 60 * 1000), // 8 hours
            sameSite: 'strict',
            secure: true,
            httpOnly: false // For access from JS
        });
    }

    async getSession() {
        const sessionCookie = await cookieStore.get('session');
        return sessionCookie ? JSON.parse(sessionCookie.value) : null;
    }

    async destroySession() {
        await cookieStore.delete('session');
    }
}

2. User Preference System

class UserPreferences {
    constructor() {
        this.preferences = {};
        this.loadPreferences();
        this.setupChangeListener();
    }

    async loadPreferences() {
        const prefCookie = await cookieStore.get('userPrefs');
        this.preferences = prefCookie ?
            JSON.parse(prefCookie.value) :
            this.getDefaultPreferences();
    }

    async updatePreference(key, value) {
        this.preferences[key] = value;

        await cookieStore.set({
            name: 'userPrefs',
            value: JSON.stringify(this.preferences),
            expires: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 year
            sameSite: 'lax'
        });

        this.applyPreference(key, value);
    }

    getDefaultPreferences() {
        return {
            theme: 'auto',
            language: 'en',
            fontSize: 16,
            notifications: true
        };
    }
}
class ConsentManager {
    constructor() {
        this.consentTypes = ['necessary', 'analytics', 'marketing', 'personalization'];
        this.initializeConsent();
    }

    async initializeConsent() {
        const consent = await cookieStore.get('cookieConsent');

        if (!consent) {
            this.showConsentBanner();
        } else {
            this.applyConsent(JSON.parse(consent.value));
        }
    }

    async updateConsent(consentData) {
        await cookieStore.set({
            name: 'cookieConsent',
            value: JSON.stringify({
                ...consentData,
                timestamp: Date.now(),
                version: '1.0'
            }),
            expires: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 year
            sameSite: 'strict',
            secure: true
        });

        this.applyConsent(consentData);
        this.cleanupNonConsentedCookies(consentData);
    }
}

Use in Service Workers

One of the most significant advantages of CookieStore API is its availability in Service Workers:

// In a Service Worker
self.addEventListener('activate', async (event) => {
    // Clean obsolete cookies when activating SW
    const oldCookies = await cookieStore.getAll({
        name: 'temp_'
    });

    for (const cookie of oldCookies) {
        await cookieStore.delete(cookie.name);
    }
});

// Sync offline data
self.addEventListener('sync', async (event) => {
    if (event.tag === 'sync-user-data') {
        const userCookie = await cookieStore.get('userData');
        if (userCookie) {
            const userData = JSON.parse(userCookie.value);
            await syncUserDataToServer(userData);
        }
    }
});

Best Practices

1. Robust Error Handling

async function safeCookieOperation(operation) {
    try {
        return await operation();
    } catch (error) {
        if (error.name === 'NotAllowedError') {
            console.warn('Cookies blocked by user');
            return null;
        } else if (error.name === 'TypeError') {
            console.error('Type error in cookie operation:', error);
            return null;
        } else {
            console.error('Unexpected error:', error);
            throw error;
        }
    }
}

// Usage
const userData = await safeCookieOperation(() =>
    cookieStore.get('userData')
);

2. Data Validation

class SecureCookieManager {
    async setSecureCookie(name, value, options = {}) {
        // Validate input
        if (!this.isValidCookieName(name)) {
            throw new Error('Invalid cookie name');
        }

        if (!this.isValidCookieValue(value)) {
            throw new Error('Invalid cookie value');
        }

        // Secure configuration by default
        const secureOptions = {
            secure: true,
            sameSite: 'strict',
            ...options
        };

        await cookieStore.set(name, value, secureOptions);
    }

    isValidCookieName(name) {
        return /^[a-zA-Z0-9_-]+$/.test(name) && name.length <= 50;
    }

    isValidCookieValue(value) {
        return typeof value === 'string' && value.length <= 4096;
    }
}

Migration from document.cookie

Gradual Migration Strategy

class CookieAdapter {
    constructor() {
        this.supportsCookieStore = 'cookieStore' in window;
    }

    async get(name) {
        if (this.supportsCookieStore) {
            return await cookieStore.get(name);
        } else {
            // Fallback to document.cookie
            return this.getLegacyCookie(name);
        }
    }

    async set(name, value, options = {}) {
        if (this.supportsCookieStore) {
            await cookieStore.set(name, value, options);
        } else {
            this.setLegacyCookie(name, value, options);
        }
    }

    // Legacy methods for compatibility
    getLegacyCookie(name) {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) {
            const cookieValue = parts.pop().split(';').shift();
            return { name, value: cookieValue };
        }
        return null;
    }

    setLegacyCookie(name, value, options) {
        let cookieString = `${name}=${value}`;

        if (options.expires) {
            cookieString += `; expires=${new Date(options.expires).toUTCString()}`;
        }
        if (options.path) {
            cookieString += `; path=${options.path}`;
        }
        if (options.secure) {
            cookieString += `; secure`;
        }
        if (options.sameSite) {
            cookieString += `; samesite=${options.sameSite}`;
        }

        document.cookie = cookieString;
    }
}

Security Considerations

1. Secure Configuration by Default

const SECURE_COOKIE_DEFAULTS = {
    secure: true,        // Only HTTPS
    sameSite: 'strict',  // CSRF protection
    httpOnly: false,     // Access from JS (adjust as needed)
    partitioned: true    // For third-party cookies
};

async function setSecureCookie(name, value, customOptions = {}) {
    const options = { ...SECURE_COOKIE_DEFAULTS, ...customOptions };
    await cookieStore.set(name, value, options);
}

2. Validation and Sanitization

class SecureDataHandler {
    static sanitizeValue(value) {
        // Remove dangerous characters
        return value.replace(/[<>\"'&]/g, '');
    }

    static encryptSensitiveData(data, key) {
        // Implement encryption for sensitive data
        // Use Web Crypto API for real encryption
        return btoa(JSON.stringify(data));
    }

    static async setEncryptedCookie(name, sensitiveData, key) {
        const encrypted = this.encryptSensitiveData(sensitiveData, key);
        await cookieStore.set(name, encrypted, SECURE_COOKIE_DEFAULTS);
    }
}

Conclusion: Should You Adopt CookieStore API?

Clear advantages:

  • Modern and async interface
  • Better developer experience
  • Functionality in Service Workers
  • Native change events
  • Less boilerplate code

Considerations:

  • Limited browser support
  • Only available in HTTPS
  • Requires fallbacks for complete compatibility

Recommendation: Start experimenting with CookieStore API in new projects that can tolerate limited browser support, but maintain a robust fallback system until adoption is broader.

Cookie management is evolving, and CookieStore API represents the future of this fundamental functionality. It’s time to start preparing for this transition.


Useful links:

Tags: #JavaScript #CookieStore #WebAPI #Cookies #ModernWeb #ServiceWorkers #WebDevelopment