User Engagement Score
Composite metric measuring how actively and meaningfully users interact with your product
Overview
User Engagement Score is a composite metric that quantifies how actively and meaningfully users interact with your product. It combines multiple behavioral indicators including activity frequency, feature usage depth, session duration, and recency to provide a holistic view of user engagement levels.
This metric is essential for product-led growth strategies, helping teams identify highly engaged users for expansion opportunities, detect at-risk users for retention efforts, and optimize product experiences to drive deeper engagement across the user base.
Engagement Score = Weighted Average of (Activity Score + Frequency Score + Depth Score + Recency Score)
Activity Level
Total actions per period
Volume indicatorUsage Frequency
Days active per period
Consistency measureFeature Depth
Breadth of features used
Product explorationRecent Activity
Time since last activity
Current engagement80-100
Highly Engaged
Power Users60-79
Moderately Engaged
Regular Users20-59
Low Engagement
Casual Users0-19
At Risk
Dormant UsersScoring Approach
Weight each component based on your product type and business model. B2B SaaS might emphasize depth and frequency, while consumer apps might prioritize activity volume and recency.
Why It Matters
- Product Development: Guides feature prioritization and product roadmap decisions
- User Segmentation: Enables targeted campaigns and personalized experiences
- Retention Prediction: Identifies users at risk of churning early
- Expansion Opportunities: Finds high-engagement users ready for upselling
- Product-Market Fit: Indicates how well the product resonates with users
- Customer Success: Enables proactive outreach and support
How to Measure It
Calculate user engagement scores by analyzing behavioral data across multiple dimensions, creating weighted scores for each component, and combining them into a comprehensive engagement metric.
Basic Engagement Score Calculation
-- Calculate comprehensive user engagement scores
WITH user_activity AS (
SELECT
user_id,
DATE_TRUNC('month', event_timestamp) as activity_month,
COUNT(*) as total_events,
COUNT(DISTINCT DATE(event_timestamp)) as active_days,
COUNT(DISTINCT session_id) as total_sessions,
COUNT(DISTINCT event_type) as unique_event_types,
COUNT(DISTINCT feature_name) as features_used,
AVG(session_duration_minutes) as avg_session_duration,
MAX(event_timestamp) as last_activity_date,
MIN(event_timestamp) as first_activity_date,
COUNT(CASE WHEN event_type = 'core_action' THEN 1 END) as core_actions,
COUNT(CASE WHEN event_type = 'advanced_feature' THEN 1 END) as advanced_actions
FROM user_events
WHERE event_timestamp >= CURRENT_DATE - INTERVAL '30 days'
AND user_id IS NOT NULL
GROUP BY user_id, DATE_TRUNC('month', event_timestamp)
),
user_profiles AS (
SELECT
u.user_id,
u.signup_date,
u.user_tier,
u.industry,
EXTRACT(days FROM AGE(CURRENT_DATE, u.signup_date)) as account_age_days
FROM users u
WHERE u.signup_date <= CURRENT_DATE - INTERVAL '7 days' -- Exclude very new users
),
engagement_scores AS (
SELECT
ua.user_id,
ua.activity_month,
ua.total_events,
ua.active_days,
ua.total_sessions,
ua.features_used,
ua.avg_session_duration,
ua.last_activity_date,
EXTRACT(days FROM AGE(CURRENT_DATE, ua.last_activity_date)) as days_since_last_activity,
-- Activity Score (0-25): Based on total events normalized
CASE
WHEN ua.total_events >= 100 THEN 25
WHEN ua.total_events >= 50 THEN 20
WHEN ua.total_events >= 20 THEN 15
WHEN ua.total_events >= 10 THEN 10
WHEN ua.total_events >= 5 THEN 5
ELSE 0
END as activity_score,
-- Frequency Score (0-25): Based on active days in month
CASE
WHEN ua.active_days >= 20 THEN 25
WHEN ua.active_days >= 15 THEN 20
WHEN ua.active_days >= 10 THEN 15
WHEN ua.active_days >= 5 THEN 10
WHEN ua.active_days >= 2 THEN 5
ELSE 0
END as frequency_score,
-- Depth Score (0-25): Based on feature usage breadth
CASE
WHEN ua.features_used >= 10 THEN 25
WHEN ua.features_used >= 8 THEN 20
WHEN ua.features_used >= 6 THEN 15
WHEN ua.features_used >= 4 THEN 10
WHEN ua.features_used >= 2 THEN 5
ELSE 0
END as depth_score,
-- Recency Score (0-25): Based on days since last activity
CASE
WHEN EXTRACT(days FROM AGE(CURRENT_DATE, ua.last_activity_date)) <= 1 THEN 25
WHEN EXTRACT(days FROM AGE(CURRENT_DATE, ua.last_activity_date)) <= 3 THEN 20
WHEN EXTRACT(days FROM AGE(CURRENT_DATE, ua.last_activity_date)) <= 7 THEN 15
WHEN EXTRACT(days FROM AGE(CURRENT_DATE, ua.last_activity_date)) <= 14 THEN 10
WHEN EXTRACT(days FROM AGE(CURRENT_DATE, ua.last_activity_date)) <= 30 THEN 5
ELSE 0
END as recency_score,
up.user_tier,
up.account_age_days
FROM user_activity ua
JOIN user_profiles up ON ua.user_id = up.user_id
),
final_scores AS (
SELECT
*,
activity_score + frequency_score + depth_score + recency_score as total_engagement_score,
CASE
WHEN activity_score + frequency_score + depth_score + recency_score >= 80 THEN 'High'
WHEN activity_score + frequency_score + depth_score + recency_score >= 60 THEN 'Medium'
WHEN activity_score + frequency_score + depth_score + recency_score >= 20 THEN 'Low'
ELSE 'At Risk'
END as engagement_tier,
NTILE(10) OVER (ORDER BY activity_score + frequency_score + depth_score + recency_score DESC) as engagement_decile
FROM engagement_scores
)
SELECT
user_id,
activity_month,
total_events,
active_days,
features_used,
days_since_last_activity,
activity_score,
frequency_score,
depth_score,
recency_score,
total_engagement_score,
engagement_tier,
engagement_decile,
user_tier,
account_age_days,
RANK() OVER (PARTITION BY engagement_tier ORDER BY total_engagement_score DESC) as tier_rank
FROM final_scores
ORDER BY total_engagement_score DESC;
Engagement Trend Analysis
-- Analyze engagement score trends and patterns over time
WITH monthly_scores AS (
SELECT
user_id,
activity_month,
total_engagement_score,
engagement_tier,
LAG(total_engagement_score) OVER (PARTITION BY user_id ORDER BY activity_month) as prev_month_score,
LAG(engagement_tier) OVER (PARTITION BY user_id ORDER BY activity_month) as prev_month_tier,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY activity_month) as month_sequence
FROM final_scores
WHERE activity_month >= CURRENT_DATE - INTERVAL '6 months'
),
engagement_trends AS (
SELECT
user_id,
COUNT(*) as months_tracked,
MIN(total_engagement_score) as min_score,
MAX(total_engagement_score) as max_score,
ROUND(AVG(total_engagement_score), 1) as avg_score,
ROUND(STDDEV(total_engagement_score), 1) as score_volatility,
MAX(total_engagement_score) - MIN(total_engagement_score) as score_range,
ARRAY_AGG(engagement_tier ORDER BY activity_month) as tier_journey,
COUNT(CASE WHEN total_engagement_score > prev_month_score THEN 1 END) as improving_months,
COUNT(CASE WHEN total_engagement_score < prev_month_score THEN 1 END) as declining_months,
CASE
WHEN COUNT(CASE WHEN total_engagement_score > prev_month_score THEN 1 END) >
COUNT(CASE WHEN total_engagement_score < prev_month_score THEN 1 END) THEN 'Improving'
WHEN COUNT(CASE WHEN total_engagement_score > prev_month_score THEN 1 END) <
COUNT(CASE WHEN total_engagement_score < prev_month_score THEN 1 END) THEN 'Declining'
ELSE 'Stable'
END as trend_direction
FROM monthly_scores
WHERE prev_month_score IS NOT NULL
GROUP BY user_id
HAVING COUNT(*) >= 3 -- Users with at least 3 months of data
),
cohort_engagement AS (
SELECT
DATE_TRUNC('month', up.signup_date) as signup_cohort,
EXTRACT(months FROM AGE(fs.activity_month, up.signup_date)) as months_since_signup,
COUNT(*) as users_in_cohort,
ROUND(AVG(fs.total_engagement_score), 1) as avg_engagement_score,
COUNT(CASE WHEN fs.engagement_tier = 'High' THEN 1 END) as high_engagement_users,
COUNT(CASE WHEN fs.engagement_tier = 'At Risk' THEN 1 END) as at_risk_users,
ROUND(100.0 * COUNT(CASE WHEN fs.engagement_tier = 'High' THEN 1 END) / COUNT(*), 1) as high_engagement_rate,
ROUND(100.0 * COUNT(CASE WHEN fs.engagement_tier = 'At Risk' THEN 1 END) / COUNT(*), 1) as at_risk_rate
FROM final_scores fs
JOIN user_profiles up ON fs.user_id = up.user_id
WHERE months_since_signup <= 12 -- First year analysis
GROUP BY 1, 2
HAVING COUNT(*) >= 20 -- Minimum cohort size
)
SELECT
signup_cohort,
months_since_signup,
users_in_cohort,
avg_engagement_score,
high_engagement_users,
at_risk_users,
high_engagement_rate,
at_risk_rate,
LAG(avg_engagement_score) OVER (PARTITION BY signup_cohort ORDER BY months_since_signup) as prev_month_avg,
ROUND(avg_engagement_score - LAG(avg_engagement_score) OVER (PARTITION BY signup_cohort ORDER BY months_since_signup), 1) as score_change
FROM cohort_engagement
ORDER BY signup_cohort DESC, months_since_signup ASC;
Feature-Level Engagement Analysis
-- Analyze engagement patterns by feature usage
WITH feature_engagement AS (
SELECT
fs.user_id,
fs.total_engagement_score,
fs.engagement_tier,
ue.feature_name,
COUNT(*) as feature_usage_count,
COUNT(DISTINCT DATE(ue.event_timestamp)) as days_used_feature,
MAX(ue.event_timestamp) as last_feature_use,
MIN(ue.event_timestamp) as first_feature_use,
AVG(ue.session_duration_minutes) as avg_session_duration_with_feature
FROM final_scores fs
JOIN user_events ue ON fs.user_id = ue.user_id
AND DATE_TRUNC('month', ue.event_timestamp) = fs.activity_month
WHERE ue.feature_name IS NOT NULL
GROUP BY 1, 2, 3, 4
),
feature_engagement_correlation AS (
SELECT
feature_name,
COUNT(DISTINCT user_id) as total_users,
COUNT(CASE WHEN engagement_tier = 'High' THEN user_id END) as high_engagement_users,
COUNT(CASE WHEN engagement_tier = 'At Risk' THEN user_id END) as at_risk_users,
ROUND(AVG(total_engagement_score), 1) as avg_user_engagement_score,
ROUND(AVG(feature_usage_count), 1) as avg_feature_usage,
ROUND(100.0 * COUNT(CASE WHEN engagement_tier = 'High' THEN user_id END) / COUNT(DISTINCT user_id), 1) as high_engagement_rate,
CORR(feature_usage_count, total_engagement_score) as usage_score_correlation,
ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY feature_usage_count), 1) as median_usage_count
FROM feature_engagement
GROUP BY feature_name
HAVING COUNT(DISTINCT user_id) >= 50 -- Minimum user threshold
),
engagement_by_feature_adoption AS (
SELECT
user_id,
total_engagement_score,
engagement_tier,
COUNT(DISTINCT feature_name) as total_features_used,
ARRAY_AGG(DISTINCT feature_name ORDER BY feature_usage_count DESC) as top_features,
SUM(feature_usage_count) as total_feature_interactions,
STRING_AGG(
feature_name || ':' || feature_usage_count::text,
', ' ORDER BY feature_usage_count DESC
) as feature_usage_breakdown
FROM feature_engagement
GROUP BY 1, 2, 3
),
feature_adoption_patterns AS (
SELECT
total_features_used,
COUNT(*) as users_count,
ROUND(AVG(total_engagement_score), 1) as avg_engagement_score,
COUNT(CASE WHEN engagement_tier = 'High' THEN 1 END) as high_engagement_users,
ROUND(100.0 * COUNT(CASE WHEN engagement_tier = 'High' THEN 1 END) / COUNT(*), 1) as high_engagement_rate
FROM engagement_by_feature_adoption
GROUP BY total_features_used
ORDER BY total_features_used DESC
)
SELECT
fec.feature_name,
fec.total_users,
fec.high_engagement_users,
fec.high_engagement_rate,
fec.avg_user_engagement_score,
fec.avg_feature_usage,
ROUND(fec.usage_score_correlation, 3) as correlation_with_engagement,
fec.median_usage_count,
RANK() OVER (ORDER BY fec.high_engagement_rate DESC) as feature_engagement_rank,
CASE
WHEN fec.usage_score_correlation >= 0.3 THEN 'Strong Positive'
WHEN fec.usage_score_correlation >= 0.1 THEN 'Moderate Positive'
WHEN fec.usage_score_correlation >= -0.1 THEN 'Neutral'
ELSE 'Negative'
END as engagement_correlation_strength
FROM feature_engagement_correlation fec
ORDER BY fec.high_engagement_rate DESC, fec.usage_score_correlation DESC;
Measurement Best Practices
Customize scoring weights based on your product and business model, track scores over time to identify trends, and segment users by engagement levels for targeted interventions and product improvements.
Best Practices
1. Score Definition
- Define meaningful actions that indicate true engagement
- Weight components based on business importance
- Account for different user types and use cases
- Normalize scores for fair comparison across segments
2. Data Collection
- Track granular user actions and feature interactions
- Capture session context and user intent
- Ensure consistent event tracking across platforms
- Include qualitative feedback to validate scores
3. Segmentation Strategy
- Power Users: High engagement for expansion opportunities
- Regular Users: Medium engagement for optimization
- Casual Users: Low engagement for activation campaigns
- At-Risk Users: Very low engagement for retention efforts
4. Optimization Tactics
- Improve onboarding to drive initial engagement
- Create engagement loops and habit-forming features
- Personalize experiences based on usage patterns
- Remove friction from high-value actions
5. Intervention Programs
- Automated campaigns for different engagement tiers
- Proactive customer success outreach
- Feature education and training programs
- Gamification and incentive programs