Vol. 6 · № 03 · Un atelier TypeScriptA TypeScript workshop

Template literal types & les utilitaires, ou quand s'arrêter Template literal types & utilities, or when to stop

Le sommet du langage des types : composer des types à partir de chaînes — `on${Capitalize<K>}` —, multiplier des unions, parser avec infer. La synthèse du Vol 6, et son frein. La puissance est telle qu'on encode l'illisible sans s'en rendre compte. La seule question : ce type calculé m'achète-t-il une garantie de synchro réelle, ou est-ce que je me prouve juste que je sais le faire ? The summit of the type language: composing types from strings — `on${Capitalize<K>}` —, multiplying unions, parsing with infer. Vol 6's synthesis, and its brake. The power is such that you encode the unreadable without noticing. The only question: does this computed type buy me a real sync guarantee, or am I just proving I can do it?

AudienceAudience
Dev qui manie mapped et conditionnels, et doit apprendre à ne pas en abuser Dev fluent in mapped and conditional types, who must learn not to overdo it
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●● Template literalsUtility typesTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Des noms de type fabriqués à partir de chaînes — et qui suivent leur source.Type names built from strings — and tracking their source.

Dernière marche du langage des types : composer des types à partir de chaînes. Un ensemble d'événements (click, focus, blur) appelle un ensemble de handlers (onClick, onFocus, onBlur) — deux listes qui doivent rester en phase. Réécrites à la main, elles divergent au premier ajout. Le template literal type les dérive : on prend les clés, on les capitalise, on les préfixe — et les handlers se recalculent à chaque changement de la source.The top step of the type language: composing types from strings. A set of events (click, focus, blur) calls for a set of handlers (onClick, onFocus, onBlur) — two lists that must stay in sync. Hand-written, they diverge on the first addition. The template literal type derives them: take the keys, capitalize, prefix — and the handlers recompute on every source change.

Réécrit à la main — un handler oublié, en silenceHand-written — a handler forgotten, silently
type Events = { click: MouseEvent; focus: FocusEvent; blur: FocusEvent };

// les props handlers, réécrites à la main — un event ajouté, un handler oublié :
type Handlers = {
  onClick: (e: MouseEvent) => void;
  onFocus: (e: FocusEvent) => void;
  // onBlur manquant — et rien ne le signale
};
Les noms dérivés suivent-ils la source ?Do the derived names track the source?
Réécrit à la mainHand-written
deux listes à synchronisertwo lists to keep in sync
un event ajouté, un handler manquantadd an event, miss a handler
Template literal typeTemplate literal type
les noms, calculésthe names, computed
dérivés des clés à chaque buildderived from the keys on each build

Un nom de propriété peut désormais être un calcul : on + clé capitalisée. Le template literal type fait ce que ni le mapped ni le conditionnel ne savaient — fabriquer la chaîne elle-même, et la garder en phase avec sa source.A property name can now be a computation: on + capitalized key. The template literal type does what neither the mapped nor the conditional could — build the string itself, and keep it in sync with its source.

Le template literal type — les noms, dérivésThe template literal type — the names, derived
type Events = { click: MouseEvent; focus: FocusEvent; blur: FocusEvent };

type Handlers = {
  [K in keyof Events as `on${Capitalize<K & string>}`]: (e: Events[K]) => void;
};
// ≡ { onClick; onFocus; onBlur } — CALCULÉ depuis Events, jamais désynchronisé
Le réflexe du numéroThe issue's reflex

« Ce type calculé me fait-il gagner une garantie de synchro réelle — ou est-ce que je me prouve juste que je sais le faire ? » C'est le réflexe de tout le volume, et il atteint ici son point le plus tranchant : la puissance est maximale, donc la tentation aussi."Does this computed type buy me a real sync guarantee — or am I just proving I can do it?" It's the whole volume's reflex, and here it reaches its sharpest point: the power is maximal, so is the temptation.

Pourquoi ce numéro clôt le Vol 6.Why this issue closes Vol 6.

C'est la synthèse — mapped (6.1) + conditionnel et infer (6.2) + chaînes — et le frein. Le template literal type est le sommet du langage des types ; ce numéro est donc le plus prudent du volume. On y montre tout ce qu'on peut faire, puis on insiste sur ce qu'il ne faut pas : savoir s'arrêter.It's the synthesis — mapped (6.1) + conditional and infer (6.2) + strings — and the brake. The template literal type is the type language's summit; so this issue is the volume's most cautious. We show everything you can do, then stress what you shouldn't: knowing when to stop.

