
TypeScript's type system has grown steadily more powerful over the past five years, allowing you to precisely type more and more patterns in JavaScript. The upcomingTypeScript 4.1 release includes a particularly exciting newaddition to the type system:template literal types.
Template literal types solve along-standing gap in TypeScript's type system and, as I'll argue at the end of the post, they solve it in a particularlyTypeScripty way.
To understand template literal types, let's start with a seemingly simple question: what can't you type?
My standard example of a pattern youcouldn't type has always been thecamelCase function, which maps something like"foo_bar" →"fooBar". It's easy to implement in JavaScript using a regular expression:
|
This function is trivial tosimply type:
|
So that's not quite what I'm getting at. Ideally you'd like to be able to use this to convert objects withsnake_cased properties (like you'd get from a database) into ones withcamelCased properties (like you typically use in JS/TS). In other words, what should the return type of this function be to make the following code type check (or not) as you'd expect?
|
Prior to TypeScript 4.1 (now a release candidate) this just wasn't possible. The reason was that string literal types like"foo_bar" were "atomic" in the sense that you couldn't observe any structure inside of them. They were indivisible. But clearly thereis structure in strings. Just look atall the methods onString.prototype.
Enter: TypeScript 4.1!
TypeScript 4.1 introduce a few features that make it possible to precisely type theobjectToCamel function:
string (think "strings starting withon").Let's use these two features to implementobjectToCamel.
First, let's look at template literal types. They look like ES template literals:
|
This lets you create a type for "strings starting withon." Before TypeScript 4.1, you either hadstring or an enumerated union of string literal types ("a" | "b" | "c"). Now you can define structured subsets ofstring.
Here are a few other patterns:
|
What makes this really powerful is that you can use theinfer keyword in a template literal type to do pattern matching:
|
The conditional matches string literal types of the form"head_tail". The "_" acts as a delimiter to split the string. Becauseconditional types distribute over unions, this also works for union types:
|
There's a big issue, though. What if there's two_s in the string literal type?
|
We can't stop after the first "_", we need to keep going. We can do this by making the type recursive:
|
The recursive bit is where we callToCamel<Tail>.
Pretty neat! Now let's put it all together.
Recall that amapped type in TypeScript looks and works something like this:
|
Thekeyof T here produces a union of string literal types ("x" | "y") and the mapped type produces an object type from this given a way to produce the values (thePromise<T[K]>). But the keys are set by the union. You can't change them.
With Key Remapping, you can add anas clause to the key in a mapped type to change things around. This works particularly well with template literal types:
|
(The& string is there for technical reasons that I don't want to get into.)
Using this, we can plug in ourToCamel generic to put it all together:
|
Here's acomplete playground.
After template literal types landed, the TypeScript Twittersphere went crazy. I shared a use case aroundexpress, which quickly became the most popular tweet I've ever posted:
Another use of@TypeScript 4.1's template literal types: extracting the URL parameters from an express route. Pretty amazing you can do this in the type system!https://t.co/gfZQy70whgpic.twitter.com/aEyfMwjjqX
— Dan Vanderkam (@danvdk)September 4, 2020
AJSON parser made the rounds and then someoneimplemented a full SQL engine in the type system. Hacker newswas impressed.
As with any new tool, it will take some time for the community to figure out the best ways to use it. Here are a few ideas. We'll see how they pan out!
Dotted access:easy win
Lodash allows you to write"iteratee" expressions likexs.map('a.b.c'), which is roughly the same asxs.map(x => x.a.b.c). Template literal types will make it possible for this sort of API to be typed.
I've never been a big fan of this style. I'd prefer to writex => x.a.b.c. But perhaps some of this is just bias from not being able to type these properly in the past. Using string literals for enums, for example, is frowned upon in Java as unsafe,stringly typed, code. But it turns out to be fine in TypeScript because the type system is rich enough to capture it. So we'll see!
Parsing routes:huge win!
See myExtractRouteParams tweet above. Parsing{userId: string} out of/users/:userId will be a big win for express users.
Going the other direction is also compelling. In a server I use at work, we issue API calls via something likeget('/users/:userId', {userId: 'id'}). We have types defined for the parameters for each route. But now we can just let TypeScript infer them to ensure that nothing will ever get out of sync.
Similar considerations apply to routes withreact-router.
Better types forquerySelector /querySelectorAll:nice win
TheDOM typings are clever enough to infer a subtype ofElement here:
|
But once you add anything more complex to the selector, you lose this:
|
With template literal types, it will be possible to fix this. I wouldn't be surprised if it becomes common practice to replace calls togetElementById with equivalent calls toquerySelector:
|
This will no doubt require me to rewrite Item 55 ofEffective TypeScript ("Understand the DOM hierarchy"). Oh well!
Parsing options inCommander ordocopt:a small win
WithCommander, you define your command line tool's arguments using something like this:
|
Setting aside the mutation style, which is hard to model in TypeScript, template literal types should make it possible to extract the parameter names from the calls to.option.
Parsing SQL or GraphQL:I could go either way!
Thets-sql demoraised some eyebrows, but it also made a real point about the power of template literal types. Given a TypeScript version of your database schema (which can be generated usingschemats orpg-to-ts), it should be possible to infer result types for a SQL query:
|
This seems potentially amazing, but also perhaps brittle. You'd have to work in the subset of SQL that your types understood: presumably you wouldn't want to implement all ofPL/pgSQL in the type system. But I could imagine getting a large class of queries, including joins, to work.
So I'm on the fence on this one! Similar considerations apply to GraphQL queries, which would be a bit easier to join with a schema in the type system than raw SQL.
Template literal types open up many new doors for TypeScript library authors and should improve the overall experience of using TypeScript for everyone by capturing more JavaScript patterns in the type system.
I'd like to conclude by pointing out that this is a veryTypeScripty solution to this problem. TypeScript is full of "puns" between value and type syntax. Depending on the context,"foo" could either be the literal value"foo" or a type consisting of the single value"foo". (I explore this in Item 8 ofEffective TypeScript, "Know How to Tell Whether a Symbol Is in the Type Space or Value Space"). Another famous example isindex types:
|
Template literal types continue this pattern by repurposing a runtime JavaScript syntax (template strings) into something that makes sense in the type system (template literal types). Theconcat function really hammers this home:
|
On the return line,`${a}${b}` is the runtime JavaScript template literal and`${A}${B}` is the TypeScript type. It's not an accident that they look identical!
I never would have thought to do it this way, but kudos to Anders and the TypeScript team for coming up with such an on-brand solution!
Image credit: modified version ofFile:Nuclear fission chain reaction.svg from Wiki Commons

Effective TypeScript shows you not justhow to use TypeScript but how to use itwell. Now in its second edition, the book's 83 items help you build mental models of how TypeScript and its ecosystem work, make you aware of pitfalls and traps to avoid, and guide you toward using TypeScript’s many capabilities in the most effective ways possible. Regardless of your level of TypeScript experience, you can learn something from this book.
After readingEffective TypeScript, your relationship with the type system will be the most productive it's ever been!Learn more »