The as const assertion creates the narrowest possible type from a value. Here's how to use it.
Basic Usage#
1// Without as const - widened types
2let color = 'red'; // string
3let count = 42; // number
4let active = true; // boolean
5
6// With as const - literal types
7let color2 = 'red' as const; // 'red'
8let count2 = 42 as const; // 42
9let active2 = true as const; // true
10
11// Object without as const
12const config = {
13 api: 'https://api.example.com',
14 timeout: 5000,
15};
16// { api: string; timeout: number }
17
18// Object with as const
19const config2 = {
20 api: 'https://api.example.com',
21 timeout: 5000,
22} as const;
23// { readonly api: 'https://api.example.com'; readonly timeout: 5000 }Arrays and Tuples#
1// Array without as const
2const colors = ['red', 'green', 'blue'];
3// string[]
4
5// Array with as const - becomes readonly tuple
6const colors2 = ['red', 'green', 'blue'] as const;
7// readonly ['red', 'green', 'blue']
8
9// Access specific element type
10type FirstColor = typeof colors2[0]; // 'red'
11type AllColors = typeof colors2[number]; // 'red' | 'green' | 'blue'
12
13// Tuple with as const
14const point = [10, 20] as const;
15// readonly [10, 20]
16
17// Mixed array
18const mixed = [1, 'hello', true] as const;
19// readonly [1, 'hello', true]
20
21// Function arguments
22function setPosition(x: number, y: number) {
23 console.log(x, y);
24}
25
26const coords = [10, 20] as const;
27setPosition(...coords); // Works - TypeScript knows it's [number, number]Enum-like Constants#
1// Object as enum
2const Direction = {
3 Up: 'UP',
4 Down: 'DOWN',
5 Left: 'LEFT',
6 Right: 'RIGHT',
7} as const;
8
9type Direction = typeof Direction[keyof typeof Direction];
10// 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
11
12function move(direction: Direction) {
13 console.log(`Moving ${direction}`);
14}
15
16move(Direction.Up); // OK
17move('UP'); // OK
18move('DIAGONAL'); // Error
19
20// Numeric enum-like
21const HttpStatus = {
22 OK: 200,
23 Created: 201,
24 BadRequest: 400,
25 NotFound: 404,
26 ServerError: 500,
27} as const;
28
29type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus];
30// 200 | 201 | 400 | 404 | 500
31
32// With descriptions
33const ErrorCode = {
34 NotFound: { code: 404, message: 'Not Found' },
35 Unauthorized: { code: 401, message: 'Unauthorized' },
36 ServerError: { code: 500, message: 'Server Error' },
37} as const;
38
39type ErrorCodeKey = keyof typeof ErrorCode;
40// 'NotFound' | 'Unauthorized' | 'ServerError'Immutable Objects#
1// Deep readonly with as const
2const theme = {
3 colors: {
4 primary: '#007bff',
5 secondary: '#6c757d',
6 success: '#28a745',
7 },
8 spacing: {
9 small: 8,
10 medium: 16,
11 large: 24,
12 },
13 breakpoints: {
14 mobile: 480,
15 tablet: 768,
16 desktop: 1024,
17 },
18} as const;
19
20// All nested properties are readonly
21// theme.colors.primary = '#000'; // Error: Cannot assign to readonly property
22
23// Type extraction
24type Theme = typeof theme;
25type Colors = typeof theme.colors;
26type ColorValue = typeof theme.colors[keyof typeof theme.colors];
27// '#007bff' | '#6c757d' | '#28a745'
28
29// Accessing values
30function getPrimaryColor(): typeof theme.colors.primary {
31 return theme.colors.primary;
32}Function Parameters#
1// Literal type parameters
2function createAction<T extends string>(type: T) {
3 return { type };
4}
5
6// Without as const
7const action1 = createAction('INCREMENT');
8// { type: string }
9
10// With as const
11const action2 = createAction('INCREMENT' as const);
12// { type: 'INCREMENT' }
13
14// Or using const type parameter (TS 5.0+)
15function createAction2<const T extends string>(type: T) {
16 return { type };
17}
18
19const action3 = createAction2('INCREMENT');
20// { type: 'INCREMENT' } - automatically inferred as literal
21
22// Options object
23function configure<const T extends { mode: string; debug: boolean }>(
24 options: T
25) {
26 return options;
27}
28
29const opts = configure({ mode: 'production', debug: false });
30// { mode: 'production'; debug: false }Route Definitions#
1// API routes
2const routes = {
3 home: '/',
4 users: '/users',
5 userDetail: '/users/:id',
6 posts: '/posts',
7 postDetail: '/posts/:id',
8} as const;
9
10type Route = typeof routes[keyof typeof routes];
11// '/' | '/users' | '/users/:id' | '/posts' | '/posts/:id'
12
13function navigate(route: Route) {
14 // Type-safe navigation
15}
16
17navigate(routes.home); // OK
18navigate('/users'); // OK
19navigate('/invalid'); // Error
20
21// Route config with methods
22const apiRoutes = {
23 getUsers: { path: '/users', method: 'GET' },
24 createUser: { path: '/users', method: 'POST' },
25 getUser: { path: '/users/:id', method: 'GET' },
26 updateUser: { path: '/users/:id', method: 'PUT' },
27 deleteUser: { path: '/users/:id', method: 'DELETE' },
28} as const;
29
30type ApiRoute = typeof apiRoutes[keyof typeof apiRoutes];
31// { readonly path: '/users'; readonly method: 'GET' } | ...Action Types (Redux-like)#
1// Action type constants
2const ActionTypes = {
3 ADD_TODO: 'todos/ADD_TODO',
4 REMOVE_TODO: 'todos/REMOVE_TODO',
5 TOGGLE_TODO: 'todos/TOGGLE_TODO',
6 SET_FILTER: 'filter/SET_FILTER',
7} as const;
8
9// Action creators with literal types
10function addTodo(text: string) {
11 return {
12 type: ActionTypes.ADD_TODO,
13 payload: { text },
14 } as const;
15}
16
17function removeTodo(id: number) {
18 return {
19 type: ActionTypes.REMOVE_TODO,
20 payload: { id },
21 } as const;
22}
23
24// Union of all actions
25type Action = ReturnType<typeof addTodo> | ReturnType<typeof removeTodo>;
26
27// Reducer with discriminated union
28function reducer(state: State, action: Action) {
29 switch (action.type) {
30 case ActionTypes.ADD_TODO:
31 // action.payload is { text: string }
32 return { ...state, todos: [...state.todos, action.payload.text] };
33 case ActionTypes.REMOVE_TODO:
34 // action.payload is { id: number }
35 return {
36 ...state,
37 todos: state.todos.filter((_, i) => i !== action.payload.id),
38 };
39 }
40}Validation Schemas#
1// Form field types
2const FieldTypes = {
3 text: 'text',
4 email: 'email',
5 password: 'password',
6 number: 'number',
7 select: 'select',
8} as const;
9
10type FieldType = typeof FieldTypes[keyof typeof FieldTypes];
11
12// Schema definition
13const userSchema = {
14 name: { type: 'text', required: true },
15 email: { type: 'email', required: true },
16 age: { type: 'number', required: false },
17 role: { type: 'select', options: ['admin', 'user', 'guest'] },
18} as const;
19
20// Extract field names
21type UserFields = keyof typeof userSchema;
22// 'name' | 'email' | 'age' | 'role'
23
24// Extract role options
25type RoleOption = typeof userSchema.role.options[number];
26// 'admin' | 'user' | 'guest'Event Maps#
1// Event name to payload mapping
2const Events = {
3 USER_LOGIN: 'user:login',
4 USER_LOGOUT: 'user:logout',
5 ITEM_ADDED: 'cart:item_added',
6 ITEM_REMOVED: 'cart:item_removed',
7} as const;
8
9type EventName = typeof Events[keyof typeof Events];
10
11interface EventPayloads {
12 'user:login': { userId: string; timestamp: Date };
13 'user:logout': { userId: string };
14 'cart:item_added': { itemId: string; quantity: number };
15 'cart:item_removed': { itemId: string };
16}
17
18function emit<E extends EventName>(
19 event: E,
20 payload: EventPayloads[E]
21) {
22 // Type-safe event emission
23}
24
25emit(Events.USER_LOGIN, {
26 userId: '123',
27 timestamp: new Date(),
28});Combining with satisfies#
1// Validate type while preserving literals
2type Config = {
3 env: string;
4 port: number;
5 debug: boolean;
6};
7
8const config = {
9 env: 'production',
10 port: 3000,
11 debug: false,
12} as const satisfies Config;
13
14// config.env is 'production', not string
15// config.port is 3000, not number
16// But it's validated against Config type
17
18// Error if invalid
19const badConfig = {
20 env: 'production',
21 port: '3000', // Error: Type 'string' not assignable to 'number'
22 debug: false,
23} as const satisfies Config;Best Practices#
Usage:
✓ Use for enum-like constants
✓ Use for configuration objects
✓ Use for route definitions
✓ Use for action types
Benefits:
✓ Narrowest possible types
✓ Deep readonly
✓ Literal type inference
✓ Better autocomplete
Patterns:
✓ Combine with typeof
✓ Extract union types
✓ Use with satisfies
✓ Create type-safe enums
Avoid:
✗ On mutable data
✗ When you need assignment
✗ For temporary values
✗ Overusing everywhere
Conclusion#
The as const assertion creates the narrowest literal types and makes values deeply readonly. Use it for configuration, constants, enum alternatives, and action types. Combine with typeof and keyof to extract useful types, and use satisfies for validation while preserving literal types.