Skip to Content
GuidesTypescript Usage

Last Updated: 3/9/2026


TypeScript Usage

Nano ID works seamlessly with TypeScript and supports advanced type patterns.

Basic Usage

Nano ID works out of the box with TypeScript:

import { nanoid } from 'nanoid' const id: string = nanoid() // Type: string

No additional type definitions needed - types are included in the package.

Type Definitions

nanoid()

function nanoid(size?: number): string

customAlphabet()

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

customRandom()

function customRandom( alphabet: string, size: number, random: (bytes: number) => Uint8Array ): () => string

Opaque Types (Branded Types)

Create type-safe ID types that can’t be accidentally mixed:

import { nanoid } from 'nanoid' // Declare unique brands declare const userIdBrand: unique symbol declare const postIdBrand: unique symbol // Create branded types type UserId = string & { [userIdBrand]: true } type PostId = string & { [postIdBrand]: true } // Type-safe ID generation function createUserId(): UserId { return nanoid() as UserId } function createPostId(): PostId { return nanoid() as PostId } // Usage const userId = createUserId() const postId = createPostId() // Type checking prevents mixing function getUser(id: UserId) { /* ... */ } function getPost(id: PostId) { /* ... */ } getUser(userId) // ✅ OK getPost(postId) // ✅ OK getUser(postId) // ❌ Type error! getPost(userId) // ❌ Type error!

Benefits

Compile-time safety - Can’t pass wrong ID type ✅ Self-documenting - Function signatures show expected ID type ✅ Refactoring safety - TypeScript catches ID type mismatches

Generic Type Parameters

Nano ID supports explicit type casting:

import { nanoid } from 'nanoid' declare const userIdBrand: unique symbol type UserId = string & { [userIdBrand]: true } // Explicit type parameter const id = nanoid<UserId>() // Type: UserId // Or use type assertion const id2: UserId = nanoid() // Also works

Interfaces and Types

Database Models

import { nanoid } from 'nanoid' interface User { id: string email: string createdAt: number } function createUser(email: string): User { return { id: nanoid(), email, createdAt: Date.now() } }

With Branded Types

import { nanoid } from 'nanoid' declare const userIdBrand: unique symbol type UserId = string & { [userIdBrand]: true } interface User { id: UserId email: string createdAt: number } function createUser(email: string): User { return { id: nanoid() as UserId, // Cast to branded type email, createdAt: Date.now() } } // Type-safe lookups function findUser(id: UserId): Promise<User | null> { return db.users.findOne({ id }) }

React with TypeScript

Component Props

import { nanoid } from 'nanoid' import { useState } from 'react' interface Todo { id: string text: string completed: boolean } interface TodoListProps { initialTodos?: Todo[] } function TodoList({ initialTodos = [] }: TodoListProps) { const [todos, setTodos] = useState<Todo[]>(initialTodos) const addTodo = (text: string) => { const newTodo: Todo = { id: nanoid(), text, completed: false } setTodos([...todos, newTodo]) } return ( <ul> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ) }

With Branded Types

import { nanoid } from 'nanoid' import { useState } from 'react' declare const todoIdBrand: unique symbol type TodoId = string & { [todoIdBrand]: true } interface Todo { id: TodoId text: string completed: boolean } function TodoList() { const [todos, setTodos] = useState<Todo[]>([]) const addTodo = (text: string) => { const newTodo: Todo = { id: nanoid() as TodoId, text, completed: false } setTodos([...todos, newTodo]) } const toggleTodo = (id: TodoId) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo )) } return ( <ul> {todos.map(todo => ( <li key={todo.id} onClick={() => toggleTodo(todo.id)}> {todo.text} </li> ))} </ul> ) }

Custom Alphabet with Types

import { customAlphabet } from 'nanoid' // Type-safe custom alphabet const nanoidHex = customAlphabet('0123456789abcdef', 16) const id: string = nanoidHex() // Type: string // With branded type declare const hexIdBrand: unique symbol type HexId = string & { [hexIdBrand]: true } function createHexId(): HexId { return nanoidHex() as HexId }

API Responses

Type-Safe API Models

import { nanoid } from 'nanoid' declare const userIdBrand: unique symbol declare const sessionIdBrand: unique symbol type UserId = string & { [userIdBrand]: true } type SessionId = string & { [sessionIdBrand]: true } interface LoginResponse { userId: UserId sessionId: SessionId expiresAt: number } function createSession(userId: UserId): LoginResponse { return { userId, sessionId: nanoid() as SessionId, expiresAt: Date.now() + 3600000 } } // Type-safe session validation function validateSession(sessionId: SessionId): boolean { return sessions.has(sessionId) }

