UTA DevHub

User Preferences Headers

Implementation guide for user preference headers across API requests

User Preferences Headers

Overview

This guide details the implementation approach for user preferences headers within our API header customization architecture. User preference headers allow backend services to deliver personalized experiences based on user settings without requiring repeated preference lookups. These headers enable content localization, accessibility optimizations, theme-based rendering, and other user-specific customizations.

Quick Start

If you need to quickly implement user preference headers in your application, follow these steps:

  1. Define preference header constants:

    // core/shared/constants/headers.ts
    export const API_HEADERS = {
      // Other headers...
      LOCALE: 'X-LOCALE',
      THEME: 'X-THEME',
      TIMEZONE: 'X-TIMEZONE',
      ACCESSIBILITY: 'X-ACCESSIBILITY',
      CONTENT_DENSITY: 'X-CONTENT-DENSITY',
      UNITS: 'X-UNITS',
    };
  2. Create a simple preferences store:

    // core/domains/preferences/store.ts
    import { create } from 'zustand';
    import { persist } from 'zustand/middleware';
    import AsyncStorage from '@react-native-async-storage/async-storage';
    import * as Localization from 'expo-localization';
     
    export const usePreferencesStore = create(
      persist(
        (set) => ({
          locale: Localization.locale,
          theme: 'system',
          timezone: Localization.timezone,
          sharePreferencesWithApi: true,
          setLocale: (locale) => set({ locale }),
          setTheme: (theme) => set({ theme }),
          setTimezone: (timezone) => set({ timezone }),
          setSharePreferencesWithApi: (share) => set({ sharePreferencesWithApi }),
        }),
        { name: 'user-preferences', getStorage: () => AsyncStorage }
      )
    );
  3. Implement a preferences header provider:

    // core/shared/api/headers/providers/userPreferencesHeaderProvider.ts
    import { HeaderProvider } from '../types';
    import { API_HEADERS } from '@/core/shared/constants/headers';
    import { usePreferencesStore } from '@/core/domains/preferences/store';
     
    export class UserPreferencesHeaderProvider implements HeaderProvider {
      id = 'user-preferences';
      priority = 40; // Lower priority than device or session headers
     
      async getHeaders() {
        const { locale, theme, timezone, sharePreferencesWithApi } = 
          usePreferencesStore.getState();
     
        // Honor user's preference sharing choice
        if (!sharePreferencesWithApi) {
          return {};
        }
     
        return {
          [API_HEADERS.LOCALE]: locale,
          [API_HEADERS.THEME]: theme !== 'system' ? theme : undefined,
          [API_HEADERS.TIMEZONE]: timezone,
        };
      }
    }
  4. Register the provider with your API client:

    // core/shared/api/client.ts
    import { UserPreferencesHeaderProvider } from './headers/providers/userPreferencesHeaderProvider';
     
    // Register with your API client
    apiClient.registerHeaderProvider(new UserPreferencesHeaderProvider());

For more advanced implementation details and best practices, continue reading the sections below.

Purpose & Scope

This implementation guide covers:

  • Creating a comprehensive user preferences header provider
  • Managing preference state with Zustand stores
  • Persisting preferences across app sessions
  • Allowing users to control preference sharing
  • Testing preference header implementations
  • Security and privacy considerations

This document extends the concepts established in the Header Customization Architecture guide, focusing specifically on implementing user preference headers.

Prerequisites

To effectively implement user preference headers, you should be familiar with:

Key User Preference Headers

The following preference headers provide important context about user settings:

HeaderDescriptionPurposeExample Value
X-LOCALEUser's preferred language/localeContent localization"en-US" or "fr-FR"
X-THEMEUser's preferred themeTheme-based rendering"light" or "dark"
X-TIMEZONEUser's preferred timezoneTime-sensitive content"America/New_York"
X-ACCESSIBILITYAccessibility requirementsA11y optimizations"screen-reader" or "high-contrast"
X-CONTENT-DENSITYUI density preferenceLayout optimization"compact" or "comfortable"
X-UNITSMeasurement unit preferenceUnit conversion"metric" or "imperial"

Implementation

1. User Preferences Store

First, implement a Zustand store to manage user preferences:

// core/domains/preferences/store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Localization from 'expo-localization';
import { I18n } from 'i18n-js';
 
