Skip to main content
Attributes store the current state of users, devices, and events. Understanding when to use attributes vs events is critical for building an effective data model that supports both analytics and personalization.

What are Attributes?

Attributes are facts that describe who a user is, what device they’re on, or what happened in an event. Think of them as the labels on a record — name, email, plan type, device model, event revenue. While events are verbs (actions), attributes are adjectives (descriptions).

The Simple Definition

If you can describe it as “This user IS X” or “This event HAS property Y,” it’s an attribute. Examples:
  • “Sarah’s email is sarah@example.com” → User attribute: email
  • “John’s plan type is Enterprise” → User attribute: plan_type
  • “This device is an iPhone 15 Pro on iOS 17” → Device attribute: model, os_version
  • “This purchase had revenue of $89.99” → Event property: revenue

Why Attributes Matter

Attributes are essential because they enable:
  1. Personalization: “Hi , your trial expires in days”
  2. Targeting: Send campaigns only to users matching specific criteria
  3. Context: Understand who’s behind each event (“This purchase was from an Enterprise customer”)
  4. Enrichment: Connect user data from your CRM, support system, or other tools
  5. Current state queries: “How many Pro plan users do we have right now?”

The Key Difference: State vs Action

Attributes store current state (one value that changes over time):
  • User’s current email: sarah@example.com → changes if they update it
  • User’s current plan: pro → changes when they upgrade to enterprise
  • Last login date: 2026-05-04 → updates every time they log in
Events record actions (immutable history):
  • Event: Updated Email at 2026-01-15 → never changes, always part of history
  • Event: Upgraded Plan at 2026-03-20 → permanent record of this action
  • Event: Logged In at 2026-05-04 10:30:15 → specific moment captured
The rule: Use attributes when you need to know “what IS true about this user right now.” Use events when you need to know “what DID this user do.”

Real-World Example

Sarah’s user attributes:
{
  email: "sarah@example.com",          // Current email
  name: "Sarah Johnson",               // Current name
  plan_type: "enterprise",             // Current plan (was 'pro' before)
  company: "Acme Corp",                 // Current company
  signup_date: "2026-01-15",           // Doesn't change
  total_purchases: 12,                  // Increments with each purchase
  lifetime_value: 1247.88,              // Calculated from all purchases
  last_login_at: "2026-05-04T10:30:15Z" // Updates each login
}
Sarah’s event history:
[
  { event: "Signed Up",        timestamp: "2026-01-15T09:00:00Z" },
  { event: "Purchased",        timestamp: "2026-01-20T14:30:00Z", amount: 89.99 },
  { event: "Upgraded Plan",    timestamp: "2026-03-20T11:15:00Z", from: "pro", to: "enterprise" },
  { event: "Purchased",        timestamp: "2026-05-01T16:45:00Z", amount: 149.99 },
  { event: "Logged In",        timestamp: "2026-05-04T10:30:15Z" }
  // ... 100 more events
]
Notice:
  • Attributes show her current state: She’s enterprise now (not pro)
  • Events show her complete history: We can see she upgraded on March 20th
  • Together they give the full picture: Who she is + What she’s done

Attributes vs Events: The Data Modeling Decision

Events = Actions (verbs) → user.track('Upgraded Plan')
Attributes = State (adjectives) → user.plan_type = 'enterprise'

The Rule

Use attributes when:
  • You need current state: “What plan are they on right now?”
  • You need to personalize: “Hi , your trial ends
  • It’s a fact about the user: Demographics, account info, preferences
Use events when:
  • You need to trigger workflows: “When user upgrades → send thank-you email”
  • You need analytics: “How many users upgraded this month?”
  • You need history: “When did they last log in?”
  • It’s an action: Clicked, viewed, purchased, upgraded

Common Mistake: Tracking State as Events

❌ Wrong:
// Don't do this
track('Email Changed', { new_email: 'user@new-domain.com' });
track('Became Enterprise Customer');
track('Credit Card Updated');
Why it’s wrong: These are state updates, not user actions. You’re creating noise in your event stream. ✅ Right:
// Update the attribute
setProfileAttributes({
  email: 'user@new-domain.com',
  plan_type: 'enterprise',
  payment_method_updated_at: '2026-05-04T10:00:00Z'
});

// Track the action that caused the change (if relevant)
track('Upgraded Plan', {
  from_plan: 'pro',
  to_plan: 'enterprise',
  mrr_change: 200
});
Why it’s right: Attribute stores current state for queries and personalization. Event records the action for analytics and workflow triggers.

Three Types of Attributes

1. User Attributes

User attributes are facts stored on the user profile, identified by user_id. They come in two kinds: system-defined fields (always present, set by the platform) and traits (custom key-value data you send via identify() or setProfileAttributes()).

System-Defined Fields

These fields exist on every user profile automatically:
FieldDescription
idInternal Sonyflake BIGINT primary key
user_idYour application’s identifier for this user
anonymous_idThe anonymous ID before identification
nameFrom traits
emailFrom traits
phoneFrom traits
timezoneIANA timezone, e.g. America/New_York
languageISO 639-1 code, e.g. en
email_opt_outChannel opt-out status (1=not opted out, 2=opted out, 3=unknown)
sms_opt_outSame 3-state model
whatsapp_opt_outSame 3-state model
rcs_opt_outSame 3-state model
push_opt_outSame 3-state model
*_opt_out_atTimestamp when each opt-out occurred
first_seen_atTimestamp of first identify call
last_seen_atTimestamp of most recent identify call
last_activity_atTimestamp of most recent event
is_deletedSet to true by User Deleted lifecycle event
is_suppressedSet to true by User Suppressed lifecycle event
deleted_at / suppressed_atWhen those states were applied
first_campaign_source/medium/name/term/contentFirst-touch UTM attribution
first_referrerReferring URL from first visit
signup_platformPlatform at time of first identify
creation_sourceHow the profile was created
external_idOptional external system identifier
customer_created_atUser’s creation time in your own system
created_at / updated_at / processed_atSystem timestamps