Utility Types

ID Factory

import { nanoid } from 'nanoid' // Generic ID factory function createIdFactory<T extends string>() { return (): T => nanoid() as T } // Create specific factories declare const userIdBrand: unique symbol declare const postIdBrand: unique symbol type UserId = string & { [userIdBrand]: true } type PostId = string & { [postIdBrand]: true } const createUserId = createIdFactory<UserId>() const createPostId = createIdFactory<PostId>() const userId = createUserId() // Type: UserId const postId = createPostId() // Type: PostId

Entity Base Type

import { nanoid } from 'nanoid' declare const entityIdBrand: unique symbol type EntityId = string & { [entityIdBrand]: true } interface Entity { id: EntityId createdAt: number updatedAt: number } interface User extends Entity { email: string name: string } interface Post extends Entity { title: string content: string authorId: EntityId } function createEntity<T extends Omit<Entity, 'id' | 'createdAt' | 'updatedAt'>>( data: T ): T & Entity { const now = Date.now() return { ...data, id: nanoid() as EntityId, createdAt: now, updatedAt: now } } const user = createEntity<Omit<User, keyof Entity>>({ email: 'user@example.com', name: 'Alice' }) // Type: User (with id, createdAt, updatedAt)

Strict Null Checks

Nano ID always returns a string, never null/undefined:

import { nanoid } from 'nanoid' // With strict null checks enabled const id = nanoid() // Type: string (never null | undefined) // No need for null checks function saveId(id: string) { localStorage.setItem('id', id) // ✅ Safe } saveId(nanoid()) // ✅ Always works

Type Guards

Validate ID format at runtime:

import { nanoid } from 'nanoid' declare const userIdBrand: unique symbol type UserId = string & { [userIdBrand]: true } // Type guard for branded IDs function isUserId(value: unknown): value is UserId { return ( typeof value === 'string' && value.length === 21 && /^[A-Za-z0-9_-]+$/.test(value) ) } // Usage function getUser(id: unknown) { if (!isUserId(id)) { throw new Error('Invalid user ID') } // Type is now UserId return db.users.findOne({ id }) }

Discriminated Unions

Type-safe entity handling:

import { nanoid } from 'nanoid' type EntityType = 'user' | 'post' | 'comment' interface BaseEntity { id: string type: EntityType } interface User extends BaseEntity { type: 'user' email: string } interface Post extends BaseEntity { type: 'post' title: string } interface Comment extends BaseEntity { type: 'comment' text: string } type Entity = User | Post | Comment function createEntity<T extends EntityType>( type: T, data: Omit<Extract<Entity, { type: T }>, 'id' | 'type'> ): Extract<Entity, { type: T }> { return { id: nanoid(), type, ...data } as Extract<Entity, { type: T }> } const user = createEntity('user', { email: 'user@example.com' }) // Type: User const post = createEntity('post', { title: 'Hello' }) // Type: Post

Best Practices

✅ Do

// Use branded types for type safety declare const userIdBrand: unique symbol type UserId = string & { [userIdBrand]: true } // Create factory functions function createUserId(): UserId { return nanoid() as UserId } // Use interfaces for entities interface User { id: UserId email: string }

❌ Don’t

// Don't use 'any' const id: any = nanoid() // ❌ Loses type safety // Don't mix ID types without branded types function getUser(id: string) { } // ❌ Accepts any string function getPost(id: string) { } // ❌ Can't distinguish from user ID // Don't forget to cast when using branded types const user: User = { id: nanoid(), // ❌ Type error if User.id is UserId email: 'user@example.com' }

Common Patterns

Repository Pattern

import { nanoid } from 'nanoid' declare const userIdBrand: unique symbol type UserId = string & { [userIdBrand]: true } interface User { id: UserId email: string name: string } class UserRepository { async create(data: Omit<User, 'id'>): Promise<User> { const user: User = { id: nanoid() as UserId, ...data } await db.users.insert(user) return user } async findById(id: UserId): Promise<User | null> { return db.users.findOne({ id }) } async update(id: UserId, data: Partial<Omit<User, 'id'>>): Promise<User> { await db.users.update({ id }, data) return this.findById(id) as Promise<User> } }