№ 02La surpriseThe surprise4 min

Un template n'assemble pas : il multiplie les unions — et sait parser une chaîne.A template doesn't assemble: it multiplies unions — and can parse a string.

Deux surprises. D'abord, un template sur des unions fait un produit cartésien : 2 tailles × 2 couleurs donnent 4 littéraux, et l'on génère des milliers de types sans s'en rendre compte. Ensuite, combiné à infer, le template ne se contente pas de composer — il décompose : il reconnaît une forme de chaîne et en capture les morceaux. On parse GET /users au niveau type. Puissance grisante, et c'est exactement là que commence le danger.Two surprises. First, a template over unions makes a cartesian product: 2 sizes × 2 colors give 4 literals, and you generate thousands of types without realizing it. Second, combined with infer, the template doesn't just compose — it decomposes: it recognizes a string shape and captures its pieces. You parse GET /users at the type level. Heady power, and that's exactly where the danger begins.

Chaque union multiplie le nombre de littérauxEach union multiplies the literal count
type Size  = "sm" | "lg";
type Color = "red" | "blue";

type Variant = `${Size}-${Color}`;
// "sm-red" | "sm-blue" | "lg-red" | "lg-blue"  — produit cartésien : 2 × 2 = 4
// 5 tailles × 8 couleurs × 4 états = 160 littéraux générés d'un coup

Le template applique chaque combinaison : toutes les tailles × toutes les couleurs. Le compte explose multiplicativement — quelques unions modestes suffisent à générer des milliers de littéraux, que tsc doit tous matérialiser. La commodité d'écriture cache un coût qui grimpe vite.The template applies every combination: all sizes × all colors. The count explodes multiplicatively — a few modest unions suffice to generate thousands of literals, all of which tsc must materialize. The writing convenience hides a cost that climbs fast.

Le template ne colle pas des chaînes — il les multiplie et les décortique. C'est un langage de manipulation de texte, glissé dans le système de types.The template doesn't glue strings — it multiplies and dissects them. It's a text-manipulation language, slipped into the type system.
№ 03La mécaniqueThe mechanics5 min

Composer, transformer la casse, remapper les clés, parser — tout le volume réuni.Compose, change case, remap keys, parse — the whole volume, together.

Quatre briques. La syntaxe template (préfixe, interpolation, suffixe) compose une chaîne. Quatre utilitaires intégrés — Uppercase, Lowercase, Capitalize, Uncapitalize — transforment la casse. Le key remapping du 6.1 (le as dans un mapped type) renomme les clés via un template. Et infer du 6.2 parse une chaîne. Mis ensemble, c'est de la vraie programmation de chaînes au niveau type — la synthèse de tout le Vol 6 en une seule expression.Four bricks. Template syntax (prefix, interpolation, suffix) composes a string. Four built-in utilities — Uppercase, Lowercase, Capitalize, Uncapitalize — change case. The key remapping from 6.1 (the as in a mapped type) renames keys via a template. And infer from 6.2 parses a string. Put together, it's genuine type-level string programming — the synthesis of all of Vol 6 in a single expression.

