Skip to main content

Engagement

Code First

Drive user retention with gamification: streaks, leaderboards, and achievements.

Streaks

Track daily activity with configurable streak mechanics

Leaderboards

Rank users by any metric with time-based competitions

Achievements

Unlock badges and milestones based on user actions

Code First

Define config in code, auto-synced to platform

Quick Start

1

Define Configuration

Create your engagement config in engagement.config.ts:

2

Wrap Your App

Add <EngagementProvider> to enable hooks and automatic sync.

3

Record Activity

Use hooks like useStreak() and useLeaderboard() to track user progress.

Configuration

Define your streaks, leaderboards, and achievements in a single config file:

engagement.config.ts
import { defineEngagement, streak, leaderboard, achievement } from '@sylphx/sdk/engagement'

export const engagement = defineEngagement({
  // Streak configurations
  streaks: {
    daily_login: streak({
      name: 'Daily Login',
      description: 'Log in every day',
      resetAfterDays: 1,      // Reset after 1 day of inactivity
      gracePeriodHours: 4,    // 4-hour grace period
      milestones: [7, 30, 100, 365],
    }),
    workout_streak: streak({
      name: 'Workout Streak',
      description: 'Complete a workout',
      resetAfterDays: 1,
      gracePeriodHours: 0,
      milestones: [7, 14, 30],
    }),
  },

  // Leaderboard configurations
  leaderboards: {
    weekly_points: leaderboard({
      name: 'Weekly Points',
      description: 'Top point earners this week',
      period: 'weekly',
      sortOrder: 'desc',
      maxEntries: 100,
    }),
    all_time_score: leaderboard({
      name: 'All-Time Score',
      period: 'alltime',
      sortOrder: 'desc',
    }),
  },

  // Achievement configurations
  achievements: {
    first_steps: achievement({
      name: 'First Steps',
      description: 'Complete your first task',
      icon: 'footprints',
      tier: 'bronze',
      points: 10,
    }),
    power_user: achievement({
      name: 'Power User',
      description: 'Use the app for 30 days',
      icon: 'zap',
      tier: 'gold',
      points: 100,
      secret: false,
    }),
  },
})

Code First Sync

Your config is automatically synced to the platform when the app starts. Changes in code are the source of truth.

Provider Setup

Wrap your app with the EngagementProvider to enable engagement features:

app/layout.tsx
import { SylphxProvider } from '@sylphx/sdk/react'
import { engagement } from './engagement.config'

export default function RootLayout({ children }) {
  return (
    <SylphxProvider
      appId={process.env.NEXT_PUBLIC_SYLPHX_APP_ID}
      engagement={engagement}  // Pass engagement config
    >
      {children}
    </SylphxProvider>
  )
}

Streaks

Track daily user activity with configurable streak mechanics:

Client-side
'use client'

import { useStreak } from '@sylphx/sdk/react'

export function StreakCard() {
  const { streak, recordActivity, isLoading } = useStreak('daily_login')

  return (
    <div className="p-6 border rounded-xl">
      <div className="flex items-center gap-3">
        <span className="text-3xl">🔥</span>
        <div>
          <div className="text-2xl font-bold">{streak?.current ?? 0} days</div>
          <div className="text-muted-foreground">Current streak</div>
        </div>
      </div>

      <div className="mt-4 text-sm text-muted-foreground">
        Best: {streak?.best ?? 0} days
      </div>

      <button
        onClick={() => recordActivity()}
        disabled={isLoading || streak?.recordedToday}
        className="mt-4 w-full btn btn-primary"
      >
        {streak?.recordedToday ? 'Logged Today ✓' : 'Record Activity'}
      </button>
    </div>
  )
}
Server-side
import { platform } from '@/lib/platform'

// Record streak activity
const result = await platform.engagement.recordStreak({
  userId: user.id,
  streakId: 'daily_login',
})

// result: { current: 5, best: 12, recordedToday: true, milestone: null }

// Get user's streak status
const streak = await platform.engagement.getStreak(user.id, 'daily_login')

// Check if milestone reached
if (result.milestone) {
  // User hit 7, 30, 100, or 365 day milestone!
  await sendNotification(user.id, `🎉 ${result.milestone} day streak!`)
}
PropertyTypeDescription
resetAfterDaysnumberDays of inactivity before streak resets (default: 1)
gracePeriodHoursnumberExtra hours before reset (for timezone flexibility)
milestonesnumber[]Day counts that trigger milestone events

Leaderboards

Create competitive rankings with automatic period resets:

Client-side
'use client'

import { useLeaderboard } from '@sylphx/sdk/react'

export function WeeklyLeaderboard() {
  const { entries, userRank, submitScore, isLoading } = useLeaderboard('weekly_points')

  return (
    <div className="p-6 border rounded-xl">
      <h3 className="text-lg font-bold mb-4">🏆 Weekly Leaderboard</h3>

      {/* Your rank */}
      {userRank && (
        <div className="mb-4 p-3 bg-primary/10 rounded-lg">
          Your rank: #{userRank.rank} ({userRank.score} pts)
        </div>
      )}

      {/* Top players */}
      <div className="space-y-2">
        {entries?.slice(0, 10).map((entry, i) => (
          <div key={entry.userId} className="flex items-center gap-3 p-2">
            <span className="w-6 text-center font-bold">
              {i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `#${i + 1}`}
            </span>
            <span className="flex-1">{entry.displayName}</span>
            <span className="font-mono">{entry.score}</span>
          </div>
        ))}
      </div>
    </div>
  )
}
Server-side
import { platform } from '@/lib/platform'

