Experience Platform
Data Models

Data Models & Protobuf Schemas

MongoDB Collections

events (Time-Series Collection)

{
  _id: ObjectId,
  eventId: string,          // UUID
  appId: ObjectId,
  profileId: ObjectId,
  sessionId: string,
  timestampMs: number,
  eventType: string,        // page_view, click, scroll, hover, custom
  pageUrl: string,
  elementTid: string,       // data-t value
  metadata: Record<string, string>,
  pageContext: {
    path: string,
    title: string,
    referrer: string,
    viewportWidth: number,
    viewportHeight: number,
    loadTimeMs: number,
  },
  elementContext: {
    tag: string,
    text: string,
    x: number,
    y: number,
    scrollDepthPct: number,
  },
}
// Indexes: { appId, timestampMs }, { appId, eventType }, { sessionId }
// TTL: 90 days (configurable per app)

heatmap_events (Time-Series Collection)

{
  _id: ObjectId,
  appId: ObjectId,
  profileId: ObjectId,
  sessionId: string,
  pageUrl: string,
  timestampMs: number,
  x: number,
  y: number,
  viewportWidth: number,
  viewportHeight: number,
}
// Indexes: { appId, pageUrl, timestampMs }
// TTL: 30 days

errors

{
  _id: ObjectId,
  errorId: string,
  appId: ObjectId,
  groupFingerprint: string,  // for grouping
  profileId: ObjectId,
  sessionId: string,
  timestampMs: number,
  errorType: string,          // runtime, unhandled_rejection, network, render
  message: string,
  stackTrace: string,
  sourceFile: string,
  line: number,
  column: number,
  severity: 'info' | 'warning' | 'error' | 'fatal',
  userComment: string | null,
  tags: Record<string, string>,
  breadcrumbs: Array<{
    timestampMs: number,
    category: string,
    message: string,
    data: Record<string, string>,
  }>,
  pageContext: { /* same as events */ },
  browserContext: {
    userAgent: string,
    browser: string,
    browserVersion: string,
    os: string,
    deviceType: string,
    screenWidth: number,
    screenHeight: number,
  },
}
// Indexes: { appId, groupFingerprint }, { appId, timestampMs }, { appId, severity }

error_groups

{
  _id: ObjectId,
  appId: ObjectId,
  fingerprint: string,
  message: string,            // representative message
  firstSeen: Date,
  lastSeen: Date,
  occurrenceCount: number,
  affectedProfiles: number,
  status: 'new' | 'investigating' | 'resolved' | 'ignored',
  assignee: ObjectId | null,
  comments: Array<{
    authorId: ObjectId,
    text: string,
    createdAt: Date,
  }>,
}

feature_flags

{
  _id: ObjectId,
  appId: ObjectId,
  key: string,
  description: string,
  type: 'boolean' | 'string' | 'number' | 'json',
  defaultValue: any,
  enabled: boolean,
  rules: Array<{
    ruleId: string,
    priority: number,
    conditions: Array<{
      attribute: string,
      operator: string,
      values: string[],
    }>,
    value: any,
    rolloutPercentage: number,    // 0-100
    experimentId: string | null,
  }>,
  createdAt: Date,
  updatedAt: Date,
  createdBy: ObjectId,
}
// Indexes: { appId, key } (unique)

user_flag_sets (Pre-Computed)

{
  _id: ObjectId,
  profileId: ObjectId,
  appId: ObjectId,
  flags: Record<string, any>,    // { "new-checkout-ui": true, "checkout-limit": 100 }
  computedAt: Date,
}
// Indexes: { profileId, appId } (unique)

translations

{
  _id: ObjectId,
  appId: ObjectId,
  locale: string,              // e.g. "fr", "de", "es"
  key: string,                 // e.g. "checkout.button"
  sourceText: string,          // original (source locale)
  translatedText: string,
  status: 'pending' | 'suggested' | 'approved' | 'overridden',
  suggestedText: string | null,  // LLM suggestion
  translatedBy: ObjectId | null, // who approved/overrode
  updatedAt: Date,
}
// Indexes: { appId, locale, key } (unique)

translation_config

{
  _id: ObjectId,
  appId: ObjectId,
  sourceLocale: string,
  targetLocales: string[],
  sourceVersion: string,         // last seen version
  webhookSecret: string,
}

feedback

{
  _id: ObjectId,
  appId: ObjectId,
  profileId: ObjectId,
  type: 'bug' | 'feature' | 'comment' | 'praise',
  text: string,
  screenshotUrl: string | null,
  pageUrl: string,
  browserContext: { /* same as errors */ },
  status: 'new' | 'acknowledged' | 'in_progress' | 'resolved' | 'wont_fix',
  upvotes: number,
  upvotedBy: ObjectId[],
  tags: string[],
  thread: Array<{
    authorId: ObjectId,
    authorType: 'user' | 'developer',
    text: string,
    createdAt: Date,
  }>,
  createdAt: Date,
  updatedAt: Date,
}
// Indexes: { appId, status }, { appId, type }, { appId, upvotes }

