Migrate users
Step-by-step guide for migrating your existing users to Snag, including handling users without wallet addresses
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:
Have your Snag API key ready (see Getting Started)
Export your user data from your existing system
Ensure all users have wallet addresses (or generate them first)
Migration Strategy
Follow these steps to migrate your users systematically:
Export your user data and ensure you have wallet addresses for each user.
Users without wallet addresses cannot be created in Snag. If your users don't have wallet addresses, see our wallet generation guide for guidance on how to create them.
Required Data
walletAddress(required) - The unique blockchain wallet address
Optional Metadata
externalIdentifier- Your internal user ID (highly recommended)displayName- User's display nameemailAddress- User's email addressdiscordUser- Discord usernamediscordUserId- Discord user IDtwitterUser- Twitter/X usernametwitterUserId- Twitter/X user IDtelegramUsername- Telegram usernametelegramUserId- Telegram user IDlogoUrl- URL to user's avatar/profile picture
Create a script to process your users in batches. Here's a complete example:
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`)For large migrations, process users in batches to avoid rate limits and make the process more manageable:
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))
}
}
}Start with a small batch (10-50 users) to verify your migration script works correctly before processing your entire user base.
After migration, verify that all users were created successfully:
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
}Review and retry failed migrations:
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
Migration Best Practices
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.
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
Log all migration attempts with timestamps, user IDs, and results. This helps with debugging and provides an audit trail.
Have a rollback strategy in case something goes wrong. Keep track of which users were migrated so you can clean up if needed.
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.
Watch for errors, rate limits, and performance issues during migration. Be prepared to pause and adjust your approach if needed.
Handling Users Without Wallet Addresses
If some of your users don't have wallet addresses, you have several options:
Complete Migration Example
Here's a complete, production-ready migration script:
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)
})