// Submit a score
await platform.engagement.submitScore({
  userId: user.id,
  leaderboardId: 'weekly_points',
  score: 150,
  metadata: { source: 'completed_task' },
})

// Get leaderboard with pagination
const leaderboard = await platform.engagement.getLeaderboard('weekly_points', {
  limit: 100,
  offset: 0,
})

// Get user's rank
const userRank = await platform.engagement.getUserRank(user.id, 'weekly_points')
// { rank: 5, score: 450, percentile: 95 }
PropertyTypeDescription
period'daily' | 'weekly' | 'monthly' | 'alltime'How often the leaderboard resets
sortOrder'asc' | 'desc'Sort direction (desc = highest first)
maxEntriesnumberMaximum entries to track (default: 1000)

Achievements

Award badges and milestones based on user actions:

Client-side
'use client'

import { useAchievements } from '@sylphx/sdk/react'

export function AchievementGrid() {
  const { achievements, unlockedIds, totalPoints } = useAchievements()

  return (
    <div>
      <div className="mb-4 text-lg font-bold">
        🏅 {totalPoints} points earned
      </div>

      <div className="grid grid-cols-3 gap-4">
        {achievements?.map((achievement) => {
          const isUnlocked = unlockedIds?.includes(achievement.id)
          return (
            <div
              key={achievement.id}
              className={`p-4 border rounded-xl text-center ${
                isUnlocked ? 'bg-primary/10 border-primary' : 'opacity-50'
              }`}
            >
              <div className="text-3xl mb-2">
                {isUnlocked ? achievement.icon : '🔒'}
              </div>
              <div className="font-medium">
                {achievement.secret && !isUnlocked ? '???' : achievement.name}
              </div>
              <div className="text-xs text-muted-foreground">
                {achievement.points} pts
              </div>
            </div>
          )
        })}
      </div>
    </div>
  )
}
Server-side
import { platform } from '@/lib/platform'

// Unlock an achievement
const result = await platform.engagement.unlockAchievement({
  userId: user.id,
  achievementId: 'first_steps',
})

if (result.unlocked) {
  // Achievement was newly unlocked
  // result.achievement contains full achievement data
  await sendNotification(user.id, `🏆 Achievement unlocked: ${result.achievement.name}`)
}

// Get all user achievements
const userAchievements = await platform.engagement.getUserAchievements(user.id)
// { unlocked: ['first_steps', 'power_user'], totalPoints: 110 }

// Check if user has achievement
const hasAchievement = await platform.engagement.hasAchievement(user.id, 'power_user')
PropertyTypeDescription
tier'bronze' | 'silver' | 'gold' | 'platinum'Achievement rarity/difficulty tier
pointsnumberPoints awarded when unlocked
secretbooleanHide details until unlocked (default: false)
iconstringIcon identifier for display

Event Integration

Automatically trigger engagement updates from analytics events:

Automatic triggers
// In your engagement config, link to analytics events
export const engagement = defineEngagement({
  streaks: {
    daily_activity: streak({
      name: 'Daily Activity',
      // Auto-record when any of these events fire
      triggers: ['page_view', 'task_complete', 'workout_done'],
    }),
  },
  achievements: {
    first_purchase: achievement({
      name: 'First Purchase',
      // Auto-unlock when this event fires
      trigger: 'purchase_complete',
    }),
    share_master: achievement({
      name: 'Share Master',
      // Unlock after 10 share events
      trigger: { event: 'content_shared', count: 10 },
    }),
  },
})

Combine with Analytics

Events tracked via analytics.track() can automatically trigger streak recordings and achievement unlocks.

Progress Tracking

Track user progress toward goals with visual progress bars:

'use client'

import { useProgress } from '@sylphx/sdk/react'

export function ProgressCard() {
  const { progress, increment, isLoading } = useProgress('onboarding')

  return (
    <div className="p-6 border rounded-xl">
      <h3 className="font-bold mb-2">Onboarding Progress</h3>

      <div className="h-3 bg-muted rounded-full overflow-hidden">
        <div
          className="h-full bg-primary transition-all"
          style={{ width: `${progress?.percentage ?? 0}%` }}
        />
      </div>

      <div className="mt-2 text-sm text-muted-foreground">
        {progress?.current ?? 0} / {progress?.target ?? 0} steps complete
      </div>
    </div>
  )
}

Privacy Controls

Control data visibility and opt-out options:

// User privacy preferences
await platform.engagement.setPrivacy(userId, {
  showOnLeaderboards: false,    // Hide from public leaderboards
  shareAchievements: false,     // Don't share achievement unlocks
  allowDataExport: true,        // Allow data export requests
})

// Anonymize leaderboard display
const leaderboard = await platform.engagement.getLeaderboard('weekly', {
  anonymize: true,  // Shows "Player #123" instead of real names
})

// GDPR: Export all engagement data
const data = await platform.engagement.exportUserData(userId)

// GDPR: Delete all engagement data
await platform.engagement.deleteUserData(userId)

Deep Dive Guides

Learn more about each engagement feature with detailed guides and advanced patterns: