Custom User Attributes
User attributes are properties that describe your users. Use them to personalize messages, create segments, and analyze your user base.
What are User Attributes?
Attributes are key-value pairs attached to user profiles:
{
"userId": "user_123",
"email": "john@example.com",
"attributes": {
"firstName": "John",
"lastName": "Doe",
"plan": "premium",
"signupDate": "2024-01-15",
"totalPurchases": 5,
"lastLoginDate": "2024-01-20",
"preferences": {
"newsletter": true,
"notifications": "email"
}
}
}
Setting Attributes
Via SDK (Client-Side)
import JoryioSDK from '@joryio/web-sdk';
const joryio = new JoryioSDK({ sdkKey: 'jry_sdk_web_...' });
// Identify a user
joryio.identify('user_123');
// Set attributes separately
joryio.setAttributes({
email: 'john@example.com',
firstName: 'John',
lastName: 'Doe',
plan: 'premium',
signupDate: '2024-01-15'
});
// Update attributes later
joryio.setAttributes({
plan: 'enterprise',
lastUpgrade: new Date().toISOString()
});
Via API (Server-Side)
// Create or update user with attributes
fetch('https://api-eu1.joryio.com/users', {
method: 'POST',
headers: {
'Authorization': 'Bearer jry_live_your_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
userId: 'user_123',
email: 'john@example.com',
attributes: {
firstName: 'John',
plan: 'premium',
totalOrders: 10,
lifetime Value: 1250.00
}
})
});
Standard vs Custom Attributes
Standard Attributes
Reserved by Joryio (stored at root level):
userId- Unique user identifieremail- Email addressphone- Phone numberexternalId- External system IDcreatedAt- User creation timestampupdatedAt- Last update timestamp
Custom Attributes
Any other properties you define (stored in attributes object):
firstName,lastName,nameplan,subscription Tiercompany,industry,jobTitletotalPurchases,lifetimeValuepreferences,settings- And anything else you need!
Data Types
Attributes support multiple data types:
String
{
firstName: "John",
plan: "premium",
country: "US"
}
Number
{
age: 30,
totalPurchases: 15,
lifetimeValue: 1250.50
}
Boolean
{
emailVerified: true,
newsletter: false,
isPremium: true
}
Date/Timestamp
{
signupDate: "2024-01-15",
lastLoginDate: "2024-01-20T10:30:00Z",
trialEndsAt: "2024-02-15T23:59:59Z"
}
For dates, use ISO 8601 format: YYYY-MM-DDTHH:mm:ssZ
Array
{
tags: ["vip", "early-adopter"],
interests: ["technology", "sports", "music"],
purchasedProducts: ["product_1", "product_2"]
}
Object/Nested
{
preferences: {
theme: "dark",
language: "en",
notifications: {
email: true,
sms: false,
push: true
}
},
address: {
street: "123 Main St",
city: "San Francisco",
state: "CA",
zip: "94102"
}
}
Common Attribute Examples
E-commerce
joryio.setAttributes({
// Account info
accountType: "premium",
memberSince: "2024-01-15",
// Purchase history
totalOrders: 15,
lastOrderDate: "2024-01-20",
lifetimeValue: 2500.00,
avgOrderValue: 166.67,
// Preferences
favoriteCategory: "electronics",
preferredShipping: "express",
// Engagement
cartAbandoned: false,
wishlistItems: 5,
reviewsWritten: 3
});
SaaS
joryio.setAttributes({
// Subscription
plan: "pro",
billingCycle: "monthly",
subscriptionStatus: "active",
trialEndsAt: "2024-02-15T23:59:59Z",
mrr: 99,
// Usage
loginCount: 45,
lastLoginDate: "2024-01-20",
featuresUsed: ["export", "api", "integrations"],
apiCallsThisMonth: 10500,
storageUsedGB: 15.5,
// Team
teamSize: 8,
role: "admin",
companyName: "Acme Corp"
});
Media/Content
joryio.setAttributes({
// Subscription
subscriptionTier: "premium",
contentAccessLevel: "unlimited",
// Engagement
articlesRead: 125,
videosWatched: 45,
podcastsListened: 30,
favoriteTopics: ["technology", "business"],
// Behavior
avgSessionDuration: 25.5,
lastVisit: "2024-01-20T14:30:00Z",
deviceType: "mobile"
});
Updating Attributes
Merge vs Replace
Merge (default) - Updates specified fields:
// Initial attributes
{
firstName: "John",
plan: "free",
country: "US"
}
// Update attributes (merges with existing)
joryio.setAttributes({
plan: "premium"
});
// Result (plan updated, others preserved)
{
firstName: "John",
plan: "premium", // Updated
country: "US" // Preserved
}
Calling setAttributes() merges new attributes with existing ones. Existing attributes not mentioned in the call are preserved.
Increment/Decrement
For counters, increment instead of getting current value:
// Bad: Race condition possible
const current = await getUserAttribute('loginCount');
joryio.setAttributes({ loginCount: current + 1 });
// Good: Atomic increment
joryio.incrementAttribute('loginCount', 1);
// Decrement
joryio.incrementAttribute('creditsRemaining', -10);
Array Attributes
You can store and manipulate arrays as attribute values:
// Set array attribute
joryio.setAttributes({
tags: ['vip', 'early-adopter'],
interests: ['technology', 'sports']
});
// Add to array (only adds if value doesn't already exist)
joryio.addToArray('tags', 'premium');
// Result: ['vip', 'early-adopter', 'premium']
// Add duplicate (no-op, prevents duplicates)
joryio.addToArray('tags', 'vip');
// Result: ['vip', 'early-adopter', 'premium'] (unchanged)
// Remove from array
joryio.removeFromArray('tags', 'early-adopter');
// Result: ['vip', 'premium']
Method Details:
| Method | Description |
|---|---|
addToArray(key, value) | Adds value to array if it doesn't already exist (prevents duplicates). Creates new array if attribute doesn't exist. |
removeFromArray(key, value) | Removes all instances of value from array. Warns if attribute isn't an array. |
Array attributes are perfect for:
- User tags:
['vip', 'trial', 'beta-tester'] - Interests:
['sports', 'technology', 'fashion'] - Purchased products:
['prod_123', 'prod_456'] - Feature flags:
['feature-a', 'feature-b'] - Roles:
['admin', 'editor']
Using Attributes for Segmentation
Create segments based on attributes:
Simple Attribute Filter
plan equals "premium"
Numeric Comparison
lifetimeValue >= 1000
totalPurchases > 5
age between 25 and 45
Date-Based Filters
signupDate is within last 30 days
trialEndsAt is within next 7 days
lastLoginDate is more than 14 days ago
String Matching
email contains "@company.com"
country equals "US"
firstName exists
plan is not "free"
Array Filters
tags contains "vip"
interests contains any of ["technology", "business"]
Personalizing Messages
Use attributes in campaign content:
Email Personalization
Hi {{firstName}},
Your {{plan}} plan includes these benefits:
...
{{#if trialEndsAt}}
Your trial ends on {{trialEndsAt}}. Upgrade now!
{{/if}}
Dynamic Content
{{#if plan == "free"}}
<p>Upgrade to Premium for more features!</p>
{{else}}
<p>Thanks for being a {{plan}} member!</p>
{{/if}}
Conditional Blocks
{{#if totalPurchases > 10}}
<div class="vip-offer">
As a valued customer, here's an exclusive offer...
</div>
{{/if}}
Attribute Best Practices
1. Use Descriptive Names
Good:
{
subscriptionTier: "premium",
lifetimeValueUSD: 1250.00,
emailVerified: true
}
Bad:
{
sub: "p",
ltv: 1250,
verified: 1
}
2. Be Consistent
Use the same naming convention:
Good (camelCase):
{
firstName: "John",
lastName: "Doe",
signupDate: "2024-01-15"
}
Bad (mixed):
{
first_name: "John",
LastName: "Doe",
"signup-date": "2024-01-15"
}
3. Use Appropriate Types
Good:
{
age: 30, // Number
isPremium: true, // Boolean
signupDate: "2024-01-15", // ISO Date String
tags: ["vip", "beta"] // Array
}
Bad:
{
age: "30", // String instead of number
isPremium: "true", // String instead of boolean
signupDate: 1705276800, // Timestamp instead of ISO
tags: "vip,beta" // String instead of array
}
4. Don't Store Sensitive Data
Never store:
- Passwords or password hashes
- Full credit card numbers
- Social security numbers
- Banking details
- Health information
Safe to store:
- Last 4 digits of card
- Payment method type ("visa", "mastercard")
- Encrypted/hashed tokens
- Public profile information
5. Keep Attribute Names Short
Good: plan, ltv, mrr
Bad: currentSubscriptionPlanTierLevel
Reserved Attribute Names
Avoid using these reserved names:
userId,user_id,idemailphonecreatedAt,created_atupdatedAt,updated_at$app_id,$platform(prefixed with $)
Bulk Updates
Update attributes for multiple users:
// API: Bulk update - POST /users accepts a bare array body (max 1000)
POST /users
[
{
"userId": "user_1",
"attributes": { "plan": "premium" }
},
{
"userId": "user_2",
"attributes": { "plan": "enterprise" }
}
]
Attribute Limits
| Limit | Value |
|---|---|
| Max attributes per user | 200 |
| Max attribute name length | 100 characters |
| Max string value length | 10,000 characters |
| Max array length | 100 items |
| Max nesting depth | 5 levels |
Troubleshooting
Attributes Not Showing
- Check attribute name spelling
- Verify user is identified
- Enable debug mode to see API calls
- Check for API errors in response
Attribute Not Updating
- Ensure using merge (not replace)
- Check data type matches
- Verify attribute name is correct
- Check rate limits
Segmentation Not Working
- Verify attribute exists for users
- Check attribute data type
- Test filter logic with known users
- Ensure attribute values match filter exactly