Skip to main content

Achievements

Code First

Reward users with badges and milestones for their accomplishments.

Tiered Rewards

Bronze, silver, gold, and platinum achievements

Point System

Each achievement awards configurable points

Secret Achievements

Hidden until unlocked for surprise delight

Auto-Unlock

Trigger achievements from analytics events

How Achievements Work

Achievements are one-time awards that recognize user accomplishments. Once unlocked, they remain in the user's profile permanently. Each achievement can award points that contribute to a total score.

🥉
Bronze
Easy to earn
🥈
Silver
Moderate effort
🥇
Gold
Significant achievement
💎
Platinum
Exceptional dedication

Configuration

Define achievements in your engagement config. Group related achievements for better organization:

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

export const engagement = defineEngagement({
  achievements: {
    // Onboarding achievements
    first_steps: achievement({
      name: 'First Steps',
      description: 'Complete the onboarding tutorial',
      icon: 'footprints',
      tier: 'bronze',
      points: 10,
      category: 'onboarding',
    }),

    profile_complete: achievement({
      name: 'Looking Good',
      description: 'Complete your profile with photo and bio',
      icon: 'user-check',
      tier: 'bronze',
      points: 15,
      category: 'onboarding',
    }),

    // Activity achievements
    power_user: achievement({
      name: 'Power User',
      description: 'Use the app for 30 consecutive days',
      icon: 'zap',
      tier: 'gold',
      points: 100,
      category: 'activity',
    }),

    night_owl: achievement({
      name: 'Night Owl',
      description: 'Complete a task between midnight and 4am',
      icon: 'moon',
      tier: 'silver',
      points: 25,
      category: 'activity',
      secret: true,  // Hidden until unlocked!
    }),

    // Social achievements
    influencer: achievement({
      name: 'Influencer',
      description: 'Refer 10 friends who sign up',
      icon: 'users',
      tier: 'platinum',
      points: 250,
      category: 'social',
    }),

    // Milestone achievements
    centurion: achievement({
      name: 'Centurion',
      description: 'Complete 100 tasks',
      icon: 'target',
      tier: 'gold',
      points: 75,
      category: 'milestones',
      // Progress tracking
      progressTarget: 100,
    }),
  },
})
PropertyTypeDescription
namerequiredstringDisplay name for the achievement
descriptionrequiredstringHow to earn this achievement
iconrequiredstringIcon identifier (Lucide icon name or custom)
tierrequired'bronze' | 'silver' | 'gold' | 'platinum'Rarity/difficulty tier
pointsnumberPoints awarded when unlocked (default: 0)
categorystringCategory for grouping in UI (e.g., "onboarding", "social")
secretbooleanHide name/description until unlocked (default: false)
progressTargetnumberTarget count for progress-based achievements

Unlocking Achievements

Unlock achievements when users complete specific actions:

'use client'

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

export function TaskCompleteHandler({ taskId }) {
  const { unlockAchievement } = useAchievements()

  const handleComplete = async () => {
    // Complete the task...
    await completeTask(taskId)

    // Check if this unlocks an achievement
    const taskCount = await getCompletedTaskCount()

    if (taskCount === 1) {
      const result = await unlockAchievement('first_steps')
      if (result.unlocked) {
        // Show celebration modal!
        showAchievementModal(result.achievement)
      }
    }

    if (taskCount === 100) {
      const result = await unlockAchievement('centurion')
      if (result.unlocked) {
        showAchievementModal(result.achievement)
      }
    }
  }

  return (
    <button onClick={handleComplete}>
      Complete Task
    </button>
  )
}

Idempotent Unlocking

It's safe to call unlockAchievement multiple times. The achievement will only be awarded once, and subsequent calls return alreadyUnlocked: true.

Automatic Triggers

Link achievements to analytics events for automatic unlocking:

engagement.config.ts
export const engagement = defineEngagement({
  achievements: {
    // Unlock when specific event fires
    first_purchase: achievement({
      name: 'First Purchase',
      description: 'Make your first purchase',
      icon: 'shopping-bag',
      tier: 'bronze',
      points: 20,

      // Auto-unlock when this event fires
      trigger: 'purchase_completed',
    }),

    // Unlock after event fires N times
    share_master: achievement({
      name: 'Share Master',
      description: 'Share content 10 times',
      icon: 'share-2',
      tier: 'silver',
      points: 50,
      progressTarget: 10,

      // Count-based trigger
      trigger: {
        event: 'content_shared',
        count: 10,
      },
    }),

    // Unlock based on event property
    big_spender: achievement({
      name: 'Big Spender',
      description: 'Make a purchase over $100',
      icon: 'credit-card',
      tier: 'gold',
      points: 75,

      // Property-based trigger
      trigger: {
        event: 'purchase_completed',
        condition: { 'amount': { $gte: 100 } },
      },
    }),

    // Time-based achievement
    night_owl: achievement({
      name: 'Night Owl',
      description: 'Complete a task between midnight and 4am',
      icon: 'moon',
      tier: 'silver',
      points: 25,
      secret: true,

      trigger: {
        event: 'task_completed',
        condition: {
          '$hour': { $gte: 0, $lt: 4 }  // 00:00-03:59
        },
      },
    }),
  },
})

