Skip to Content
Api ReferenceNon Secure Api

Last Updated: 3/9/2026


Non-Secure API

A faster, smaller variant of Nano ID that uses Math.random() instead of cryptographic random generation.

Overview

Package: nanoid/non-secure
Size: 90 bytes (vs 118 bytes for secure version)
Speed: ~2.2M ops/sec (vs ~3.7M ops/sec for secure version)
Security: ❌ Not cryptographically secure

When to Use

Safe for:

  • Development and testing
  • Temporary IDs that don’t persist
  • Non-sensitive UI state
  • Environments without crypto support
  • Placeholder data

Never use for:

  • Session IDs
  • API keys or tokens
  • Password reset tokens
  • Security-critical identifiers
  • Production database primary keys
  • Authentication/authorization

API

nanoid()

import { nanoid } from 'nanoid/non-secure' const id = nanoid() console.log(id) //=> "Uakgb_J5m9g-0JDMbcJqLJ" // Custom size const shortId = nanoid(10) console.log(shortId) //=> "IRFa-VaY2b"

Signature: Same as secure nanoid()

function nanoid(size?: number): string

customAlphabet()

import { customAlphabet } from 'nanoid/non-secure' const nanoid = customAlphabet('1234567890', 10) console.log(nanoid()) //=> "4926581703"

Signature: Same as secure customAlphabet()

function customAlphabet( alphabet: string, defaultSize?: number ): (size?: number) => string

Examples

Development/Testing

import { nanoid } from 'nanoid/non-secure' // Mock data generation const mockUsers = Array(100).fill(null).map((_, i) => ({ id: nanoid(), name: `User ${i}`, email: `user${i}@example.com` }))

Temporary UI State

import { nanoid } from 'nanoid/non-secure' function TodoList() { const [todos, setTodos] = useState([]) const addTodo = (text) => { setTodos([...todos, { id: nanoid(), // Temporary ID, not persisted text, completed: false }]) } }

Client-Side Only IDs

import { nanoid } from 'nanoid/non-secure' // IDs that never leave the browser const formId = nanoid() const componentId = nanoid() <form id={formId}> <input id={componentId} /> </form>

Environments Without Crypto

// Fallback for old browsers or restricted environments let nanoid try { ({ nanoid } = await import('nanoid')) } catch { // Crypto not available, use non-secure ({ nanoid } = await import('nanoid/non-secure')) } const id = nanoid()

Security Risks

Predictability

Math.random() is not cryptographically secure:

import { nanoid } from 'nanoid/non-secure' const id1 = nanoid() const id2 = nanoid() const id3 = nanoid() // ❌ An attacker can predict id3 from id1 and id2

Why: Math.random() uses a pseudorandom number generator (PRNG) seeded by time. With enough samples, the seed can be determined.

Low Entropy

Math.random() provides only ~52 bits of entropy (vs 126 bits for secure version):

Secure nanoid: 2^126 possible values Non-secure nanoid: ~2^52 possible values (much easier to brute force)

Timing Attacks

Math.random() can be influenced by system time:

// If attacker knows approximate time of ID generation, // they can narrow down possible values significantly

Performance

Benchmarks

nanoid (secure) 3,693,964 ops/sec nanoid/non-secure 2,226,483 ops/sec

Paradox: Non-secure is slower than secure!

Why: The secure version uses pooled random generation (128 IDs per system call), while non-secure calls Math.random() for every character.

Bundle Size

nanoid 118 bytes nanoid/non-secure 90 bytes (28 bytes smaller)

Use case: Bundle size is the main reason to use non-secure, not performance.

Implementation Details

Source Code

// non-secure/index.js let urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' export let nanoid = (size = 21) => { let id = '' let i = size | 0 while (i--) { // Math.random() instead of crypto id += urlAlphabet[(Math.random() * 64) | 0] } return id } export let customAlphabet = (alphabet, defaultSize = 21) => { return (size = defaultSize) => { let id = '' let i = size | 0 while (i--) { id += alphabet[(Math.random() * alphabet.length) | 0] } return id } }

Key differences from secure version:

  • Uses Math.random() instead of crypto.getRandomValues()
  • No random pool (generates on demand)
  • Simpler algorithm (no rejection sampling)
  • Smaller code size

Migration

From Non-Secure to Secure

// Before import { nanoid } from 'nanoid/non-secure' // After (just change import) import { nanoid } from 'nanoid' // Everything else stays the same const id = nanoid()

Note: IDs generated by secure version will be different (different random source), but format is identical.

Gradual Migration

// Use secure for new IDs, keep non-secure for existing code import { nanoid as secureNanoid } from 'nanoid' import { nanoid as insecureNanoid } from 'nanoid/non-secure' // New security-critical code const sessionId = secureNanoid() // Legacy non-critical code (to be migrated) const tempId = insecureNanoid()

Best Practices

✅ Do’s

Use for development data:

import { nanoid } from 'nanoid/non-secure' const mockData = { id: nanoid(), name: 'Test User' }

Use for client-side temporary IDs:

const componentId = nanoid() // Never sent to server

Document the usage:

// SECURITY: Using non-secure IDs for temporary UI state only. // These IDs never persist or leave the client. import { nanoid } from 'nanoid/non-secure'

❌ Don’ts

Don’t use for session IDs:

// WRONG: Session IDs must be secure import { nanoid } from 'nanoid/non-secure' const sessionId = nanoid() // ❌ INSECURE!

Don’t use for database primary keys:

// WRONG: Primary keys should be secure import { nanoid } from 'nanoid/non-secure' const userId = nanoid() // ❌ PREDICTABLE! await db.users.insert({ id: userId, ... })

Don’t use for tokens:

// WRONG: Tokens must be cryptographically secure import { nanoid } from 'nanoid/non-secure' const apiKey = nanoid() // ❌ CAN BE GUESSED!

Alternatives

If you need fast, non-secure IDs:

Sequential IDs

let counter = 0 const getId = () => `id-${counter++}` getId() //=> "id-0" getId() //=> "id-1"

Pros: Faster, smaller, sortable
Cons: Not distributed-system-friendly

UUID v1 (Time-based)

import { v1 as uuidv1 } from 'uuid' const id = uuidv1() //=> "6c84fb90-12c4-11e1-840d-7b25c5ee775a"

Pros: Sortable by time, no collision
Cons: Leaks timestamp and MAC address

ULID

import { ulid } from 'ulid' const id = ulid() //=> "01ARZ3NDEKTSV4RRFFQ69G5FAV"

Pros: Sortable, compact, secure
Cons: Larger package size

See Also