Traits (Custom Attributes)

Anything you pass in traits via identify() or via setProfileAttributes() is stored as a custom trait and merged with existing data:
// On identify (signup/login)
Zixflow.instance.identify(
  userId: 'user_12345',
  traits: {
    'plan_type': 'enterprise',
    'company': 'Acme Corp',
    'mrr': 499,
    'trial_ends_at': '2026-06-01T00:00:00Z',
  },
);

// Update anytime — merges with existing traits
Zixflow.instance.setProfileAttributes({
  'last_upgraded_at': '2026-05-04T10:00:00Z',
  'feature_flags': ['beta_reports', 'new_dashboard'],
});
Traits are schema-less — you define the keys. All trait keys are automatically catalogued for use in queries and targeting.

2. Device Attributes

Device attributes are facts stored on a device record, identified by device_id (derived from the push token). Like user attributes, they split into system-defined fields and custom traits.

System-Defined Fields

FieldDescription
device_idInternal device ID (derived from token hash)
tokenPush token (FCM/APNs)
platformios or android
manufacturere.g. Apple, Samsung
modele.g. iPhone 15 Pro, Pixel 8
os_namee.g. iOS, Android
os_versione.g. 17.4.1, 14
app_versionYour app’s version string
sdk_versionZixflow SDK version
screen_width / screen_height / screen_densityDisplay info
localee.g. en-US
timezoneIANA timezone
carrierMobile carrier name
activefalse after Device Deleted lifecycle event
push_enabledWhether the device can receive push
last_used_atLast time SDK sent an event from this device
created_at / updated_at / processed_atSystem timestamps

Traits (Custom Attributes)

Custom key-value data you attach to a specific device:
Zixflow.instance.setDeviceAttributes({
  'notification_categories': ['promotions', 'transactional'],
  'dark_mode_enabled': true,
  'preferred_locale': 'en-US',
});
Use cases:
  • Target specific app versions: “Send ‘Update Available’ push to devices on v1.x”
  • Platform-specific campaigns: “iOS-only feature announcement”
  • Notification category filtering

3. Event Properties

Event properties are the custom key-value pairs attached to individual events. Unlike user or device attributes (which describe current state), event properties are immutable — they record the details of a specific moment in time.

Anatomy of an Event with Properties

{
  "event_name": "Order Completed",
  "event_type": 1,
  "user_id": "user_12345",
  "anonymous_id": null,
  "timestamp": "2026-05-04T14:32:15Z",
  "properties": {
    "order_id": "ord_789",
    "revenue": 129.99,
    "currency": "USD",
    "product_name": "Nike Air Max 90",
    "payment_method": "credit_card"
  },
  "context": {
    "platform": "ios",
    "app": { "version": "2.1.0" },
    "os": { "name": "iOS", "version": "17.4.1" },
    "device": { "model": "iPhone 15 Pro" },
    "locale": "en-US",
    "timezone": "America/New_York"
  }
}

System-Defined Event Fields

Every event has these fields set automatically:
FieldDescription
event_nameThe name you pass to track() / screen() / page()
event_typeInteger: 1=track, 2=screen, 3=page, 4=identify
user_idSet if user is identified
anonymous_idSet if user is anonymous
timestampWhen the event occurred (millisecond precision)
contextPlatform, device, OS, app version, locale, timezone — captured by SDK
workspace_idYour workspace

Custom Properties

The properties object is fully custom — you define the keys and values:
Zixflow.instance.track('Purchased', properties: {
  'order_id': 'ord_123',
  'revenue': 89.99,
  'currency': 'USD',
  'product_name': 'Red Sneakers',
  'payment_method': 'credit_card',
});
All property keys are automatically catalogued for use in queries and campaign conditions. Note: Event properties are stored in dedicated event tables (profile_events, device_logs, profile_identify_events) with separate retention policies — not in the user profile.

Automatic Attribute Discovery

Zixflow automatically learns what attributes exist based on the data you send - no need to define a schema upfront.

How It Works

You track an event with properties:
Zixflow.track('Purchased', {
  order_id: 'ord_123',
  product_name: 'Enterprise Plan',
  revenue: 499.00,
  currency: 'USD',
  payment_method: 'credit_card'
});
Zixflow automatically:
  1. Discovers all the property names (order_id, product_name, revenue, etc.)
  2. Figures out their data types (order_id is text, revenue is a number)
  3. Makes them available for querying and filtering
Now you can:
  • Segment: “Users who purchased where product_name = ‘Enterprise Plan’”
  • Analyze: “Top 10 products by revenue”
  • Filter: “Show events where payment_method = ‘credit_card’”
Benefits:No schema setup — Just start tracking, Zixflow learns automatically
Self-documenting — See what attributes you’re actually using
Type checking — Zixflow warns if data types don’t match
Easy filtering — All attributes available for querying

Reserved Attribute Names

These attribute names are reserved for system use. Don’t use them as custom attributes: User-level:
  • id, workspace_id, user_id, email, anonymous_id
  • created_at, updated_at, last_seen_at
Device-level:
  • device_id, device_token, platform, last_seen_at
If you try to set a reserved attribute:
  • System logs a warning
  • Value is ignored (not written)
  • Original system value preserved

Next: Understand how events flow through Zixflow → The Data Journey