ANALYTICS

Google Analytics 4 Cookie Format Change: From GS1 to GS2 Explained


David Vallejo
Share

Overview


Google Analytics has introduced a new cookie format (v2) to their Cookie Stream Cookie that changes how tracking data is structured. This guide explains the differences between the old GS1 format and the new GS2 format, and demonstrates how to parse both versions

Why Did Google Change the Cookie Format?

Google's transition from the GS1 to GS2 cookie format wasn't arbitrary. Here are several strategic reasons behind this change:

1. Improved Extensibility

The new format with single-letter prefixes allows Google to easily add new tracking parameters without breaking existing implementations. With GS1's fixed positional format, adding new parameters would require updating all parsers to handle additional positions.

2. Self-Documenting Structure

The prefix system makes cookies more readable and debuggable. Instead of remembering that position 5 is "lastHitTimestamp", developers can see t1746825440 and understand what t represents.

3. Version Control

The explicit GS2 prefix enables Google to:

  • Track which format version is being used
  • Potentially introduce GS3, GS4, etc. in the future
  • Maintain backward compatibility during transitions
  • 4. Data Optimization

    The dollar sign separator and prefix system can be more efficient:

  • Empty values can be omitted entirely (no need for consecutive dots)
  • Parameters can appear in any order
  • Optional parameters don't require placeholder positions
  • 5. Better Error Handling

    With prefixed values, parsers can:

  • Ignore unknown prefixes without breaking
  • Handle missing parameters gracefully
  • Validate data more effectively
  • 6. Alignment with Modern Standards

    The key-value pair approach (prefix + value) aligns better with:

  • JSON-like structures
  • URL parameter formats
  • Modern data serialization patterns
  • 7. Align with other Google Cookies

    These format updates aren't isolated to Google Analytics. Google is systematically updating various tracking mechanisms to:

    Google has recently updated several other cookie formats as well, most notably the Google Click ID (gclid) cookie. These changes follow a similar pattern of modernization:

    This evolution reflects Google's need for a more flexible, maintainable tracking system as Google Analytics continues to evolve and add new features.

    Old Format (GS1)

    The original Google Analytics cookie format uses a straightforward dot-separated structure:

    GS1.1.1746825440.14.0.17468254406.0.0.295082955

    Characteristics:

  • Uses dots (.) as separators throughout
  • Begins with "GS1" (Google Stream Version 1)
  • Values are in fixed positions without prefixes
  • Each position has a specific meaning
  • Structure Breakdown:

    
    
    
    
    

    New Format (GS2)

    Google Analytics has transitioned to a more flexible cookie format that uses prefixed values:

    GS2.1.s1746825440$o14$g0$t1746825440$j60$l0$h295082955

    Characteristics:

  • Uses dollar signs ($) as separators after the header
  • Begins with "GS2" (Google Stream Version 2)
  • Each value has a single-letter prefix identifier
  • More extensible and self-documenting format
  • Structure Breakdown:

    
    
    
    
    

    Prefix Meanings:

  • s - Session ID
  • o - Session Number
  • g - Session Engaged
  • t - Last Hit Timestamp
  • j - Join Timer
  • l - Enhanced User ID Logged In State
  • h - Enhanced User ID (hash)
  • d - Join ID (not present in this example, appears in some cookies)
  • Complete Parser Implementation

    I've created a universal parser that handles both Google Analytics cookie formats. This parser is based on my previous work that originally handled only the GS1 format, which I've now adapted to automatically detect and parse both GS1 and GS2 versions.

    The parser provides:

  • Automatic format detection (GS1 vs GS2)
  • Consistent output structure regardless of input format
  • URL encoding support for dollar signs (%24)
  • Fallback handling for missing values
  • I'm providing both ES6 and ES5 versions to ensure compatibility across different JavaScript environments:

    ES6 Version (Modern JavaScript)

    
    
    
    
    
    const parseGoogleStreamCookie = str => {
      const mapping = {
        s: "sessionId",
        o: "sessionNumber", 
        g: "sessionEngaged",
        t: "lastHitTimestamp",
        j: "joinTimer",
        l: "enhancedUserIdLoggedInState",
        h: "enhancedUserId",
        d: "joinId"
      };
      
      const [version, , data, ...rest] = str.split('.');
      const keys = ['s', 'o', 'g', 't', 'j', 'l', 'h', 'd'];
      
      if (version === 'GS1') {
        return Object.fromEntries(
          keys.map((k, i) => [mapping[k], [data, ...rest][i] || ''])
        );
      }
      
      return Object.fromEntries(
        data.replace(/%24/g, '$')
            .split('$')
            .map(s => [mapping[s[0]] || s[0], decodeURIComponent(s.slice(1))])
      );
    };

    ES5 Version (Legacy Browser/GTM Support)

    
    
    
    
    
    function parseGoogleStreamCookie(str) {
      var mapping = {
        s: "sessionId",
        o: "sessionNumber",
        g: "sessionEngaged",
        t: "lastHitTimestamp",
        j: "joinTimer",
        l: "enhancedUserIdLoggedInState",
        h: "enhancedUserId",
        d: "joinId"
      };
      
      var parts = str.split('.');
      var version = parts[0];
      var result = {};
      var entries = [];
      
      if (version === 'GS1') {
        // GS1: Create entries from positional values
        var values = parts.slice(2);
        var keys = ['s', 'o', 'g', 't', 'j', 'l', 'h', 'd'];
        
        for (var i = 0; i < keys.length; i++) {
          entries.push([mapping[keys[i]], values[i] || '']);
        }
      } else {
        // GS2: Create entries from prefixed values
        var segments = parts[2].replace(/%24/g, '$').split('$');
        
        for (var j = 0; j < segments.length; j++) {
          var prefix = segments[j][0];
          var value = decodeURIComponent(segments[j].slice(1));
          entries.push([mapping[prefix] || prefix, value]);
        }
      }
      
      // Build result object from entries
      for (var k = 0; k < entries.length; k++) {
        result[entries[k][0]] = entries[k][1];
      }
      
      return result;
    }

    Feel free to use either version based on your project's requirements.

    The parser will return a consistent object structure with descriptive property names, making it easy to work with Google Analytics cookie data in your applications.