> ## Documentation Index
> Fetch the complete documentation index at: https://docs.snagsolutions.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate users

> Step-by-step guide for migrating your existing users to Snag, including handling users without wallet addresses

<head>
  <script type="application/ld+json">
    {JSON.stringify({
            "@context": "https://schema.org",
            "@type": "BreadcrumbList",
            "itemListElement": [
              {"@type": "ListItem", "position": 1, "name": "Home", "item": "https://docs.snagsolutions.io/welcome"},
              {"@type": "ListItem", "position": 2, "name": "Loyalty", "item": "https://docs.snagsolutions.io/loyalty/loyalty-overview"},
              {"@type": "ListItem", "position": 3, "name": "Development", "item": "https://docs.snagsolutions.io/loyalty/development/getting-started"},
              {"@type": "ListItem", "position": 4, "name": "Migrate users"}
            ]
          })}
  </script>
</head>

This guide walks you through migrating your existing user base to Snag's loyalty system. Whether you're moving from another platform or setting up Snag for the first time, this guide will help you successfully migrate your users.

## Prerequisites

Before starting your migration:

<Check>
  Have your Snag API key ready (see [Getting Started](/loyalty/development/getting-started))
</Check>

<Check>
  Export your user data from your existing system
</Check>

<Check>
  Ensure all users have wallet addresses (or [generate them](/loyalty/development/generate-wallet-addresses) first)
</Check>

## Migration Strategy

Follow these steps to migrate your users systematically:

<Steps>
  <Step title="Prepare your user data">
    Export your user data and ensure you have wallet addresses for each user.

    <Warning>
      Users without wallet addresses cannot be created in Snag. If your users don't have wallet addresses, see our [wallet generation guide](/loyalty/development/generate-wallet-addresses) for guidance on how to create them.
    </Warning>

    ### Required Data

    * `walletAddress` (required) - The unique blockchain wallet address

    ### Optional Metadata

    * `externalIdentifier` - Your internal user ID (highly recommended)
    * `displayName` - User's display name
    * `emailAddress` - User's email address
    * `discordUser` - Discord username
    * `discordUserId` - Discord user ID
    * `twitterUser` - Twitter/X username
    * `twitterUserId` - Twitter/X user ID
    * `telegramUsername` - Telegram username
    * `telegramUserId` - Telegram user ID
    * `logoUrl` - URL to user's avatar/profile picture
  </Step>

  <Step title="Set up your migration script">
    Create a script to process your users in batches. Here's a complete example:

    ```typescript theme={null}
    import SnagSolutions from '@snagsolutions/sdk'

    const client = new SnagSolutions({
      apiKey: process.env.SNAG_API_KEY,
    })

    interface YourUser {
      id: string
      walletAddress: string
      name: string
      email: string
      // ... other fields
    }

    async function migrateUsers(users: YourUser[]) {
      const results = {
        successful: 0,
        failed: 0,
        errors: [] as Array<{ userId: string; error: string }>,
      }

      for (const user of users) {
        try {
          await client.users.createMetadata({
            walletAddress: user.walletAddress,
            externalIdentifier: user.id,
            displayName: user.name,
            emailAddress: user.email,
          })
          
          results.successful++
          console.log(`✓ Migrated user ${user.id}`)
        } catch (error) {
          results.failed++
          results.errors.push({
            userId: user.id,
            error: error.message,
          })
          console.error(`✗ Failed to migrate user ${user.id}:`, error.message)
        }
      }

      return results
    }

    // Usage
    const users = await fetchYourUsers()
    const results = await migrateUsers(users)
    console.log(`Migration complete: ${results.successful} successful, ${results.failed} failed`)
    ```
  </Step>

  <Step title="Process in batches">
    For large migrations, process users in batches to avoid rate limits and make the process more manageable:

    ```typescript theme={null}
    async function migrateInBatches(
      users: YourUser[],
      batchSize: number = 100,
      delayMs: number = 1000
    ) {
      const totalBatches = Math.ceil(users.length / batchSize)
      
      for (let i = 0; i < totalBatches; i++) {
        const start = i * batchSize
        const end = start + batchSize
        const batch = users.slice(start, end)
        
        console.log(`Processing batch ${i + 1}/${totalBatches}`)
        await migrateUsers(batch)
        
        // Add delay between batches to respect rate limits
        if (i < totalBatches - 1) {
          await new Promise(resolve => setTimeout(resolve, delayMs))
        }
      }
    }
    ```

    <Tip>
      Start with a small batch (10-50 users) to verify your migration script works correctly before processing your entire user base.
    </Tip>
  </Step>

  <Step title="Verify migration">
    After migration, verify that all users were created successfully:

    ```typescript theme={null}
    async function verifyMigration(externalIds: string[]) {
      const notFound: string[] = []
      
      for (const externalId of externalIds) {
        try {
          // Query by external identifier
          const users = await client.users.list({
            externalIdentifier: externalId,
          })
          
          if (users.length === 0) {
            notFound.push(externalId)
          }
        } catch (error) {
          console.error(`Error checking user ${externalId}:`, error)
          notFound.push(externalId)
        }
      }
      
      if (notFound.length > 0) {
        console.log(`Missing users: ${notFound.length}`)
        console.log(notFound)
      } else {
        console.log('✓ All users verified successfully')
      }
      
      return notFound
    }
    ```
  </Step>

  <Step title="Handle failed migrations">
    Review and retry failed migrations:

    ```typescript theme={null}
    async function retryFailed(errors: Array<{ userId: string; error: string }>) {
      console.log(`Retrying ${errors.length} failed migrations...`)
      
      const usersToRetry = await fetchUsersById(errors.map(e => e.userId))
      const results = await migrateUsers(usersToRetry)
      
      return results
    }
    ```

    Common errors and solutions:

    * **Invalid wallet address** - Verify the wallet address format
    * **Duplicate user** - User already exists, use update instead
    * **Rate limit** - Increase delay between batches
    * **Authentication error** - Check your API key
  </Step>