themes

{
  _id: ObjectId,
  authorProfileId: ObjectId,
  name: string,
  description: string,
  isPublic: boolean,
  isOfficial: boolean,
  category: string[],          // ['dark', 'minimal', 'professional']
  installCount: number,
  rating: { avg: number, count: number },
  definition: AdvancedTheme,   // Full theme definition (see Theming section)
  createdAt: Date,
  updatedAt: Date,
}
// Indexes: { isPublic, installCount }, { authorProfileId }

app_registrations

{
  _id: ObjectId,
  appId: string,
  name: string,
  ownerProfileId: ObjectId,
  apiKey: string,              // exp_xxxxxxxxxxxx (hashed in DB)
  apiKeyPrefix: string,        // exp_xxxx (for display)
  config: {
    eventRetentionDays: number,
    heatmapRetentionDays: number,
    maxEventsPerDay: number,
  },
  createdAt: Date,
}

Protobuf Schemas

All .proto files live in shellapps-js/packages/experience-proto/.

events.proto

syntax = "proto3";
package shellapps.experience;

message TrackEvent {
  string event_id = 1;
  string app_id = 2;
  string profile_id = 3;
  string session_id = 4;
  int64 timestamp_ms = 5;
  string event_type = 6;
  string page_url = 7;
  string element_tid = 8;
  map<string, string> metadata = 9;
  PageContext page_context = 10;
  ElementContext element_context = 11;
}

message PageContext {
  string path = 1;
  string title = 2;
  string referrer = 3;
  int32 viewport_width = 4;
  int32 viewport_height = 5;
  int64 load_time_ms = 6;
}

message ElementContext {
  string tag = 1;
  string text = 2;
  int32 x = 3;
  int32 y = 4;
  int32 scroll_depth_pct = 5;
}

message HeatmapEvent {
  string app_id = 1;
  string profile_id = 2;
  string session_id = 3;
  string page_url = 4;
  int64 timestamp_ms = 5;
  int32 x = 6;
  int32 y = 7;
  int32 viewport_width = 8;
  int32 viewport_height = 9;
}

message EventBatch {
  repeated TrackEvent events = 1;
  repeated HeatmapEvent heatmap_events = 2;
}

message IngestResponse {
  bool success = 1;
  int32 events_accepted = 2;
  int32 events_rejected = 3;
}

errors.proto

syntax = "proto3";
package shellapps.experience;

message ErrorEvent {
  string error_id = 1;
  string app_id = 2;
  string profile_id = 3;
  string session_id = 4;
  int64 timestamp_ms = 5;
  string error_type = 6;
  string message = 7;
  string stack_trace = 8;
  string source_file = 9;
  int32 line = 10;
  int32 column = 11;
  PageContext page_context = 12;
  BrowserContext browser_context = 13;
  string user_comment = 14;
  Severity severity = 15;
  map<string, string> tags = 16;
  repeated Breadcrumb breadcrumbs = 17;
}

enum Severity {
  INFO = 0;
  WARNING = 1;
  ERROR = 2;
  FATAL = 3;
}

message Breadcrumb {
  int64 timestamp_ms = 1;
  string category = 2;
  string message = 3;
  map<string, string> data = 4;
}

message BrowserContext {
  string user_agent = 1;
  string browser = 2;
  string browser_version = 3;
  string os = 4;
  string device_type = 5;
  int32 screen_width = 6;
  int32 screen_height = 7;
}

message ErrorBatch {
  repeated ErrorEvent errors = 1;
}

flags.proto

syntax = "proto3";
package shellapps.experience;

message FeatureFlag {
  string flag_id = 1;
  string app_id = 2;
  string key = 3;
  string description = 4;
  FlagType type = 5;
  FlagValue default_value = 6;
  repeated FlagRule rules = 7;
  bool enabled = 8;
  int64 created_at = 9;
  int64 updated_at = 10;
}

enum FlagType {
  BOOLEAN = 0;
  STRING = 1;
  NUMBER = 2;
  JSON = 3;
}

message FlagValue {
  oneof value {
    bool bool_value = 1;
    string string_value = 2;
    double number_value = 3;
    string json_value = 4;
  }
}

message FlagRule {
  string rule_id = 1;
  int32 priority = 2;
  repeated FlagCondition conditions = 3;
  FlagValue value = 4;
  int32 rollout_percentage = 5;
  string experiment_id = 6;
}

message FlagCondition {
  string attribute = 1;
  string operator = 2;
  repeated string values = 3;
}

message UserFlagSet {
  string profile_id = 1;
  string app_id = 2;
  map<string, FlagValue> flags = 3;
  int64 computed_at = 4;
}

© 2026 Shell Technology. All rights reserved.