Data Structure
This guide outlines how NotifyVisitors structures and manages events and user profiles, the core building blocks of your analytics data model. You’ll learn about the different attribute types (system, default, and custom), how they’re captured, and best practices for designing a scalable schema that remains consistent as your product evolves.
In data warehouse parlance, events represent your fact tables — the actions users take — while user profiles serve as dimension tables, providing context about who those users are. Together, they form a reliable foundation for accurate tracking, analysis, and personalization across your product.
User attributes
A user profile represents a collection of properties associated with an individual user. It functions as a key–value store that maintains the current state of that user. User profiles are connected to events through a distinct id (nv_uid): event.nv_uid = user.nv_uid.
In NotifyVisitors, user profiles bring together system-generated identifiers, demographic details, and custom-defined attributes. At a minimum, each profile includes a distinct identifier (such as user_id, email, or mobile).
How User Profiles Are Stored
Behind the scenes, NotifyVisitors organizes user data in a structured table. Each row represents a single user profile, while columns represent user attributes (e.g., Name, Email, Department). These attributes can be updated as new information becomes available.
| nv_uid | Name | Department | |
|---|---|---|---|
| 101 | David | [email protected] | Engineering |
| 202 | Emma | [email protected] | Product |
| 303 | Liam | [email protected] | Design |
Concepts
- System attributes:
Core identifiers and reserved fields such asnv_uid,name/first_name/last_name,email,mobile, and first/last seen timestamps. - Default attributes:
Automatically derived and maintained metadata, includingCity,Country,Region Code,Time Zone, etc., which are updated whenever a profile changes. - Custom attributes:
Business-specific traits that extend profile context — such asplan,account_tier, orLTV— allowing deeper segmentation and personalization beyond system and default fields.
Event attributes
Events represent actions that occur within your product. Each event includes details describing that action — such as time, device, browser, and location. At minimum, every event should include:
- Event name
- Timestamp
- Distinct identifier (such as
user_id,email, ormobile)
Events can also be joined with user profiles to provide additional context and enrichment.
Concepts
System attributes:
These are automatically captured when an event occurs. They include information like device, operating system, IP-based location, language, platform, timezone, UTM parameters, and page or app context. Coverage varies by platform (Web, Android, iOS).
Custom attributes:
These are business-defined details that you add to specific events — for example, order_id, plan_tier, or quantity.
- If you think in database terms: events are like tables, and attributes are like columns.
- If you’re familiar with Google Analytics: events are like hits, and attributes are like dimensions.
Examples
- A
Page Viewedevent might include an attribute calledPage URL, representing the page that was viewed. - A
Signed Upevent could includeSignup Type, showing whether the signup was organic or referral. - A
Song Playedevent might includeSong Name, containing the name of the song. - An
Order Completedevent could includeItems, which lists objects describing each item (name, category, price, etc.).
Use Cases
You can filter, group, and analyze events using their attributes to answer questions such as:
- Which pages do users visit before the pricing page?
- How many sign-ups came from organic vs. referral sources?
- Which song is played most often?
- How many orders included shoes, and what was the total amount spent on them last month?
Best Practices (Keep Events Action-Oriented)
Define events around user actions rather than being too broad or too narrow. Use event properties to add context, instead of creating multiple events that describe similar actions.
Examples:
- To analyze page navigation:
Instead of separate events likeHome Page ViewedandPricing Page Viewed, track a singlePage Viewedevent with aPage Nameproperty (e.g., “/home”, “/pricing”). - To track items added to a cart:
Instead ofAdd Shirt to Cart,Add Hoodie to Cart, andAdd Socks to Cart, use oneAdd to Cartevent with anItemproperty (e.g., “Shirt”, “Hoodie”, “Socks”). - To track button interactions on a single screen:
Instead ofBlue Button ClickedandCheckout Button Clicked, record oneButton Clickedevent withColor(“Blue”) andButton Name(“Checkout”). - To capture different user actions:
Instead of one genericButton Clickedevent for everything (withButton Name= “Play”, “Profile”, “X”), create distinct events likeSong Played,Profile Updated, andLogout, each with properties specific to their context.
Critical Guidelines
We don’t impose a limit on the total number of events you can send to NotifyVisitors, but it will be considered in your pricing. However, we have a soft limit of 1000 distinct event names. If you send more event names, we’ll still process them, but those event names won’t be indexed and won’t appear in our autocomplete menus.
- Each event can have up to 255 properties.
- Event names are case-sensitive and must be < 255 characters.
sign_up_completedandSign_Up_Completedare different events. - Custom event attribute names are also case-sensitive and must be < 50 characters.
- String attribute values must be < 512 characters.
- Per-event attribute fan-out: on a single event, you can include ≤ 25 attributes per data type (e.g., at most 25 string-type attributes, 25 number-type attributes, etc.).
- Type locking: the first value seen for an attribute establishes its type. If you later send a different type (e.g.,
quantity: "five"after sendingquantity: 5), those mismatched values won’t flow to your NotifyVisitors dashboard.
Tip: In such cases, introduce a new attribute likequantity_v2with the correct type; do not change the original type.
Naming conventions that survive the long run
Because keys are case-sensitive and hard to rename, choose a durable scheme: Use snake_case for event names and property names.
It’s resilient in code, storage, and BI tools.
-
Use object_verb for events:
page_viewed,song_played,order_completed. -
Keep names short but specific: prefer
signup_typeovertype. -
If you need friendlier labels for the UI, NotifyVisitors allows you to set display names for analytics reports without changing the underlying keys.
Property data types
Selecting the appropriate data type for properties is fundamental to effective data analysis. Here is a detailed breakdown of the property data types, their characteristics, and how they behave in an analytics context.
String
A String is an alphanumeric value and serves as the default data type for any property value that doesn't conform to other types.
- Description: Represents text-based data.
- Examples: User ID = “Bruno_21”, Status = “Active”.
- Limits: Each string is limited to 255 bytes. The number of characters this accommodates depends on the character encoding used.
Numeric
A Numeric property can be either an integer or a decimal. This type is essential for any quantitative analysis.
- Description: Represents numerical data.
- Examples: Cost = 15.00, Quantity = 5
- Functionality: Enables the use of mathematical operators such as sum, median, and percentile in reports.
Boolean
A Boolean property represents a true or false value.
- Description: Represents binary states.
- Examples: true, false.
- Typecasting: Non-boolean values are automatically converted. For instance, 0, null, undefined, and empty strings are treated as false, while any non-zero or non-empty values are treated as true.
Date
A Date stores a calendar date only (no time, no timezone).
- Description: Day on the calendar. Use ISO-8601 date-only format.
- Examples:
renewal_date = "2025-12-01",dob = "1998-07-14". - Limits: Does not include time or timezone. Format
YYYY-MM-DD. Keep consistent across sources.
Timestamp
A Timestamp stores an exact point in time and includes timezone.
- Description: The Moment an action occurred. Use ISO-8601 with timezone (prefer UTC
Z). - Examples:
timestamp = "2025-09-10T12:34:56Z",client_timestamp = "2025-09-10T18:04:56+05:30". - Limits: Must include timezone (
Zor±HH:MM). Avoid locale strings (e.g.,"09/10/25 12:34 PM").
Object
An Object is a key–value map grouping related attributes.
- Description: Structured container (e.g., address, device capabilities).
- Example:
{
"shipping_address": {
"street": "42 MG Road",
"city": "Mumbai",
"zip": "400001",
"country_code": "IN"
}
}
- Limits: Less than 50 characters. Counts as one Object-type attribute toward per-type limits; inner strings still follow string limits.
Nested Object
A Nested Object is an array of multiple objects.
- Description: Use when a group contains another group (e.g., payment → card details).
- Example:
{ "payment": { "method": "credit_card", "amount": 119.93, "card": { "brand": "Visa", "last4": "4242", "exp_month": 12, "exp_year": 2030 } } } - Limits: Event property: 8 KB; User profile property: 255 characters; up to 25 keys per nested object with a max nesting depth of 3.
Array
An Array is an ordered list of values (e.g., strings or numbers).
-
Description: Use for tags, categories, or line items.
-
Example:
{ "tags": ["summer-sale", "new-arrival", "bestseller"], "applied_discounts": ["SUMMER20", "FIRST10"] } -
Limits
- Each item obeys data-type limits: e.g., strings ≤ 255 characters.
Array of Objects
An Array of Objects is a list where each element is an object containing key-value pairs.
Use this format when you need to store multiple structured records, such as product items, transactions, or users.
- Description: Use when each item requires multiple attributes (e.g.,
sku,name,price,qty). - Example:
{
"items": [
{ "sku": "SN-001", "name": "AirStride Pro", "price": 99.95, "qty": 1 },
{ "sku": "SK-042", "name": "Cotton Ankle Socks", "price": 9.99, "qty": 2 }
]
}
-
Limits
- Each item obeys data-type limits: e.g., strings ≤ 255 characters.
- An array of objects can’t be nested. For example:
Not allowed: object nesting inside array of objects
{ "items": [ { "sku": "SN-001", "name": "AirStride Pro", "price": 99.95, "qty": 1, "tags": [ { "tag": "summer-sale" }, { "tag": "clearance" } ] }, { "sku": "SK-042", "name": "Cotton Ankle Socks", "price": 9.99, "qty": 2, "tags": [ { "tag": "cotton" }, { "tag": "bundle" } ] } ] }Allowed option A: make inner arrays strings
{ "items": [ { "sku": "SN-001", "name": "AirStride Pro", "price": 99.95, "qty": 1, "tags": ["summer-sale", "clearance"] }, { "sku": "SK-042", "name": "Cotton Ankle Socks", "price": 9.99, "qty": 2, "tags": ["cotton", "bundle"] } ] }Allowed option B: split into a separate array of objects (no inner arrays)
{ "items": [ { "sku": "SN-001", "name": "AirStride Pro", "price": 99.95, "qty": 1 }, { "sku": "SK-042", "name": "Cotton Ankle Socks", "price": 9.99, "qty": 2 } ], "item_tags": [ { "sku": "SN-001", "tag": "summer-sale" }, { "sku": "SN-001", "tag": "clearance" }, { "sku": "SK-042", "tag": "cotton" }, { "sku": "SK-042", "tag": "bundle" } ] }
Updated about 18 hours ago