// When you track events via analytics...
analytics.track('content_shared', { contentId: '...' })

// ...achievements with matching triggers are automatically checked!

Progress Tracking

For count-based triggers, progress is automatically tracked. Users can see their progress toward achievements like "Share content: 7/10".

Progress Achievements

Track incremental progress toward achievements:

'use client'

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

export function AchievementProgress({ achievementId }) {
  const {
    progress,      // { current: 7, target: 10, percentage: 70 }
    increment,     // Function to increment progress
    isLoading,
  } = useAchievementProgress(achievementId)

  return (
    <div className="p-4 border rounded-xl">
      <div className="flex justify-between mb-2">
        <span className="font-medium">Share Master</span>
        <span className="text-sm text-muted-foreground">
          {progress?.current ?? 0} / {progress?.target ?? 0}
        </span>
      </div>

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

Secret Achievements

Secret achievements add an element of discovery and delight:

// In config
night_owl: achievement({
  name: 'Night Owl',
  description: 'Complete a task between midnight and 4am',
  icon: 'moon',
  tier: 'silver',
  points: 25,
  secret: true,  // 👈 Hidden until unlocked
}),

// In UI, secret achievements show as:
// - Name: "???" or "Secret Achievement"
// - Description: "This achievement is hidden"
// - Icon: 🔒

// After unlocking, the real name/description is revealed!

// Query with secret flag
const achievements = await platform.engagement.listAchievements()
// Returns all achievements with:
// - secret: true achievements have name/description hidden
// - unlocked secret achievements are fully visible

// Or explicitly check
const definition = achievements.find(a => a.id === 'night_owl')
if (definition.secret && !userUnlocked.includes('night_owl')) {
  // Show placeholder
} else {
  // Show real achievement
}
???
Secret Achievement

Keep exploring to discover this hidden badge!

🌙
Night Owl
Unlocked!

Complete a task between midnight and 4am

Achievement Categories

Group achievements by category for organized display:

'use client'

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

export function AchievementsByCategory() {
  const { achievements, unlockedIds, isLoading } = useAchievements()

  // Group by category
  const byCategory = achievements?.reduce((acc, a) => {
    const cat = a.category || 'general'
    if (!acc[cat]) acc[cat] = []
    acc[cat].push(a)
    return acc
  }, {} as Record<string, typeof achievements>)

  return (
    <div className="space-y-8">
      {Object.entries(byCategory ?? {}).map(([category, items]) => (
        <div key={category}>
          <h3 className="text-lg font-bold capitalize mb-4">
            {category}
          </h3>
          <div className="grid grid-cols-4 gap-4">
            {items.map((achievement) => {
              const isUnlocked = unlockedIds?.includes(achievement.id)
              return (
                <AchievementCard
                  key={achievement.id}
                  achievement={achievement}
                  isUnlocked={isUnlocked}
                />
              )
            })}
          </div>
        </div>
      ))}
    </div>
  )
}

Notifications

Notify users when they unlock achievements:

// Server-side: After unlocking
const result = await platform.engagement.unlockAchievement({
  userId,
  achievementId: 'power_user',
})

if (result.unlocked) {
  // Send push notification
  await platform.push.send({
    userId,
    title: '🏆 Achievement Unlocked!',
    body: `You earned "${result.achievement.name}"!`,
    data: {
      type: 'achievement',
      achievementId: result.achievement.id,
    },
  })

  // Or send email for special achievements
  if (result.achievement.tier === 'platinum') {
    await sendEmail(userId, {
      template: 'achievement-unlocked',
      data: {
        achievementName: result.achievement.name,
        points: result.achievement.points,
        totalPoints: result.totalPoints,
      },
    })
  }
}

// Client-side: Listen for achievement events
import { useEngagementEvents } from '@sylphx/sdk/react'

function AchievementListener() {
  useEngagementEvents({
    onAchievementUnlocked: (achievement) => {
      // Show toast or modal
      showCelebration(achievement)
    },
  })
  return null
}

Display Components

Pre-built components for achievement UIs:

import {
  AchievementGrid,      // Grid of all achievements
  AchievementCard,      // Single achievement card
  AchievementBadge,     // Compact badge icon
  AchievementProgress,  // Progress bar for progress-based
  AchievementModal,     // Celebration modal on unlock
  TotalPoints,          // User's total achievement points
} from '@sylphx/sdk/react'

// Full grid with categories
<AchievementGrid
  groupByCategory
  showProgress         // Show progress bars
  showLocked           // Show locked achievements (default: true)
  hideSecrets={false}  // Show "???" placeholders for secrets
/>

// Single achievement card
<AchievementCard
  achievementId="power_user"
  size="lg"           // sm, md, lg
  showDescription
/>

// Compact badge for profiles
<AchievementBadge achievementId="power_user" />

// Total points display
<TotalPoints />
// Renders: "🏅 450 points"

// Celebration modal (shown automatically on unlock)
<AchievementModal
  autoShow              // Show when achievement unlocks
  celebrationDuration={3000}
  onClose={() => {}}
/>