// Define supported preference types
export type ThemeType = 'light' | 'dark' | 'system';
export type AccessibilityType = 'default' | 'screen-reader' | 'high-contrast' | 'reduced-motion';
export type ContentDensity = 'default' | 'compact' | 'comfortable';
export type UnitSystem = 'metric' | 'imperial' | 'auto';
 
export interface UserPreferences {
  // User preference values
  locale: string;
  theme: ThemeType;
  timezone: string;
  accessibility: AccessibilityType;
  contentDensity: ContentDensity;
  unitSystem: UnitSystem;
  
  // Control flags
  sharePreferencesWithApi: boolean;
  
  // Actions
  setLocale: (locale: string) => void;
  setTheme: (theme: ThemeType) => void;
  setTimezone: (timezone: string) => void;
  setAccessibility: (type: AccessibilityType) => void;
  setContentDensity: (density: ContentDensity) => void;
  setUnitSystem: (system: UnitSystem) => void;
  setSharePreferencesWithApi: (share: boolean) => void;
  resetToDefaults: () => void;
}
 
// Default preferences
const getDefaultPreferences = () => ({
  locale: Localization.locale,
  theme: 'system' as ThemeType,
  timezone: Localization.timezone,
  accessibility: 'default' as AccessibilityType,
  contentDensity: 'default' as ContentDensity,
  unitSystem: 'auto' as UnitSystem,
  sharePreferencesWithApi: true,
});
 
export const usePreferencesStore = create<UserPreferences>()(
  persist(
    (set) => ({
      ...getDefaultPreferences(),
      
      // Setters for each preference
      setLocale: (locale) => set({ locale }),
      setTheme: (theme) => set({ theme }),
      setTimezone: (timezone) => set({ timezone }),
      setAccessibility: (accessibility) => set({ accessibility }),
      setContentDensity: (contentDensity) => set({ contentDensity }),
      setUnitSystem: (unitSystem) => set({ unitSystem }),
      setSharePreferencesWithApi: (sharePreferencesWithApi) => set({ sharePreferencesWithApi }),
      
      // Reset all preferences to default values
      resetToDefaults: () => set(getDefaultPreferences()),
    }),
    {
      name: 'user-preferences-storage',
      storage: {
        getItem: async (name) => {
          const value = await AsyncStorage.getItem(name);
          return value ?? null;
        },
        setItem: async (name, value) => {
          await AsyncStorage.setItem(name, value);
        },
        removeItem: async (name) => {
          await AsyncStorage.removeItem(name);
        },
      },
    }
  )
);

2. User Preferences Header Provider

Next, implement the header provider that retrieves values from the preferences store:

// core/shared/api/headers/providers/userPreferencesHeaderProvider.ts
import { HeaderProvider } from '../types';
import { API_HEADERS } from '@/core/shared/constants/headers';
import { usePreferencesStore } from '@/core/domains/preferences/store';
 
export class UserPreferencesHeaderProvider implements HeaderProvider {
  id = 'user-preferences';
  priority = 80; // High priority but below device headers
  
  /**
   * Returns preference-related headers for API requests
   */
  async getHeaders(): Promise<Record<string, string | undefined>> {
    // Get current preferences from store
    const {
      locale,
      theme,
      timezone,
      accessibility,
      contentDensity,
      unitSystem,
      sharePreferencesWithApi
    } = usePreferencesStore.getState();
    
    // If user has opted out of sharing preferences, return empty object
    if (!sharePreferencesWithApi) {
      return {};
    }
    
    // Map preferences to header values
    return {
      [API_HEADERS.LOCALE]: locale,
      [API_HEADERS.THEME]: theme === 'system' ? undefined : theme,
      [API_HEADERS.TIMEZONE]: timezone,
      [API_HEADERS.ACCESSIBILITY]: accessibility === 'default' ? undefined : accessibility,
      [API_HEADERS.CONTENT_DENSITY]: contentDensity === 'default' ? undefined : contentDensity,
      [API_HEADERS.UNITS]: unitSystem === 'auto' ? undefined : unitSystem,
    };
  }
}

3. Registering the Provider

Add the preferences header provider to your API clients as outlined in the API Header Customization guide:

// core/shared/api/client.ts
import { PublicApiClient, AuthenticatedApiClient } from '@/core/shared/api/client';
import { DeviceHeaderProvider } from '@/core/shared/api/headers/providers/deviceHeaderProvider';
import { UserPreferencesHeaderProvider } from '@/core/shared/api/headers/providers/userPreferencesHeaderProvider';
 
// Create API client instances
const apiConfig = { baseURL: process.env.API_BASE_URL };
export const publicApi = new PublicApiClient(apiConfig);
export const authenticatedApi = new AuthenticatedApiClient(apiConfig);
 
// Register header providers
// Device headers (higher priority)
const deviceHeaderProvider = new DeviceHeaderProvider();
publicApi.registerHeaderProvider(deviceHeaderProvider);
authenticatedApi.registerHeaderProvider(deviceHeaderProvider);
 
// User preference headers
const preferencesHeaderProvider = new UserPreferencesHeaderProvider();
publicApi.registerHeaderProvider(preferencesHeaderProvider);
authenticatedApi.registerHeaderProvider(preferencesHeaderProvider);

4. API Header Constants

Ensure the preference headers are defined in your header constants:

// core/shared/constants/headers.ts
export const API_HEADERS = {
  // ... existing headers
  
  // User preference headers
  LOCALE: 'X-LOCALE',
  THEME: 'X-THEME',
  TIMEZONE: 'X-TIMEZONE',
  ACCESSIBILITY: 'X-ACCESSIBILITY',
  CONTENT_DENSITY: 'X-CONTENT-DENSITY',
  UNITS: 'X-UNITS',
  
  // ... other headers
} as const;

Advanced Use Cases

1. User Preference Override for Specific Requests

Sometimes you might need to override user preferences for specific API requests:

// core/domains/content/api.ts
import { authenticatedApi } from '@/core/shared/api/client';
import { API_HEADERS } from '@/core/shared/constants/headers';
 
class ContentApi {
  public readonly protected = {
    // Get content with standard user preferences
    getContent: async (contentId: string) => {
      return await authenticatedApi.get(`/content/${contentId}`);
    },
    
    // Get content with a specific locale override
    getContentInLocale: async (contentId: string, locale: string) => {
      return await authenticatedApi.get(`/content/${contentId}`, {
        headers: {
          [API_HEADERS.LOCALE]: locale, // Override the user's default locale
        },
      });
    },
    
    // Preview content with different theme
    previewContentWithTheme: async (contentId: string, theme: 'light' | 'dark') => {
      return await authenticatedApi.get(`/content/${contentId}/preview`, {
        headers: {
          [API_HEADERS.THEME]: theme, // Override user's theme preference
        },
      });
    },
  };
}
 
export const contentApi = new ContentApi();

2. Syncing Preferences with User Profile

For applications that store user preferences in the backend, you might need to sync local preferences with the server profile:

// core/domains/preferences/syncService.ts
import { usePreferencesStore } from './store';
import { useUserStore } from '@/core/domains/user/store';
import { userApi } from '@/core/domains/user/api';
 
export class PreferenceSyncService {
  /**
   * Sync local preferences with user profile from server
   */
  async syncWithServer(): Promise<void> {
    const { user } = useUserStore.getState();
    
    // Only sync for authenticated users
    if (!user) {
      return;
    }
    
    try {
      // Fetch latest preferences from server
      const userProfile = await userApi.protected.getCurrentUser();
      
      // Only update preferences that exist in the profile
      const preferenceUpdates: Partial<typeof usePreferencesStore.getState()> = {};
      
      if (userProfile.preferences) {
        const { preferences } = userProfile;
        
        if (preferences.locale) preferenceUpdates.locale = preferences.locale;
        if (preferences.theme) preferenceUpdates.theme = preferences.theme;
        if (preferences.timezone) preferenceUpdates.timezone = preferences.timezone;
        if (preferences.accessibility) preferenceUpdates.accessibility = preferences.accessibility;
        if (preferences.contentDensity) preferenceUpdates.contentDensity = preferences.contentDensity;
        if (preferences.unitSystem) preferenceUpdates.unitSystem = preferences.unitSystem;
      }
      
      // Update local preferences if there are changes
      if (Object.keys(preferenceUpdates).length > 0) {
        // Use the "set" function from Zustand
        usePreferencesStore.setState(preferenceUpdates);
      }
    } catch (error) {
      console.error('Failed to sync preferences with server:', error);
      // Continue with local preferences
    }
  }
  
  /**
   * Save local preferences to server
   */
  async saveToServer(): Promise<void> {
    const { user } = useUserStore.getState();
    
    // Only sync for authenticated users
    if (!user) {
      return;
    }
    
    const {
      locale,
      theme,
      timezone,
      accessibility,
      contentDensity,
      unitSystem,
    } = usePreferencesStore.getState();
    
    try {
      // Send preferences to server
      await userApi.protected.updatePreferences({
        locale,
        theme,
        timezone,
        accessibility,
        contentDensity,
        unitSystem,
      });
    } catch (error) {
      console.error('Failed to save preferences to server:', error);
      // Continue with local preferences
    }
  }
}
 
// Create a hook for easy access to the sync service
export function usePreferenceSync() {
  const syncService = new PreferenceSyncService();
  
  return {
    syncWithServer: () => syncService.syncWithServer(),
    saveToServer: () => syncService.saveToServer(),
  };
}

3. Context-Specific Preferences

Some applications may need to handle different preferences for different contexts (e.g., work vs. personal):

// core/domains/preferences/contextStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
 
export type PreferenceContext = 'personal' | 'work' | 'custom';
 
interface ContextState {
  activeContext: PreferenceContext;
  setActiveContext: (context: PreferenceContext) => void;
}
 
export const usePreferenceContextStore = create<ContextState>()(
  persist(
    (set) => ({
      activeContext: 'personal',
      setActiveContext: (activeContext) => set({ activeContext }),
    }),
    {
      name: 'preference-context-storage',
      storage: {
        getItem: async (name) => {
          const value = await AsyncStorage.getItem(name);
          return value ?? null;
        },
        setItem: async (name, value) => {
          await AsyncStorage.setItem(name, value);
        },
        removeItem: async (name) => {
          await AsyncStorage.removeItem(name);
        },
      },
    }
  )
);
 
// Enhanced provider that includes context
export class ContextualPreferencesHeaderProvider extends UserPreferencesHeaderProvider {
  id = 'contextual-user-preferences';
  
  async getHeaders(): Promise<Record<string, string | undefined>> {
    // Get base preference headers
    const headers = await super.getHeaders();
    
    // Add context information
    const { activeContext } = usePreferenceContextStore.getState();
    
    return {
      ...headers,
      'X-PREFERENCE-CONTEXT': activeContext,
    };
  }
}

Testing Strategies

1. Unit Testing the Provider

// __tests__/core/shared/api/headers/providers/userPreferencesHeaderProvider.test.ts
import { UserPreferencesHeaderProvider } from '@/core/shared/api/headers/providers/userPreferencesHeaderProvider';
import { usePreferencesStore } from '@/core/domains/preferences/store';
import { API_HEADERS } from '@/core/shared/constants/headers';
 
