Sintaxis de template literal types
Los template literal types trasladan la sintaxis de los template literals de JavaScript al sistema de tipos de TypeScript. En lugar de producir valores de cadena en tiempo de ejecución, producen nuevos tipos de cadena literal en tiempo de compilación.

La sintaxis es identica a la de los template literals de JavaScript, pero se utiliza en posiciones de tipo:
type Saludo = `Hola, ${string}`
const s1: Saludo = "Hola, mundo"
const s2: Saludo = "Hola, TypeScript"
// const s3: Saludo = "Adios, mundo" // Error: no cumple el patrón
El tipo ${string} actua como comodin que acepta cualquier cadena. También se pueden usar otros tipos primitivos como ${number} o ${boolean}:
type IdConPrefijo = `user-${number}`
const id1: IdConPrefijo = "user-42"
const id2: IdConPrefijo = "user-0"
// const id3: IdConPrefijo = "user-abc" // Error: "abc" no es number
Cuando se interpolan literales concretos, el resultado es un nuevo tipo literal de cadena:
type Mundo = "mundo"
type SaludoMundo = `Hola, ${Mundo}` // "Hola, mundo"
Los template literal types operan exclusivamente en el espacio de tipos. No generan código JavaScript ni afectan al rendimiento en tiempo de ejecución.
Combinación con tipos de union
La potencia real de los template literal types emerge al interpolar uniones de literales. TypeScript calcula automáticamente el producto cartesiano de todas las combinaciones posibles:
type Color = "rojo" | "verde" | "azul"
type Tamano = "pequeño" | "mediano" | "grande"
type ClaseCSS = `${Color}-${Tamano}`
// "rojo-pequeño" | "rojo-mediano" | "rojo-grande"
// | "verde-pequeño" | "verde-mediano" | "verde-grande"
// | "azul-pequeño" | "azul-mediano" | "azul-grande"
Con múltiples posiciones interpoladas, las uniones se multiplican entre si. Esto resulta especialmente útil para sistemas de diseño donde los valores válidos son combinaciones de partes:
type Variante = "primary" | "secondary" | "danger"
type Estado = "default" | "hover" | "active" | "disabled"
type ClaseBoton = `btn-${Variante}-${Estado}`
// Genera 12 combinaciones válidas
También se pueden combinar literales fijos con interpolaciones:
type Protocolo = "http" | "https"
type Dominio = "api.ejemplo.com" | "cdn.ejemplo.com"
type URL = `${Protocolo}://${Dominio}`
// "http://api.ejemplo.com" | "http://cdn.ejemplo.com"
// | "https://api.ejemplo.com" | "https://cdn.ejemplo.com"
Tipos de transformación de cadenas
TypeScript incluye cuatro tipos utilitarios integrados para transformar cadenas literales a nivel de tipo. Estos tipos están implementados directamente en el compilador:
type T1 = Uppercase<"hola mundo"> // "HOLA MUNDO"
type T2 = Lowercase<"HOLA MUNDO"> // "hola mundo"
type T3 = Capitalize<"hola mundo"> // "Hola mundo"
type T4 = Uncapitalize<"Hola Mundo"> // "hola Mundo"
Estos tipos son especialmente útiles cuando se combinan con uniones:
type Evento = "click" | "hover" | "focus" | "blur"
type Manejador = `on${Capitalize<Evento>}`
// "onClick" | "onHover" | "onFocus" | "onBlur"
Las transformaciones se pueden encadenar para lograr efectos más complejos:
type Accion = "GUARDAR_CAMBIOS" | "CARGAR_DATOS" | "ELIMINAR_ITEM"
type AccionNormalizada = Lowercase<Accion>
// "guardar_cambios" | "cargar_datos" | "eliminar_item"
type MetodoReducer = `handle${Capitalize<Lowercase<Accion>>}`
Los cuatro tipos de transformación de cadenas (Uppercase, Lowercase, Capitalize, Uncapitalize) son tipos intrinsecos del compilador. No se encuentran en archivos .d.ts y no son sensibles a la configuración regional.
Patrón: nombres de eventos tipados
Uno de los patrones más comunes es generar nombres de manejadores de eventos a partir de tipos de acción:
type AccionFormulario = "submit" | "reset" | "change" | "input"
type ManejadorFormulario = `on${Capitalize<AccionFormulario>}`
// "onSubmit" | "onReset" | "onChange" | "onInput"
type EventosFormulario = {
[K in ManejadorFormulario]?: () => void
}
const eventos: EventosFormulario = {
onSubmit: () => console.log("enviando"),
onChange: () => console.log("cambiando")
// onInvalido: () => {} // Error: no es un manejador válido
}
Este patrón se puede extender para crear un sistema de eventos completo con tipado de datos:
type EventosApp = {
"usuario:creado": { id: string; nombre: string }
"usuario:actualizado": { id: string; cambios: Record<string, unknown> }
"pedido:completado": { pedidoId: string; total: number }
}
type NombreEvento = keyof EventosApp
function emitir<K extends NombreEvento>(
evento: K,
datos: EventosApp[K]
): void {
console.log(`Evento: ${evento}`, datos)
}
emitir("usuario:creado", { id: "1", nombre: "Ana" })
// emitir("usuario:creado", { id: "1", total: 50 }) // Error: tipo incorrecto
Patrón: unidades CSS tipadas
Los template literal types permiten tipar valores CSS con unidades específicas:
type UnidadCSS = "px" | "em" | "rem" | "vh" | "vw" | "%"
type ValorCSS = `${number}${UnidadCSS}`
function establecerAncho(ancho: ValorCSS): void {
console.log(`width: ${ancho}`)
}
establecerAncho("100px")
establecerAncho("50%")
establecerAncho("3.5rem")
// establecerAncho("100") // Error: falta la unidad
// establecerAncho("grande") // Error: no cumple el patrón
Las variables CSS custom también se pueden tipar:
type VariableCSS = `--${string}`
type EstilosPersonalizados = {
[K: VariableCSS]: string | number
}
const estilos: EstilosPersonalizados = {
"--color-primario": "#3178c6",
"--espaciado-base": 8,
"--radio-borde": "4px"
// "color": "#000" // Error: no empieza con "--"
}
Patrón: rutas de API tipadas
Los template literal types son excelentes para construir rutas de API REST con seguridad de tipos:
type Recurso = "usuarios" | "productos" | "pedidos"
type RutaBase = `/api/${Recurso}`
type RutaConId = `/api/${Recurso}/${string}`
type EndpointAPI = RutaBase | RutaConId
function llamarAPI(endpoint: EndpointAPI): Promise<unknown> {
return fetch(endpoint).then(r => r.json())
}
llamarAPI("/api/usuarios")
llamarAPI("/api/productos/abc-123")
// llamarAPI("/api/desconocido") // Error
Se puede refinar este patrón para incluir el método HTTP:
type MetodoHTTP = "GET" | "POST" | "PUT" | "DELETE"
type DescriptorEndpoint = `${MetodoHTTP} ${EndpointAPI}`
type Rutas = {
"GET /api/usuarios": { respuesta: { id: string; nombre: string }[] }
"POST /api/usuarios": { cuerpo: { nombre: string; email: string } }
"DELETE /api/usuarios/${string}": { respuesta: void }
}
Combinación con mapped types
La combinación de template literal types con mapped types y la cláusula as permite crear transformaciones sofisticadas de tipos de objetos:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface Usuario {
nombre: string
edad: number
email: string
}
type GettersUsuario = Getters<Usuario>
// {
// getNombre: () => string
// getEdad: () => number
// getEmail: () => string
// }
El mismo patrón se aplica para generar setters:
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (valor: T[K]) => void
}
type SettersUsuario = Setters<Usuario>
// {
// setNombre: (valor: string) => void
// setEdad: (valor: number) => void
// setEmail: (valor: string) => void
// }
Combinando ambos, se puede construir un tipo completo de accesores:
type ConAccesores<T> = T & Getters<T> & Setters<T>
type UsuarioConAccesores = ConAccesores<Usuario>
Se pueden filtrar propiedades durante la transformación produciendo never:
type SoloGettersString<T> = {
[K in keyof T as T[K] extends string
? `get${Capitalize<string & K>}`
: never
]: () => T[K]
}
type GettersStringUsuario = SoloGettersString<Usuario>
// { getNombre: () => string; getEmail: () => string }
Inferencia con infer en template literal types
La palabra clave infer dentro de un template literal type permite extraer partes de una cadena literal:
type ExtraerMetodo<T extends string> =
T extends `${infer Método} ${string}` ? Método : never
type M1 = ExtraerMetodo<"GET /api/usuarios"> // "GET"
type M2 = ExtraerMetodo<"POST /api/pedidos"> // "POST"
Se pueden extraer múltiples segmentos simultáneamente:
type ExtraerPartes<T extends string> =
T extends `${infer Dominio}:${infer Accion}`
? { dominio: Dominio; accion: Accion }
: never
type P1 = ExtraerPartes<"usuario:creado">
// { dominio: "usuario"; accion: "creado" }
type P2 = ExtraerPartes<"pedido:completado">
// { dominio: "pedido"; accion: "completado" }
La inferencia se puede combinar con transformaciones de cadena:
type EventoAManejador<T extends string> =
T extends `${infer Dominio}:${infer Accion}`
? `on${Capitalize<Dominio>}${Capitalize<Accion>}`
: never
type H1 = EventoAManejador<"usuario:creado"> // "onUsuarioCreado"
type H2 = EventoAManejador<"pedido:completado"> // "onPedidoCompletado"
Parseo recursivo de cadenas
La inferencia dentro de template literals habilita el parseo recursivo de cadenas:
type DividirPorPunto<T extends string> =
T extends `${infer Cabeza}.${infer Cola}`
? [Cabeza, ...DividirPorPunto<Cola>]
: [T]
type R1 = DividirPorPunto<"a.b.c"> // ["a", "b", "c"]
type R2 = DividirPorPunto<"usuario.nombre"> // ["usuario", "nombre"]
type R3 = DividirPorPunto<"simple"> // ["simple"]
Este patrón se puede usar para crear un tipo de acceso a propiedades anidadas:
type AccesoAnidado<T, Ruta extends string> =
Ruta extends `${infer Clave}.${infer Resto}`
? Clave extends keyof T
? AccesoAnidado<T[Clave], Resto>
: never
: Ruta extends keyof T
? T[Ruta]
: never
interface Config {
base: {
url: string
puerto: number
}
auth: {
secreto: string
}
}
type TipoUrl = AccesoAnidado<Config, "base.url"> // string
type TipoPuerto = AccesoAnidado<Config, "base.puerto"> // number
type TipoSecreto = AccesoAnidado<Config, "auth.secreto"> // string
Ejemplo integrado: sistema de rutas tipado
Un caso de uso avanzado combina template literal types, mapped types e inferencia para construir un enrutador completamente tipado:
type Rutas = {
"/usuarios": { lista: true }
"/usuarios/:id": { detalle: true }
"/productos": { lista: true }
"/productos/:id/variantes": { anidada: true }
}
type ExtraerParametros<T extends string> =
T extends `${string}:${infer Param}/${infer Resto}`
? { [K in Param]: string } & ExtraerParametros<Resto>
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {}
type ParamsUsuario = ExtraerParametros<"/usuarios/:id">
// { id: string }
type ParamsVariante = ExtraerParametros<"/productos/:id/variantes">
// { id: string }
function navegar<R extends keyof Rutas>(
ruta: R,
...args: keyof ExtraerParametros<R> extends never
? []
: [params: ExtraerParametros<R>]
): void {
console.log("Navegando a:", ruta, args[0])
}
navegar("/usuarios")
navegar("/usuarios/:id", { id: "42" })
// navegar("/usuarios/:id") // Error: faltan parámetros
Este patrón demuestra como los template literal types, combinados con inferencia condicional y mapped types, permiten modelar APIs complejas con total seguridad de tipos a nivel del compilador.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Dominar la sintaxis de template literal types, los tipos de transformación de cadenas integrados, la combinación con uniones para generar permutaciones de cadenas, patrones practicos con nombres de eventos, unidades CSS y rutas de API, y la inferencia dentro de template literals con infer.