Skip to Content
Migration Guide

Last Updated: 3/9/2026


Migration Guide

Guide for migrating to Nano ID from other ID generators or upgrading between versions.

Migrating from UUID

Why Migrate?

Advantages of Nano ID over UUID:

  • ✅ Shorter: 21 chars vs 36 chars
  • ✅ URL-safe: No encoding needed
  • ✅ Smaller package: 118 bytes vs 423 bytes
  • ✅ Similar collision resistance

Disadvantages:

  • ❌ Not RFC 4122 compliant
  • ❌ Not sortable by time (unless using ULID variant)
  • ❌ Different format (may affect existing systems)

Side-by-Side Comparison

import { v4 as uuidv4 } from 'uuid' import { nanoid } from 'nanoid' // UUID v4 const uuid = uuidv4() // "f47ac10b-58cc-4372-a567-0e02b2c3d479" (36 chars) // Nano ID const id = nanoid() // "V1StGXR8_Z5jdHi6B-myT" (21 chars)

Migration Strategy

Option 1: Dual Column (Gradual)

-- Add new column ALTER TABLE users ADD COLUMN nanoid VARCHAR(21); -- Generate Nano IDs for existing rows UPDATE users SET nanoid = generate_nanoid() WHERE nanoid IS NULL; -- Make unique ALTER TABLE users ADD UNIQUE (nanoid); -- Update application to use nanoid -- Gradually migrate foreign keys -- Eventually drop uuid column

Option 2: New Table (Clean Break)

-- Create new table CREATE TABLE users_new ( id VARCHAR(21) PRIMARY KEY, uuid VARCHAR(36) UNIQUE, -- Keep for reference email VARCHAR(255), created_at TIMESTAMP ); -- Copy data INSERT INTO users_new (id, uuid, email, created_at) SELECT generate_nanoid(), uuid, email, created_at FROM users; -- Update foreign keys in other tables -- Rename tables -- Drop old table

Option 3: New Records Only

import { nanoid } from 'nanoid' import { v4 as uuidv4 } from 'uuid' // Keep UUID for existing records // Use Nano ID for new records const createUser = (email) => { return { id: nanoid(), // New format for new users email, createdAt: new Date() } } // Handle both formats in queries const getUser = (id) => { // Check format const isUuid = id.includes('-') const column = isUuid ? 'uuid' : 'id' return db.query(`SELECT * FROM users WHERE ${column} = ?`, [id]) }

Code Changes

// Before (UUID) import { v4 as uuidv4 } from 'uuid' const user = { id: uuidv4(), email: 'user@example.com' } // After (Nano ID) import { nanoid } from 'nanoid' const user = { id: nanoid(), email: 'user@example.com' }

Migrating from shortid

Why Migrate?

shortid is deprecated and has security issues:

  • ❌ Predictable IDs
  • ❌ No longer maintained
  • ❌ Security vulnerabilities

Nano ID advantages:

  • ✅ Cryptographically secure
  • ✅ Actively maintained
  • ✅ Better performance
  • ✅ Smaller package

Code Changes

// Before (shortid) const shortid = require('shortid') const id = shortid.generate() // "rJv2ZvZ8W" (9 chars, variable length) // After (Nano ID) import { nanoid } from 'nanoid' const id = nanoid(10) // Match length if needed // "V1StGXR8_Z" (10 chars, fixed length)

Migration Checklist

  1. Install Nano ID:

    npm install nanoid npm uninstall shortid
  2. Replace imports:

    // Find: const shortid = require('shortid') // Replace: import { nanoid } from 'nanoid'
  3. Replace generate calls:

    // Find: shortid.generate() // Replace: nanoid()
  4. Update database schema (if needed):

    -- shortid: VARCHAR(14) (max length) -- Nano ID: VARCHAR(21) (default) ALTER TABLE users MODIFY COLUMN id VARCHAR(21);
  5. Test thoroughly: IDs will be different format

Upgrading Nano ID Versions

From v3 to v5

Major changes:

  • ❌ CommonJS support removed (ESM only)
  • ✅ Smaller package size
  • ✅ Better TypeScript types

Breaking Changes

CommonJS require() no longer works:

// v3 (works) const { nanoid } = require('nanoid') // v5 (error) const { nanoid } = require('nanoid') // Error: require() of ES Module not supported

Solutions:

  1. Use Node.js 22.12+:

    const { nanoid } = require('nanoid') // Now works
  2. Use dynamic import:

    const { nanoid } = await import('nanoid')
  3. Convert to ESM:

    // package.json { "type": "module" }
    import { nanoid } from 'nanoid'
  4. Stay on v3:

    npm install nanoid@3

API Changes

No API changes - same functions, same signatures:

// v3 import { nanoid, customAlphabet } from 'nanoid' const id = nanoid() // v5 (identical) import { nanoid, customAlphabet } from 'nanoid' const id = nanoid()

From v2 to v3

Major changes:

  • ✅ ESM support added
  • ✅ Better browser support
  • ✅ TypeScript improvements

No breaking changes for ESM users.

Migrating from Auto-Increment

Why Migrate?

Auto-increment limitations:

  • ❌ Requires database coordination
  • ❌ Not suitable for distributed systems
  • ❌ Reveals business metrics (e.g., user count)
  • ❌ Sequential = predictable

Nano ID advantages:

  • ✅ Generate client-side
  • ✅ No coordination needed
  • ✅ Works in distributed systems
  • ✅ Doesn’t reveal metrics

Migration Strategy

Option 1: Keep Both

CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, nanoid VARCHAR(21) UNIQUE NOT NULL, email VARCHAR(255) );
import { nanoid } from 'nanoid' const user = { nanoid: nanoid(), // External ID (in URLs) email: 'user@example.com' } // Use nanoid in URLs app.get('/users/:nanoid', async (req, res) => { const user = await db.users.findOne({ nanoid: req.params.nanoid }) res.json(user) })

Option 2: Replace Completely

-- Before CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) ); -- After CREATE TABLE users ( id VARCHAR(21) PRIMARY KEY, email VARCHAR(255) );
// Before const user = { email: 'user@example.com' // id generated by database } await db.users.insert(user) // After import { nanoid } from 'nanoid' const user = { id: nanoid(), email: 'user@example.com' } await db.users.insert(user)

Testing Migration

Validation Script

import { nanoid } from 'nanoid' // Test ID generation const ids = new Set() for (let i = 0; i < 10000; i++) { const id = nanoid() // Check length if (id.length !== 21) { throw new Error(`Wrong length: ${id.length}`) } // Check uniqueness if (ids.has(id)) { throw new Error(`Duplicate ID: ${id}`) } ids.add(id) } console.log('✅ Generated 10,000 unique IDs')

Performance Test

import { nanoid } from 'nanoid' const iterations = 100000 const start = Date.now() for (let i = 0; i < iterations; i++) { nanoid() } const elapsed = Date.now() - start const opsPerSec = (iterations / elapsed) * 1000 console.log(`Performance: ${opsPerSec.toFixed(0)} ops/sec`) // Should be >1M ops/sec

Rollback Plan

If migration fails:

  1. Keep old ID column:

    -- Don't drop old column immediately ALTER TABLE users ADD COLUMN nanoid VARCHAR(21); -- Test first, drop later
  2. Dual write:

    // Write to both old and new ID columns const user = { id: autoIncrementId, nanoid: nanoid(), email: 'user@example.com' }
  3. Feature flag:

    const USE_NANOID = process.env.USE_NANOID === 'true' const createUser = (email) => { const id = USE_NANOID ? nanoid() : null // Let DB generate return { id, email } }

See Also