describe('UserPreferencesHeaderProvider', () => {
  let provider: UserPreferencesHeaderProvider;
  
  beforeEach(() => {
    // Reset mocks and state
    jest.clearAllMocks();
    
    // Reset preferences store to default values
    usePreferencesStore.setState({
      locale: 'en-US',
      theme: 'system',
      timezone: 'America/New_York',
      accessibility: 'default',
      contentDensity: 'default',
      unitSystem: 'auto',
      sharePreferencesWithApi: true,
      // Include the required functions to satisfy the type
      setLocale: jest.fn(),
      setTheme: jest.fn(),
      setTimezone: jest.fn(),
      setAccessibility: jest.fn(),
      setContentDensity: jest.fn(),
      setUnitSystem: jest.fn(),
      setSharePreferencesWithApi: jest.fn(),
      resetToDefaults: jest.fn(),
    });
    
    // Create provider instance
    provider = new UserPreferencesHeaderProvider();
  });
  
  it('should return user preference headers', async () => {
    // Update preferences store with test values
    usePreferencesStore.setState({
      locale: 'fr-FR',
      theme: 'dark',
      timezone: 'Europe/Paris',
      accessibility: 'high-contrast',
      contentDensity: 'compact',
      unitSystem: 'metric',
    });
    
    const headers = await provider.getHeaders();
    
    // Verify headers contain the correct values
    expect(headers[API_HEADERS.LOCALE]).toBe('fr-FR');
    expect(headers[API_HEADERS.THEME]).toBe('dark');
    expect(headers[API_HEADERS.TIMEZONE]).toBe('Europe/Paris');
    expect(headers[API_HEADERS.ACCESSIBILITY]).toBe('high-contrast');
    expect(headers[API_HEADERS.CONTENT_DENSITY]).toBe('compact');
    expect(headers[API_HEADERS.UNITS]).toBe('metric');
  });
  
  it('should not include default values in headers', async () => {
    // 'system', 'default', and 'auto' values should be excluded
    usePreferencesStore.setState({
      theme: 'system',
      accessibility: 'default',
      contentDensity: 'default',
      unitSystem: 'auto',
    });
    
    const headers = await provider.getHeaders();
    
    expect(headers[API_HEADERS.THEME]).toBeUndefined();
    expect(headers[API_HEADERS.ACCESSIBILITY]).toBeUndefined();
    expect(headers[API_HEADERS.CONTENT_DENSITY]).toBeUndefined();
    expect(headers[API_HEADERS.UNITS]).toBeUndefined();
  });
  
  it('should not include any headers when user opts out of sharing', async () => {
    // Opt out of sharing preferences
    usePreferencesStore.setState({
      sharePreferencesWithApi: false,
      locale: 'fr-FR',
      theme: 'dark',
    });
    
    const headers = await provider.getHeaders();
    
    // Should return empty object
    expect(Object.keys(headers).length).toBe(0);
    expect(headers[API_HEADERS.LOCALE]).toBeUndefined();
    expect(headers[API_HEADERS.THEME]).toBeUndefined();
  });
});

2. Integration Testing with API Client

// __tests__/integration/api/userPreferencesIntegration.test.ts
import { authenticatedApi } from '@/core/shared/api/client';
import { UserPreferencesHeaderProvider } from '@/core/shared/api/headers/providers/userPreferencesHeaderProvider';
import { usePreferencesStore } from '@/core/domains/preferences/store';
import { API_HEADERS } from '@/core/shared/constants/headers';
import MockAdapter from 'axios-mock-adapter';
 
describe('User Preferences Headers Integration', () => {
  let mockAxios: MockAdapter;
  
  beforeEach(() => {
    // Setup mock for the Axios instance
    mockAxios = new MockAdapter((authenticatedApi as any).client);
    
    // Reset preferences store
    usePreferencesStore.setState({
      locale: 'en-US',
      theme: 'system',
      timezone: 'America/New_York',
      accessibility: 'default',
      contentDensity: 'default',
      unitSystem: 'auto',
      sharePreferencesWithApi: true,
      // Include the required functions
      setLocale: jest.fn(),
      setTheme: jest.fn(),
      setTimezone: jest.fn(),
      setAccessibility: jest.fn(),
      setContentDensity: jest.fn(),
      setUnitSystem: jest.fn(),
      setSharePreferencesWithApi: jest.fn(),
      resetToDefaults: jest.fn(),
    });
    
    // Register preferences header provider for testing
    authenticatedApi.registerHeaderProvider(new UserPreferencesHeaderProvider());
  });
  
  afterEach(() => {
    mockAxios.restore();
  });
  
  it('should include user preference headers in API requests', async () => {
    // Set specific preferences for testing
    usePreferencesStore.setState({
      locale: 'es-ES',
      theme: 'dark',
      timezone: 'Europe/Madrid',
    });
    
    // Setup mock response
    mockAxios.onGet('/test-endpoint').reply((config) => {
      // Check that preference headers are present
      expect(config.headers[API_HEADERS.LOCALE]).toBe('es-ES');
      expect(config.headers[API_HEADERS.THEME]).toBe('dark');
      expect(config.headers[API_HEADERS.TIMEZONE]).toBe('Europe/Madrid');
      
      return [200, { success: true }];
    });
    
    // Make request
    await authenticatedApi.get('/test-endpoint');
    
    // Verification is done in the mock response
    expect(mockAxios.history.get.length).toBe(1);
  });
  
  it('should allow per-request header overrides', async () => {
    // Set default preferences
    usePreferencesStore.setState({
      locale: 'en-US',
      theme: 'light',
    });
    
    // Setup mock response
    mockAxios.onGet('/test-endpoint').reply((config) => {
      // Check that the override header is used instead of store value
      expect(config.headers[API_HEADERS.LOCALE]).toBe('fr-FR');
      expect(config.headers[API_HEADERS.THEME]).toBe('light'); // From store, not overridden
      
      return [200, { success: true }];
    });
    
    // Make request with locale override
    await authenticatedApi.get('/test-endpoint', {
      headers: {
        [API_HEADERS.LOCALE]: 'fr-FR',
      },
    });
    
    // Verification is done in the mock response
    expect(mockAxios.history.get.length).toBe(1);
  });
});

