RFC-001: GA4 Event Contract & Reporting Alignment
Related Documents: ADR-018 (Typed Analytics Architecture)
Abstract
This document specifies the Google Analytics 4 (GA4) event contract and reporting alignment protocol for the OneCMS platform. It defines event naming conventions, payload structure, versioning strategy, parameter normalization rules, and cross-functional ownership boundaries between Engineering and Analytics teams.
The specification is designed to prevent analytics schema drift, ensure dashboard stability, enforce compile-time type safety, and establish a formal telemetry contract that evolves safely over time. Implementations emitting analytics events from OneCMS MUST comply with this specification.
1. Introduction
1.1 Background
OneCMS has implemented a typed Google Analytics (GA4) event architecture (ADR-018) to enforce compile-time validation, schema consistency, and production-safe event emission. With the involvement of the Analytics team, telemetry becomes a cross-functional contract between Engineering and Analytics.
Analytics events are sent to a dedicated GA4 property separate from consumer-facing analytics to enable operational insights, product telemetry, and behavioral analysis without interfering with end-user tracking.
1.2 Motivation
Without a clearly documented event schema, naming convention, and ownership boundary, the following risks arise:
- Silent event schema drift leading to invalid dashboard queries
- Dashboard instability due to inconsistent payload shapes
- Ambiguity around event ownership and configuration authority
- Confusion regarding impression tracking and conversion event designation
- Inability to safely evolve analytics schema over time
1.3 Goals
This RFC formalizes:
- The canonical event naming pattern
- The typed event envelope structure
- Parameter naming and validation rules
- Schema versioning and backward compatibility strategy
- Ownership boundaries between Engineering and Analytics
- Production emission safeguards
1.4 Scope
This specification supersedes previous untyped analytics implementations and governs all GA4 event emission from OneCMS frontend applications.
2. Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
2.1 Definitions
- Event: An analytics occurrence representing a user action or system state change
- Category: A domain grouping for related events (e.g.,
authentication,content) - Event Name: The GA4-emitted event identifier in the format
category_event - Envelope: The canonical typed container for all event data
- Payload: Event-specific parameters beyond the common envelope fields
- Schema Version: A centrally managed version identifier for the analytics contract
- Normalization: The process of transforming event data to meet GA4 constraints
- Production Gating: The mechanism preventing non-production events from reaching GA4
3. Event Naming Convention
3.1 Format Specification
All events emitted from OneCMS MUST follow the format:
<category>_<event>
Where:
<category>is an approved domain category (Section 13.1)<event>is a lowercase action identifier using snake_case- The complete event name MUST be lowercase
3.2 Event Name Construction
Event names are constructed programmatically:
const eventName = `${category}_${event}`.toLowerCase();
3.3 Examples
Valid Event Names:
authentication_logincontent_publishupload_completecustomer_support_session_start
Invalid Event Names (DO NOT USE):
AuthenticationLogin(not lowercase)content-publish(hyphen instead of underscore)login_success(outcome encoded in name - useoutcomeparameter instead)
3.4 Event Name Stability
Once an event name is published to production, it MUST NOT be changed. Renaming requires schema version increment and deprecation period.
4. Event Contract Structure
4.1 Envelope Structure
All events MUST conform to the AnalyticsEventEnvelope type:
type AnalyticsEventEnvelope<C extends AnalyticsCategory, E extends CategoryEventMap[C]> = {
category: C;
event: E;
outcome?: EventOutcome;
} & BaseEventContext &
EventPayloadRegistry[E];
4.2 Common Parameters
The following parameters MUST be present on all events:
| Parameter | Type | Required | Description |
|---|---|---|---|
category |
string | Yes | Event domain |
event |
string | Yes | Action identifier |
schema_version |
string | Yes | Analytics contract version (injected automatically) |
site |
string | Yes | Site identifier |
siteId |
string | Yes | Numeric site ID |
env |
string | Yes | Environment: development | testing | production |
outcome |
string | No | Result: success | fail | cancel |
userId |
string | No | Opaque user identifier (hashed) |
4.3 Event-Specific Parameters
Each event MAY define additional typed parameters beyond the common envelope.
Example: authentication_login
type LoginPayload = {
method: 'credentials' | 'google' | 'microsoft';
error_code?: string; // Only present when outcome = 'fail'
};
Example: upload_init
type UploadInitPayload = {
file_type: 'video' | 'audio' | 'image' | 'document';
file_size_mb: number;
upload_strategy: 'chunked' | 'direct';
};
Example: content_publish
type ContentPublishPayload = {
content_type: 'movie' | 'series' | 'clip' | 'article';
has_schedule: boolean;
duration_sec?: number;
error_code?: string;
};
5. Parameter Specifications
5.1 Parameter Naming Convention
All custom parameters MUST follow these rules:
- Case: snake_case (lowercase with underscores)
- Boolean prefixes:
is_orhas_(e.g.,is_scheduled,has_errors) - Error parameters: Prefixed with
error_(e.g.,error_code,error_message) - Duration parameters: Suffixed with unit (e.g.,
duration_sec,timeout_ms,timestamp_unix) - Count parameters: Suffixed with subject (e.g.,
item_count,retry_count)
5.2 Parameter Value Constraints
Implementations MUST enforce the following GA4 limits:
- Parameter name length: Maximum 40 characters
- Parameter value length: Maximum 100 characters (strings auto-truncated)
- Total custom parameters per event: SHOULD NOT exceed 25 to ensure compatibility with GA4 reporting limits (built-in parameters do not count toward this limit)
- Array values: Serialized as comma-separated strings
5.3 Parameter Types
| Type | Serialization | Example |
|---|---|---|
string |
As-is (truncated to 100 chars) | "credentials" |
number |
Numeric or string (implementation choice; GA4 accepts both) | 7200 or "7200" |
boolean |
"true" or "false" |
"true" |
Array<string> |
Comma-joined | "tag1,tag2,tag3" |
undefined |
Removed from payload | N/A |
null |
Removed from payload | N/A |
5.4 Reserved Parameter Names
The following parameter names are RESERVED by GA4 and MUST NOT be used for custom parameters:
page_location,page_title,page_referrerlanguage,screen_resolutionclient_id,session_id- Any parameter prefixed with
ga_orfirebase_
6. Versioning and Evolution
6.1 Schema Version Format
The schema_version follows semantic versioning: MAJOR.MINOR.PATCH
Current version: 1.0.0
6.2 Version Increment Rules
MAJOR increment (e.g., 1.0.0 → 2.0.0) is REQUIRED when:
- Removing a common parameter
- Renaming any parameter
- Changing parameter semantics
- Restructuring envelope shape
- Changes that would break interpretation of historical data
MINOR increment (e.g., 1.0.0 → 1.1.0) is OPTIONAL for:
- Adding new optional parameters
- Adding new event types
- Expanding enum values
PATCH increment (e.g., 1.0.0 → 1.0.1) for:
- Bug fixes in normalization logic
- Documentation clarifications
- Internal implementation improvements that do not affect payload structure
6.3 Backward Compatibility
Schema changes MUST maintain dashboard compatibility:
- Historical queries MUST remain valid
- Dashboards MUST filter by
schema_versionif behavior differs - Deprecated parameters MUST be documented with sunset date
6.4 Deprecation Process
- Document deprecation with replacement guidance
- Dual-emit old and new versions for 30 days minimum
- Deprecated parameters MUST remain readable by dashboards until sunset
- Remove deprecated version only after Analytics team confirmation
7. Payload Normalization
7.1 Normalization Adapter
All events MUST pass through the normalizeAnalyticsPayload adapter before emission:
function normalizeAnalyticsPayload(payload: Record<string, unknown>): Record<string, unknown> {
const normalizedEntries = Object.entries(payload)
.filter(([, value]) => value !== undefined)
.map(([key, value]) => [key, normalizeValue(value)]);
const limitedEntries = normalizedEntries.slice(0, param_limit);
return Object.fromEntries(limitedEntries);
}
7.2 Normalization Rules
The normalization process:
- Removes:
undefinedandnullvalues - Truncates: Strings exceeding 100 characters
- Serializes: Arrays to comma-separated strings
- Limits: Total parameters to 25 per event
- Preserves: Parameter order (first 25 if overflow)
7.3 Value Normalization
function normalizeValue(value: unknown): unknown {
if (value === undefined || value === null) return undefined;
if (typeof value === 'string') {
return value.length > 100 ? value.slice(0, 100) : value;
}
if (Array.isArray(value)) {
return value.join(',');
}
return value;
}
8. Environment and Emission Control
8.1 Production Gating
Events MUST only be emitted to GA4 when:
envelope.env === 'production';
8.2 Non-Production Behavior
In non-production environments (development, testing):
- Events MUST be logged to console with
[GA - Skipped in non-prod]prefix - Events MUST NOT be sent to GA4
- Full payload MUST be visible for debugging
if (envelope.env !== 'production') {
console.debug('[GA - Skipped in non-prod]', eventName, finalPayload);
return;
}
8.3 Implementation
static trackAnalytics(envelope: AllAnalyticsEnvelopes) {
const eventName = `${envelope.category}_${envelope.event}`.toLowerCase();
const finalPayload = normalizeAnalyticsPayload({
...envelope,
schema_version: Config.GA.schema_version
});
if (envelope.env !== 'production') {
console.debug('[GA - Skipped in non-prod]', eventName, finalPayload);
return;
}
this.track(eventName, finalPayload);
}
9. Ownership and Responsibilities
9.1 Engineering Team (OneCMS)
Owns and Maintains:
- Event emission logic and transport layer
- Typed event contracts and TypeScript definitions
- Category → event mapping enforcement
- Schema versioning (incrementing
schema_version) - Payload normalization adapter
- Production gating mechanism
- Compile-time type safety
- Event payload registry
- Frontend analytics instrumentation
- Backward compatibility within TypeScript contracts
Does NOT Own:
- GA4 property configuration
- Dashboard design or queries
- Custom dimension registration in GA4
- Conversion event designation
9.2 Analytics Team
Owns and Maintains:
- GA4 property configuration and settings
- Custom dimension and metric registration
- Conversion event designation and goals
- Impression tracking design and implementation
- Report structuring, collections, and visualizations
- Event grouping strategy within GA4
- Dashboard queries and data exploration
- BigQuery export configuration (if applicable)
Does NOT Own:
- Frontend event payload structure
- Event naming conventions
- TypeScript type definitions
- Event emission timing or logic
9.3 Collaboration Points
Both teams MUST collaborate on:
- Custom Dimension Mapping: Which payload parameters should be registered as GA4 custom dimensions
- Conversion Events: Which events represent meaningful conversions
- High-Frequency Events: Sampling or throttling strategies for noisy events
- Breaking Changes: Schema version increments and migration planning
- New Categories: Adding new event categories requires joint approval
10. Security Considerations
10.1 Personally Identifiable Information (PII)
Event payloads MUST NOT contain:
- Real names, email addresses, phone numbers
- IP addresses (anonymized at GA4 property level)
- Plaintext passwords or credentials
- Payment information
- Unmasked sensitive identifiers
10.2 User Identification
userIdMUST be an opaque, hashed identifier- MUST NOT be reversible to original user identity
- MUST comply with GDPR and privacy regulations
10.3 Error Messages
error_code and error_message parameters MUST NOT contain:
- Stack traces with file paths
- Database query details
- API keys or tokens
- Internal system architecture details
10.4 Data Minimization
Events SHOULD include only parameters necessary for analytics insights. Avoid over-instrumentation.
Raw user-generated content (e.g., titles, comments, descriptions) MUST NOT be emitted as analytics parameters to prevent PII exposure and excessive data collection.
10.5 Consent Management
GA4 tracking respects user consent settings:
static DefaultPermissionMap: Record<GtagCategoryType, GAPermissionType> = {
analytics: 'denied', // Requires explicit user consent
necessary: 'granted', // Essential cookies
};
11. Implementation Requirements
11.1 Type Safety
All events MUST be strongly typed using TypeScript:
type AnalyticsEventEnvelope<C extends AnalyticsCategory, E extends CategoryEventMap[C]> = {
category: C;
event: E;
outcome?: EventOutcome;
} & BaseEventContext &
EventPayloadRegistry[E];
11.2 Event Emission
Events MUST be emitted via the centralized GA.trackAnalytics() method:
GA.trackAnalytics({
category: 'authentication',
event: 'login',
outcome: 'success',
method: 'google',
site: currentSite,
siteId: currentSiteId,
env: process.env.NODE_ENV,
});
11.3 Testing Strategy
Development Testing
- Non-production events logged to console
- Payload shape validated via TypeScript compiler
- Console logs inspected for correct structure
Staging Testing
- Separate GA4 property for staging environment
- Events emitted to staging property for validation
- Analytics team reviews staging data before production
Production Testing
- Smoke tests for critical event flows
- Dashboard monitoring for event volume anomalies
- Schema version tracking in GA4 reports
11.4 Error Handling
Event emission failures MUST:
- NOT throw exceptions that block user workflow
- Log errors to console in development mode
- Fail silently in production (fire-and-forget)
- NOT retry automatically (prevents event duplication)
private static track(event: string, payload: Record<string, unknown>) {
if (typeof window === 'undefined' || !('gtag' in window)) return;
try {
window.gtag('event', event, payload);
} catch (error) {
console.debug('[GA Error]', error);
}
}
11.5 Configuration
Analytics configuration MUST be centralized in /src/config/config.ts:
GA: {
measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID,
schema_version: '1.0.0',
string_char_limit: 100,
param_limit: 25,
}
12. References
12.1 Normative References
- [RFC-2119]: Key words for use in RFCs to Indicate Requirement Levels
- [ADR-018]: Typed Analytics (GA) Architecture
- [GA4-LIMITS]: Google Analytics 4 Event Parameter Limits https://support.google.com/analytics/answer/9267744
12.2 Informative References
- [GDPR]: General Data Protection Regulation
- [GA4-DOCS]: Google Analytics 4 Developer Documentation
- [TYPESCRIPT]: TypeScript Handbook
12.3 Internal References
/src/lib/external/GA.ts: Analytics transport layer/src/adapters/analytics/normalizeAnalyticsPayload.adapter.ts: Normalization logic/src/types/analytics/index.ts: Event type definitions/src/config/config.ts: Analytics configuration
13. Appendices
13.1 Approved Event Categories
The following categories are currently approved for use:
| Category | Domain | Example Events |
|---|---|---|
authentication |
User authentication lifecycle | login, logout, mfa, reset_password |
content |
Content management operations | publish, update, archive, preview |
upload |
Media upload workflows | init, progress, complete, fail |
monetization |
Revenue and pricing operations | plan_change, subscription_update |
customer_support |
Support interactions | session_start, ticket_create |
navigation |
User navigation patterns | menu_item_click, tab_change |
appcms |
App configuration management | build_submit, settings_save |
13.2 High-Frequency Event Guidance
Events that fire more than 10 times per user session SHOULD be evaluated for:
- Throttling: Limit to 1 emission per N seconds
- Sampling: Emit only X% of occurrences
- Exclusion: Don't track at all if low analytical value
This threshold is guidance, not a strict rule. Actual thresholds depend on event value and GA4 quota considerations.
Examples:
autosave_success: Throttle to max 1/minutecontent_search_keystroke: Don't track (usesearch_completeinstead)upload_progress: Sample at 25% intervals only
13.3 Conversion Event Recommendations
The following recommendations are informative only and subject to Analytics team approval.
Primary Conversions (high-value actions):
authentication_login(outcome: success)content_publish(outcome: success)build_submittedmonetization_subscription_upgrade
Secondary Conversions (engagement indicators):
content_previewcustomer_support_session_startsettings_save
Final conversion designation determined by Analytics team.
13.4 Custom Dimension Mapping
Recommended Custom Dimensions:
| Parameter | GA4 Dimension Name | Scope | Rationale |
|---|---|---|---|
schema_version |
analytics_version |
Event | Track contract evolution |
site |
tenant_site |
User | Multi-tenant analysis |
outcome |
event_outcome |
Event | Success/fail segmentation |
method (auth) |
auth_method |
Event | SSO vs credentials tracking |
content_type |
cms_content_type |
Event | Content type analysis |
Analytics team configures actual dimension registration in GA4.
13.5 Sample Event Payloads
Example 1: Successful Login
{
"category": "authentication",
"event": "login",
"outcome": "success",
"method": "google",
"schema_version": "1.0.0",
"site": "acme-ott",
"siteId": "12345",
"env": "production",
"userId": "a1b2c3d4e5f6"
}
Example 2: Failed Content Publish
{
"category": "content",
"event": "publish",
"outcome": "fail",
"content_type": "movie",
"error_code": "validation_failed",
"duration_sec": 7200,
"has_schedule": false,
"schema_version": "1.0.0",
"site": "acme-ott",
"siteId": "12345",
"env": "production"
}
Example 3: Upload Complete
{
"category": "content",
"event": "upload_complete",
"outcome": "success",
"file_type": "video",
"file_size_mb": 1250,
"upload_strategy": "chunked",
"duration_sec": 3840,
"schema_version": "1.0.0",
"site": "acme-ott",
"siteId": "12345",
"env": "production"
}
13.6 Migration Path
From: Untyped analytics implementation (pre-ADR-018)
To: Typed analytics contract (this RFC)
Timeline:
- Phase 1 (Current): New events use typed contract
- Phase 2 (Q2 2026): Legacy events dual-emit (old + new format)
- Phase 3 (Q3 2026): Legacy events deprecated, warnings logged
- Phase 4 (Q4 2026): Legacy events removed
13.7 Change Management Process
For new events (no RFC required):
- Add type definition to
/src/types/analytics/<category>/ - Add to
EventPayloadRegistry - Update
CategoryEventMapif needed - Create PR with type-checked implementation
- Notify Analytics team via Slack (include sample payload)
For breaking changes (RFC required):
- Draft RFC amendment
- Increment
schema_version - Engineering + Analytics team review
- Plan deprecation timeline
- Dual-emit old + new versions
- Monitor dashboard impact
- Complete migration
14. Open Questions
The following items require alignment with the Analytics team before finalization:
-
Conversion Event Designation:
- Which CMS actions should be marked as GA4 conversion events?
- Should publish workflows be primary or secondary conversions?
-
High-Frequency Event Handling:
- Specific throttling rates for autosave, search, progress events?
- Which events warrant complete exclusion from tracking?
-
Custom Dimension Priorities:
- Which 50 parameters (GA4 limit) should be promoted to custom dimensions?
- What dimension scopes (event vs user vs session)?
-
Multi-Site Handling:
- How to differentiate events when users switch sites in one session?
- Should
sitebe a dimension or filter?
-
Event Lifecycle Patterns:
- Prefer paired events (
upload_start+upload_complete) or atomic events? - Should failed events have separate event names or rely on
outcome?
- Prefer paired events (
15. Decision Outcome
Status: APPROVED
Approved On: 2026-03-11
This RFC is the governing analytics contract blueprint for dependent implementation RFCs. It is treated as a standards baseline and remains in APPROVED state as the authoritative specification.
16. Acknowledgments
This specification builds upon ADR-018 (Typed Analytics Architecture) and incorporates feedback from the OneCMS Engineering and Analytics teams. Special thanks to contributors who reviewed early drafts and provided implementation insights.
17. Document History
| Date | Version | Change Summary | Author |
|---|---|---|---|
| 2026-02-25 | 1.0 | Initial draft of GA4 global event contract RFC | Aakash Jha |
| 2026-03-05 | 1.1 | Revised to RFC standard format and expanded technical specification details | Aakash Jha |
| 2026-03-11 | 1.2 | Status promoted to APPROVED as canonical analytics contract blueprint | Aakash Jha |
| 2026-03-14 | 1.3 | Decision outcome clarified and document history normalized to standard table format | Aakash Jha |