</Steps>

## Migration Best Practices

<AccordionGroup>
  <Accordion title="Test with a Subset First">
    Before migrating your entire user base, test with a small subset (10-100 users) to identify any issues with your data format or migration script.
  </Accordion>

  <Accordion title="Use External Identifiers">
    Always include the `externalIdentifier` field mapping to your internal user ID. This makes it easy to:

    * Query Snag users from your system
    * Link Snag data back to your users
    * Debug migration issues
    * Avoid duplicate migrations
  </Accordion>

  <Accordion title="Keep a Migration Log">
    Log all migration attempts with timestamps, user IDs, and results. This helps with debugging and provides an audit trail.
  </Accordion>

  <Accordion title="Plan for Rollback">
    Have a rollback strategy in case something goes wrong. Keep track of which users were migrated so you can clean up if needed.
  </Accordion>

  <Accordion title="Handle Idempotency">
    The metadata endpoint is idempotent - calling it multiple times with the same wallet address updates the user. This means you can safely re-run your migration script.
  </Accordion>

  <Accordion title="Monitor During Migration">
    Watch for errors, rate limits, and performance issues during migration. Be prepared to pause and adjust your approach if needed.
  </Accordion>
</AccordionGroup>

## Handling Users Without Wallet Addresses

If some of your users don't have wallet addresses, you have several options:

<CardGroup cols={2}>
  <Card title="Generate Wallets" icon="sparkles" href="/loyalty/development/generate-wallet-addresses">
    Generate wallet addresses programmatically using viem or smart wallet providers
  </Card>

  <Card title="Prompt Users" icon="user" color="#ca8b04">
    Ask users to connect their wallets during their next login
  </Card>

  <Card title="Gradual Migration" icon="clock" color="#0285c7">
    Migrate users as they connect wallets, rather than all at once
  </Card>

  <Card title="Email-Based" icon="envelope" color="#16a34a">
    Use email-to-wallet services to create wallets tied to email addresses
  </Card>
</CardGroup>

## Complete Migration Example

Here's a complete, production-ready migration script:

```typescript theme={null}
import SnagSolutions from '@snagsolutions/sdk'
import { writeFileSync } from 'fs'

const client = new SnagSolutions({
  apiKey: process.env.SNAG_API_KEY,
})

interface MigrationResult {
  timestamp: string
  totalUsers: number
  successful: number
  failed: number
  errors: Array<{ userId: string; error: string }>
}

async function runMigration(): Promise<MigrationResult> {
  console.log('Starting user migration...')
  
  // Fetch your users
  const users = await fetchYourUsers()
  console.log(`Found ${users.length} users to migrate`)
  
  const result: MigrationResult = {
    timestamp: new Date().toISOString(),
    totalUsers: users.length,
    successful: 0,
    failed: 0,
    errors: [],
  }
  
  // Process in batches
  const batchSize = 100
  const totalBatches = Math.ceil(users.length / batchSize)
  
  for (let i = 0; i < totalBatches; i++) {
    const start = i * batchSize
    const end = start + batchSize
    const batch = users.slice(start, end)
    
    console.log(`\nProcessing batch ${i + 1}/${totalBatches} (${batch.length} users)`)
    
    for (const user of batch) {
      try {
        await client.users.createMetadata({
          walletAddress: user.walletAddress,
          externalIdentifier: user.id,
          displayName: user.name,
          emailAddress: user.email,
          discordUser: user.discordUsername,
          twitterUser: user.twitterUsername,
        })
        
        result.successful++
        process.stdout.write('.')
      } catch (error) {
        result.failed++
        result.errors.push({
          userId: user.id,
          error: error.message,
        })
        process.stdout.write('✗')
      }
    }
    
    // Rate limit protection
    if (i < totalBatches - 1) {
      await new Promise(resolve => setTimeout(resolve, 1000))
    }
  }
  
  // Save results
  const filename = `migration-${Date.now()}.json`
  writeFileSync(filename, JSON.stringify(result, null, 2))
  
  console.log(`\n\nMigration complete!`)
  console.log(`✓ Successful: ${result.successful}`)
  console.log(`✗ Failed: ${result.failed}`)
  console.log(`Results saved to: ${filename}`)
  
  return result
}

// Run migration
runMigration()
  .then(() => process.exit(0))
  .catch(error => {
    console.error('Migration failed:', error)
    process.exit(1)
  })
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Generate Wallet Addresses" icon="wallet" href="/loyalty/development/generate-wallet-addresses">
    Learn how to generate wallet addresses for users who don't have them
  </Card>

  <Card title="Manage User Groups" icon="users" href="/loyalty/development/manage-user-groups">
    Connect multiple wallets to a single user using user groups
  </Card>

  <Card title="Connect Social Accounts" icon="share" href="/loyalty/development/connect-social-accounts">
    Integrate social media platforms with your users
  </Card>

  <Card title="Create Users" icon="user-plus" href="/loyalty/development/create-users">
    Learn more about creating and managing individual users
  </Card>
</CardGroup>