Privacy and Security Considerations

When implementing user preference headers, it's important to consider these privacy and security best practices to protect user data while still providing personalization:

  1. User Consent:

    • (Do ✅) Always provide users with the option to opt out of sharing preferences with the API.
    • (Do ✅) Clearly explain how preference information is used by your application.
    • (Consider 🤔) Implementing granular controls for which preferences are shared.
  2. Data Minimization:

    • (Do ✅) Only include necessary preference information in headers.
    • (Do ✅) Exclude default values to minimize data transfer.
    • (Don't ❌) Include personally identifiable information in preference headers.
  3. Transport Security:

    • (Do ✅) Always transmit preference headers over HTTPS.
    • (Consider 🤔) Hashing preference values that might reveal sensitive information.
  4. Storage Security:

    • (Do ✅) Use secure, encrypted storage for persistence.
    • (Consider 🤔) Implementing timeout periods for sensitive preferences.
  5. Preference Validation:

    • (Do ✅) Validate preference values on both client and server.
    • (Don't ❌) Trust client-provided preferences for security decisions.

Best Practices

1. Performance Optimization

  • Cache preferences in memory during app session to avoid repeated AsyncStorage access.
  • Batch preference updates when multiple preferences change simultaneously.
  • Debounce preference syncing with the server to prevent excessive API calls.

2. Preference Management UI

  • Provide a dedicated settings screen for users to manage their preferences.
  • Group related preferences for better usability.
  • Show real-time previews of preference changes when possible.
  • Include an explicit option to control preference sharing with APIs.

3. Integration Best Practices

  • Load preferences early in the application lifecycle.
  • Handle preference loading errors gracefully with sensible defaults.
  • Maintain preference continuity across app updates.
  • Consider device capabilities when applying preferences (e.g., high-contrast might not work on all devices).

4. Preference Lifecycle Management

  • Sync preferences across devices for logged-in users.
  • Preserve preferences during account transitions when appropriate.
  • Provide reset options for individual preferences and all preferences.
  • Migrate preferences when your preference schema changes.

Migration Considerations

If you're transitioning to this user preferences header approach, consider the following migration paths:

From Hard-Coded Preferences

If your application currently uses hard-coded preferences directly in API calls:

  1. Identify all hard-coded preferences in your codebase, typically found near API call sites
  2. Create the preference store to centralize preference management
  3. Implement the preference header provider as outlined in this guide
  4. Remove hard-coded preferences from individual API calls
  5. Update unit tests to reflect the new approach

From Query Parameters

If you're currently passing user preferences as query parameters:

  1. Identify query parameter patterns used across your API calls
  2. Map query parameters to headers in your implementation
  3. Coordinate with backend teams to support both approaches during transition
  4. Gradually migrate endpoints to use header-based preferences
  5. Remove query parameter code once migration is complete

From Server-Side User Profile

If preferences are currently stored and retrieved solely from server profiles:

  1. Implement client-side preference store while maintaining server storage
  2. Create synchronization logic between client and server preferences
  3. Add header provider to include preferences in API calls
  4. Update backend services to prioritize header preferences over stored preferences when present
  5. Monitor performance improvements from reduced profile lookups

Summary

User preference headers provide a robust mechanism for communicating user settings to backend services, enabling personalized experiences without additional API calls. By implementing the UserPreferencesHeaderProvider and integrating it with our API client architecture, we ensure consistent preference application across all API interactions while respecting user privacy choices.

Key takeaways:

  • Use Zustand for preference state management and persistence
  • Implement the HeaderProvider pattern for seamless integration with API clients
  • Only include non-default preference values in headers
  • Respect user opt-out options for preference sharing
  • Enable per-request overrides for specific use cases
  • Consider both performance and privacy implications

This implementation approach ensures that user preferences are consistently applied across your application while maintaining the flexibility to override preferences when needed for specific contexts.