Les utilitaires de casse, intégrés au compilateurThe case utilities, built into the compiler
type A = Uppercase<"get">;       // "GET"
type B = Capitalize<"click">;    // "Click"
type C = `on${Capitalize<"focus">}`;   // "onFocus"
// Uppercase, Lowercase, Capitalize, Uncapitalize : intégrés au compilateur
La synthèse — mapped + template + capitalizeThe synthesis — mapped + template + capitalize
// La synthèse du volume : mapped + remapping as (6.1) + template (6.3).
type Getters<T> = {
  [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};

type U = Getters<{ name: string; age: number }>;
//   { getName: () => string; getAge: () => number }
La trousse des chaînes-typesThe string-type toolkit
ComposerCompose
`${A}-${B}`concatène / multiplieconcatenate / multiply
Capitalize<S>transforme la cassetransforms case
Uppercase<S>intégré au compilateurcompiler built-in
Remapper & parserRemap & parse
[K in … as renomme les clés (6.1)renames keys (6.1)
${infer X}capture un morceau (6.2)captures a piece (6.2)
string & Kcontraint K aux chaînesconstrains K to strings
Les voisins, seulement nommés.The neighbors, only named.

Les quatre utilitaires de casse sont intrinsèques : le compilateur les implémente, on ne peut pas les réécrire en TypeScript. Vrais usages : routes typées (`/users/${string}`), émetteurs d'événements, clés de traduction. Le & string sert à filtrer les clés non-chaînes (une clé peut être number ou symbol) avant Capitalize. Et tout cela s'efface à l'exécution (Vol 2) : ça décrit des chaînes, ça n'en valide aucune.The four case utilities are intrinsic: the compiler implements them, you can't rewrite them in TypeScript. Real uses: typed routes (`/users/${string}`), event emitters, translation keys. The & string filters out non-string keys (a key can be number or symbol) before Capitalize. And all of it erases at runtime (Vol 2): it describes strings, it validates none.

№ 04Le piègeThe trap4 min

La type-gymnastique : impressionnante, lente, illisible — pour zéro garantie de plus.Type-gymnastics: impressive, slow, unreadable — for zero extra guarantee.

Voici le contre-exemple obligatoire du volume entier. Parser les paramètres d'une route en un type objet, par template récursif et infer, est faisable — et c'est précisément le piège. Le type met une fraction de seconde à se résoudre, l'éditeur l'affiche en points de suspension, et personne ne sait pourquoi un cas tombe sur l'objet vide. La question n'est jamais « est-ce possible ? », mais « est-ce que ça m'achète une synchro réelle, ou est-ce que je me prouve que je sais le faire ? ».Here's the whole volume's mandatory counter-example. Parsing a route's params into an object type, by recursive template and infer, is doable — and that's precisely the trap. The type takes a fraction of a second to resolve, the editor shows it as an ellipsis, and no one knows why a case lands on the empty object. The question is never 'is it possible?', but 'does it buy me a real sync, or am I proving I can do it?'.

Un parseur récursif que personne ne reliraA recursive parser no one will reread
// "Parser les params d'une route en un objet typé" — au niveau type :
type Params<P> =
  P extends `${string}:${infer Name}/${infer Rest}`
    ? { [K in Name | keyof Params<`/${Rest}`>]: string }
    : P extends `${string}:${infer Last}`
      ? { [K in Last]: string }
      : {};

type R = Params<"/users/:id/posts/:postId">;   // { id: string; postId: string }
// récursif, illisible, lent à typer — et indéboguable quand un cas tombe sur {}

Ça marche, et c'est le problème : la prouesse fait oublier le coût. Lent à typer, illisible, sans débogueur — et la « garantie » n'existe que si les routes changent souvent et restent l'unique source. Pour un projet où elles bougent deux fois l'an, c'est une dette pure.It works, and that's the problem: the feat hides the cost. Slow to typecheck, unreadable, no debugger — and the "guarantee" only exists if the routes change often and stay the single source. For a project where they move twice a year, it's pure debt.

La contagion, et le même réflexe que tout le long.The contagion, and the same reflex throughout.

La magie au niveau type est contagieuse : un helper malin en appelle un autre, et six mois plus tard personne — toi compris — n'ose y toucher. C'est la même discipline que le sur-branding (3.3), la sur-contrainte (5.2), l'unknown partout (4.2) : la cérémonie coûte, ne la dépense que pour une garantie réelle. Ici le prix est invisible jusqu'à ce que tsc rampe — d'où la prudence maximale.Type-level magic is contagious: one clever helper calls for another, and six months on no one — you included — dares touch it. It's the same discipline as over-branding (3.3), over-constraining (5.2), unknown-everywhere (4.2): ceremony costs, spend it only for a real guarantee. Here the price is invisible until tsc crawls — hence the maximal caution.

« Puis-je l'écrire au niveau type ? » est presque toujours oui. « Est-ce que ça m'achète une garantie que je n'aurais pas autrement ? » est la seule question qui compte."Can I write it at the type level?" is almost always yes. "Does it buy me a guarantee I wouldn't have otherwise?" is the only question that matters.
№ 05Bilan & éditoWrap-up & editorial2 min

Le sommet du langage des types — et le moment de savoir s'arrêter.The summit of the type language — and the moment to know when to stop.

Le template literal type fabrique des types à partir de chaînes : composer, multiplier, transformer la casse, remapper des clés, parser avec infer. C'est la synthèse du Vol 6, et son sommet. Mais ce numéro est aussi le frein : la puissance est telle qu'on encode l'illisible sans s'en rendre compte. La seule question : ce type calculé m'achète-t-il une garantie de synchro réelle, ou est-ce que je me prouve que je sais le faire ?The template literal type builds types from strings: compose, multiply, transform case, remap keys, parse with infer. It's Vol 6's synthesis, and its summit. But this issue is also the brake: the power is such that you encode the unreadable without realizing. The only question: does this computed type buy me a real sync guarantee, or am I proving I can do it?

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Composer un type depuis des chaînesCompose a type from stringsTemplate literal `${A}${B}` + Capitalize…Template literal `${A}${B}` + Capitalize…Produit cartésien : peut exploser en milliers de littéraux.Cartesian product: can explode into thousands of literals.
Dériver des clés d'autres clésDerive keys from other keysKey remapping [K in keyof T as …].Key remapping [K in keyof T as …].Lisible tant que le motif reste simple.Readable as long as the pattern stays simple.
Parser une chaîne au niveau typeParse a string at the type levelinfer dans un template.infer in a template.Puissant — et le plus souvent trop malin.Powerful — and most often too clever.
Ne PAS l'employerNOT use itLa gymnastique là où un type écrit suffit.Gymnastics where a written type suffices.Illisible, lente à compiler — pour zéro garantie de plus.Unreadable, slow — for zero extra guarantee.
Tout ce qu'on calcule au niveau type s'efface à l'exécution. Ça décrit des chaînes ; ça n'en valide aucune — et la prouesse ne vaut que la garantie qu'elle tient.Everything you compute at the type level erases at runtime. It describes strings; it validates none — and the feat is worth only the guarantee it holds.
Mot de l'éditeurFrom the editor

« Quand s'arrêter. »"When to stop."

Le template literal type est le sommet du langage dans le langage : avec lui, on compose, on multiplie, on parse des chaînes, on combine tout ce que les deux numéros précédents ont posé. C'est réellement puissant — des routes typées, des noms de handlers, des clés de traduction qui ne dérivent jamais de leur source. Et c'est précisément pour cela que ce numéro est le plus prudent du volume : plus l'outil est puissant, plus la tentation de s'en servir pour le plaisir est forte. On finit par écrire des types qui mettent une seconde à se résoudre, que l'éditeur n'affiche plus, et que personne ne saura corriger — soi compris, dans six mois.The template literal type is the summit of the language within the language: with it, you compose, multiply, parse strings, combine everything the two previous issues laid down. It's genuinely powerful — typed routes, handler names, translation keys that never drift from their source. And that's precisely why this issue is the volume's most cautious: the more powerful the tool, the stronger the temptation to use it for its own sake. You end up writing types that take a second to resolve, that the editor no longer displays, and that no one will be able to fix — yourself included, in six months.

Le fil du Vol 6 tenait en une question — calculer, ou recopier ? Sa réponse honnête, c'est que « calculer » ne gagne que s'il achète une garantie de synchro réelle, sur une source qui bouge vraiment. Sinon, le type écrit à la main, explicite et stable, est plus clair et plus rapide. Et il reste un fait que toute cette magie ne change pas : au niveau type, on raisonne sur des formes qui s'effacent à l'exécution (Vol 2). Rien de ce qu'on calcule ici ne vérifie un seul octet venu du réseau. C'est l'objet du dernier volume — la frontière entre le typé et le non-typé, là où il faut cesser de calculer des types et commencer à valider des données.Vol 6's thread held in one question — compute, or copy? Its honest answer is that 'compute' only wins if it buys a real sync guarantee, on a source that genuinely moves. Otherwise, the hand-written type, explicit and stable, is clearer and faster. And one fact all this magic doesn't change remains: at the type level, you reason about shapes that erase at runtime (Vol 2). Nothing you compute here checks a single byte from the network. That's the last volume's subject — the boundary between typed and untyped, where you must stop computing types and start validating data.