Configurable Reset
Define how many days of inactivity trigger a reset
Grace Periods
Give users extra hours for timezone flexibility
Milestones
Celebrate achievements at key day counts
Streak Recovery
Optional paid recovery for broken streaks
How Streaks Work
Streaks track consecutive days of user activity. Each day a user performs the tracked action, their streak counter increments. If they miss a day (plus any grace period), the streak resets to zero.
Streak Timeline Example
User missed Thursday. With resetAfterDays: 1, the streak reset on Friday.
Configuration
Define streaks in your engagement config file. Each streak has its own reset rules and milestones:
import { defineEngagement, streak } from '@sylphx/sdk/engagement'
export const engagement = defineEngagement({
streaks: {
// Daily login streak - most common pattern
daily_login: streak({
name: 'Daily Login',
description: 'Log in every day to maintain your streak',
resetAfterDays: 1, // Reset after 1 day of inactivity
gracePeriodHours: 4, // 4-hour buffer for timezone issues
milestones: [7, 30, 100, 365],
recoverable: true, // Allow streak recovery (paid feature)
maxRecoveryDays: 3, // Can only recover if missed <= 3 days
}),
// Weekly check-in - more lenient
weekly_checkin: streak({
name: 'Weekly Check-in',
description: 'Check in at least once per week',
resetAfterDays: 7, // Reset after 7 days of inactivity
gracePeriodHours: 24, // Full day grace period
milestones: [4, 12, 52], // 1 month, 3 months, 1 year
}),
// Workout streak - strict, no grace period
workout: streak({
name: 'Workout Streak',
description: 'Complete a workout every day',
resetAfterDays: 1,
gracePeriodHours: 0, // No grace period - strict tracking
milestones: [7, 14, 30, 90],
}),
},
})| Property | Type | Description |
|---|---|---|
namerequired | string | Display name for the streak |
description | string | User-facing description of what maintains the streak |
resetAfterDaysrequired | number | Number of days of inactivity before the streak resets (typically 1) |
gracePeriodHours | number | Extra hours added to resetAfterDays for timezone flexibility (default: 0) |
milestones | number[] | Day counts that trigger milestone events (e.g., [7, 30, 100]) |
recoverable | boolean | Whether users can pay to recover a broken streak (default: false) |
maxRecoveryDays | number | Maximum days after reset that recovery is allowed (default: 7) |
Recording Activity
Record streak activity from either the client or server. The platform handles all the streak logic automatically.
'use client'
import { useStreak } from '@sylphx/sdk/react'
export function DailyLoginButton() {
const {
streak, // Current streak data
recordActivity, // Function to record activity
isLoading, // Loading state
error, // Any error
} = useStreak('daily_login')
const handleLogin = async () => {
const result = await recordActivity()
if (result.milestone) {
// User hit a milestone! Show celebration
toast.success(`🎉 ${result.milestone} day streak!`)
}
}
return (
<button
onClick={handleLogin}
disabled={isLoading || streak?.recordedToday}
className="btn btn-primary"
>
{streak?.recordedToday ? (
<>✓ Logged Today ({streak.current} days)</>
) : (
<>Check In (Current: {streak?.current ?? 0} days)</>
)}
</button>
)
}Idempotent Recording
recordedToday will be true after the first recording.Grace Periods
Grace periods add flexibility for users in different timezones or with irregular schedules. The grace period extends the deadline for recording activity.
// With gracePeriodHours: 4 and resetAfterDays: 1
// User's last activity: Monday 11:00 PM UTC
// Without grace period: Must record by Tuesday 11:59 PM UTC
// With 4-hour grace: Must record by Wednesday 3:59 AM UTC
// This helps users who:
// - Are in different timezones
// - Have varying schedules
// - Might miss midnight by a few hoursGrace Period Best Practices
- 4-6 hours - Good for daily streaks, covers timezone differences
- 12-24 hours - Very lenient, good for weekly activities
- 0 hours - Strict tracking, use for competitive/fitness apps
Streak Recovery
Allow users to recover broken streaks as a premium feature. This is a powerful monetization and retention tool.
'use client'
import { useStreak } from '@sylphx/sdk/react'
export function StreakRecoveryModal({ onClose }) {
const { streak, recoverStreak, isLoading } = useStreak('daily_login')
// Only show if streak was recently broken and is recoverable
if (!streak?.canRecover) return null
return (
<div className="modal">
<h2>😢 Your streak was broken!</h2>
<p>You had a {streak.lastStreak} day streak.</p>
<p>Days missed: {streak.daysSinceReset}</p>
<button
onClick={async () => {
const result = await recoverStreak()
if (result.recovered) {
toast.success(`Streak restored to ${result.current} days!`)
onClose()
}
}}
disabled={isLoading}
className="btn btn-primary"
>
Recover Streak (Use 1 Recovery Token)
</button>
<button onClick={onClose} className="btn btn-ghost">
Start Fresh
</button>
</div>
)
}Monetization Opportunity
Milestones & Events
Milestones trigger when users reach specific day counts. Use these to celebrate achievements and drive continued engagement.
// In your streak config
milestones: [7, 30, 100, 365]
// When recording activity, check for milestones
const result = await platform.engagement.recordStreak({
userId,
streakId: 'daily_login',
})
if (result.milestone) {
// Milestone reached! Take action:
// 1. Send notification
await sendNotification(userId, {
title: '🎉 Milestone Reached!',
body: `You've logged in for ${result.milestone} days straight!`,
})
// 2. Award bonus points
await platform.engagement.submitScore({
userId,
leaderboardId: 'monthly_points',
score: result.milestone * 10, // 70, 300, 1000, 3650 points
})
// 3. Unlock achievement
const achievementMap = {
7: 'week_warrior',
30: 'monthly_master',
100: 'centurion',
365: 'year_champion',
}
await platform.engagement.unlockAchievement({
userId,
achievementId: achievementMap[result.milestone],
})
// 4. Track analytics
analytics.track('streak_milestone', {
streakId: 'daily_login',
milestone: result.milestone,
})
}Automatic Triggers
Link streaks to analytics events for automatic recording without explicit API calls:
export const engagement = defineEngagement({
streaks: {
daily_active: streak({
name: 'Daily Active',
description: 'Use the app every day',
resetAfterDays: 1,
gracePeriodHours: 4,
milestones: [7, 30, 100],
// Auto-record when ANY of these events fire
triggers: [
'page_view',
'button_click',
'form_submit',
'feature_used',
],
}),
workout_streak: streak({
name: 'Workout Streak',
description: 'Complete a workout every day',
resetAfterDays: 1,
// Only this specific event triggers
triggers: ['workout_completed'],
}),
},
})
// Now when you track these events via analytics...
analytics.track('workout_completed', { duration: 45 })
// ...the streak is automatically recorded!
// No need to call recordStreak() explicitly.Trigger Considerations
page_view. They make streaks very easy to maintain, which may reduce their motivational value.Displaying Streaks
Pre-built components for common streak display patterns:
import {
StreakBadge, // Compact flame icon with count
StreakCard, // Full card with progress to milestone
StreakCalendar, // Monthly calendar view of activity
} from '@sylphx/sdk/react'
// Simple badge in header
<StreakBadge streakId="daily_login" />
// Renders: 🔥 7
// Full card with milestone progress
<StreakCard
streakId="daily_login"
showMilestoneProgress // Progress bar to next milestone
showBestStreak // Display all-time best
onMilestone={(m) => celebrate(m)}
/>
// Calendar view showing active days
<StreakCalendar
streakId="daily_login"
month={new Date()}
highlightMilestones
/>