Every individual item sold on Mercari.com has an id that looks like m<number>. If you buy several items from the same seller, you get a discount because you buy it as a bundle. Every bundle has its own unique id that looks like b<number>. Both ids are strings, yet they have a certain format that differs. In our tests we don't want to be confused which type we are passing: is it an item id; a bundle id; a random string we pass as id by mistake?
Here is where TypeScript string template literal types come in very handy. Let's define an ItemId type.
1 | type ItemId = `m${number}` |
TypeScript immediately complains about "x123" and "mabc" strings - these are NOT item ids. My VSCode editor highlights the errors

We don't have to run a test to know it does not work; static types check tells us about our mistake.
If we have a runtime ID value (which we could load from a network call for example), we can confirm and typecast it using is a <type> syntax. For example, here is utility function to check if a string is an item id:
1 | type ItemId = `m${number}` |
If we look at the "else" branch, we can see the type inference; TS can safely say that "id1" in the "else" branch has "never" type, meaning this code should be unreachable.

We can go beyond a predicate, we can write a utility function that throws an exception if it is given an invalid string value; and this function tells TypeScript compiler that the input has certain type afterwards.
1 | type ItemId = `m${number}` |
What about other strings, like phone numbers? Let's say that every US phone number we have to store must be fully formatted. We can define a type:
1 | type PhoneNumber = `+1-${number}-${number}-${number}` |
Looks good, but notice my comment on the phone3 - TypeScript does not flag its incorrect format.

Hmm, we do detect the "abridged" phone number strings, yet TS "missed" the obvious problem. Even worse, TS will miss completely wrong numbers!
1 | type PhoneNumber = `+1-${number}-${number}-${number}` |
Look at the PhoneNumber type definition: +1-${number}-${number}-${number} says nothing about how many digits each number part should have. Thus the string "+1-1-2-3" satisfies the type, yet it is a bad phone number string.
Let's step back for a second and look at the bank card codes or credit card CVV codes. We can define a type for a string that is just 4 digits in a row:
1 | type digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
Look at the TS error given for pin2 and pin3

TypeScript does NOT tell us "123 is too short" or "123 does not match digit+digit+digit+digit", instead it shows us how such string literal type works. It simply "expands" every possible combination. String type PinCode is a union of ALL strings like 0000 | 0001 | 0002 | ... | 9999. Wow, brute force works!
Ok, let's use the digit type to form a better phone number type.
1 | type digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
Does the above code work? I don't see any TS errors, hmm. What is the PhoneNumber type, let's hover over it

There are too many combinations for the PhoneNumber string literal type, so TS does NOT expand it into +1-000-000-0000 | +1-000-000-0001 | ... - there would be way too many strings in that union to keep track. From my checks, anything longer than 4 digits is not handled, so US 5-digit zip codes are out.
Ok, so we saw string template literal types, how they work under the hood, and even their limitations. One more note, if you need to check how your web application handles invalid inputs, you do NOT need to special types. You can simply use strings. Let's confirm that entering an invalid phone number leads to an error:
1 | const invalidNumbers: string[] = [ |
The above Cypress test simply verifies that entering a bad phone number string is handled by the app. Aside from this test, your testing code probably should only accept PhoneNumber. If you must make an exception (for checking the error handling for example), you can be explicit and use @ts-expect-error when calling it:
1 | const ItemPage = { |
Without @ts-expect-error our TS compiler would not let us pass non-item id string into the page object method ItemPage.visit, but we do want to pass it.
Learn more
We can go beyond string literal types into branded types to represent things like currencies and time durations. I also suggest reading 6 TypeScript Tips to Write Safer, Cleaner Code article.