Tagged template literals allow custom processing of template strings. Here's how to use them for powerful string transformations.
Basic Syntax#
1// Tag function receives strings and values separately
2function tag(strings, ...values) {
3 console.log('Strings:', strings);
4 console.log('Values:', values);
5 return strings[0] + values[0] + strings[1];
6}
7
8const name = 'World';
9const result = tag`Hello, ${name}!`;
10// Strings: ['Hello, ', '!']
11// Values: ['World']
12// Result: 'Hello, World!'Raw Strings#
1function showRaw(strings) {
2 console.log('Cooked:', strings[0]);
3 console.log('Raw:', strings.raw[0]);
4}
5
6showRaw`Line1\nLine2`;
7// Cooked: 'Line1
8// Line2'
9// Raw: 'Line1\\nLine2'
10
11// Built-in String.raw
12const path = String.raw`C:\Users\name\file.txt`;
13console.log(path); // 'C:\\Users\\name\\file.txt'HTML Escaping#
1function html(strings, ...values) {
2 const escape = (str) =>
3 String(str)
4 .replace(/&/g, '&')
5 .replace(/</g, '<')
6 .replace(/>/g, '>')
7 .replace(/"/g, '"')
8 .replace(/'/g, ''');
9
10 return strings.reduce((result, str, i) => {
11 const value = values[i - 1];
12 return result + escape(value) + str;
13 });
14}
15
16const userInput = '<script>alert("XSS")</script>';
17const safe = html`<div>${userInput}</div>`;
18// '<div><script>alert("XSS")</script></div>'SQL Query Builder#
1function sql(strings, ...values) {
2 const query = strings.reduce((result, str, i) => {
3 return result + '$' + i + str;
4 });
5
6 return {
7 text: query,
8 values: values,
9 };
10}
11
12const userId = 123;
13const status = 'active';
14
15const query = sql`
16 SELECT * FROM users
17 WHERE id = ${userId}
18 AND status = ${status}
19`;
20
21// {
22// text: 'SELECT * FROM users WHERE id = $1 AND status = $2',
23// values: [123, 'active']
24// }CSS-in-JS#
1function css(strings, ...values) {
2 const style = strings.reduce((result, str, i) => {
3 const value = values[i];
4 return result + str + (value !== undefined ? value : '');
5 }, '');
6
7 return style.trim();
8}
9
10const primaryColor = '#3b82f6';
11const padding = 16;
12
13const buttonStyle = css`
14 background-color: ${primaryColor};
15 padding: ${padding}px;
16 border: none;
17 border-radius: 4px;
18 color: white;
19 cursor: pointer;
20
21 &:hover {
22 opacity: 0.9;
23 }
24`;Internationalization#
1const translations = {
2 en: {
3 greeting: (name) => `Hello, ${name}!`,
4 items: (count) => `You have ${count} item${count !== 1 ? 's' : ''}`,
5 },
6 es: {
7 greeting: (name) => `¡Hola, ${name}!`,
8 items: (count) => `Tienes ${count} elemento${count !== 1 ? 's' : ''}`,
9 },
10};
11
12function createI18n(locale) {
13 return function t(strings, ...values) {
14 const key = strings.join('{}');
15 const template = translations[locale]?.[key];
16
17 if (template) {
18 return template(...values);
19 }
20
21 // Fallback to original
22 return strings.reduce((result, str, i) => {
23 return result + str + (values[i] ?? '');
24 }, '');
25 };
26}
27
28const t = createI18n('es');
29console.log(t`greeting${'Maria'}`); // '¡Hola, Maria!'Logging with Context#
1function log(strings, ...values) {
2 const message = strings.reduce((result, str, i) => {
3 const value = values[i];
4 const formatted =
5 typeof value === 'object' ? JSON.stringify(value, null, 2) : value;
6 return result + str + (formatted ?? '');
7 }, '');
8
9 console.log(`[${new Date().toISOString()}]`, message);
10
11 return message;
12}
13
14const user = { id: 1, name: 'John' };
15log`User logged in: ${user}`;
16// [2024-01-15T10:30:00.000Z] User logged in: {
17// "id": 1,
18// "name": "John"
19// }Highlight/Formatting#
1// Terminal colors
2const colors = {
3 red: '\x1b[31m',
4 green: '\x1b[32m',
5 blue: '\x1b[34m',
6 reset: '\x1b[0m',
7};
8
9function highlight(color) {
10 return function (strings, ...values) {
11 return strings.reduce((result, str, i) => {
12 const value = values[i];
13 const highlighted =
14 value !== undefined
15 ? `${colors[color]}${value}${colors.reset}`
16 : '';
17 return result + str + highlighted;
18 }, '');
19 };
20}
21
22const red = highlight('red');
23const green = highlight('green');
24
25console.log(red`Error: ${new Error('Something went wrong').message}`);
26console.log(green`Success: ${'Operation completed'}`);GraphQL Queries#
1function gql(strings, ...values) {
2 const query = strings.reduce((result, str, i) => {
3 return result + str + (values[i] ?? '');
4 }, '');
5
6 // Parse and validate query (simplified)
7 return {
8 query: query.trim(),
9 parse() {
10 // Return AST
11 return { kind: 'Document', definitions: [] };
12 },
13 };
14}
15
16const userFields = `
17 id
18 name
19 email
20`;
21
22const GET_USER = gql`
23 query GetUser($id: ID!) {
24 user(id: $id) {
25 ${userFields}
26 posts {
27 title
28 }
29 }
30 }
31`;Markdown Processing#
1function md(strings, ...values) {
2 let result = strings.reduce((acc, str, i) => {
3 const value = values[i];
4 const escaped =
5 value !== undefined
6 ? String(value).replace(/[*_`]/g, '\\$&')
7 : '';
8 return acc + str + escaped;
9 }, '');
10
11 // Convert to HTML (simplified)
12 result = result
13 .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
14 .replace(/\*(.+?)\*/g, '<em>$1</em>')
15 .replace(/`(.+?)`/g, '<code>$1</code>')
16 .replace(/\n/g, '<br>');
17
18 return result;
19}
20
21const code = 'console.log()';
22const text = md`
23 **Bold text** and *italic text*
24 Code: ${code}
25`;Regex Builder#
1function regex(flags = '') {
2 return function (strings, ...values) {
3 const pattern = strings.reduce((result, str, i) => {
4 const value = values[i];
5 // Escape special regex characters in interpolated values
6 const escaped =
7 value !== undefined
8 ? String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
9 : '';
10 return result + str + escaped;
11 }, '');
12
13 return new RegExp(pattern, flags);
14 };
15}
16
17const re = regex('gi');
18const searchTerm = 'hello (world)';
19const pattern = re`^${searchTerm}$`;
20// /^hello \(world\)$/giDedent/Trim#
1function dedent(strings, ...values) {
2 // Reconstruct full string
3 let result = strings.reduce((acc, str, i) => {
4 return acc + str + (values[i] ?? '');
5 }, '');
6
7 // Find minimum indentation
8 const lines = result.split('\n');
9 const minIndent = lines
10 .filter((line) => line.trim())
11 .reduce((min, line) => {
12 const indent = line.match(/^\s*/)[0].length;
13 return Math.min(min, indent);
14 }, Infinity);
15
16 // Remove indentation
17 return lines
18 .map((line) => line.slice(minIndent))
19 .join('\n')
20 .trim();
21}
22
23const code = dedent`
24 function hello() {
25 console.log('Hello');
26 }
27`;
28// No leading indentationCaching Tag#
1function createCachedTag() {
2 const cache = new Map();
3
4 return function cached(strings, ...values) {
5 const key = strings.join('\0');
6
7 if (!cache.has(key)) {
8 cache.set(key, {
9 template: strings,
10 results: new Map(),
11 });
12 }
13
14 const entry = cache.get(key);
15 const valuesKey = JSON.stringify(values);
16
17 if (!entry.results.has(valuesKey)) {
18 const result = strings.reduce((acc, str, i) => {
19 return acc + str + (values[i] ?? '');
20 }, '');
21 entry.results.set(valuesKey, result);
22 }
23
24 return entry.results.get(valuesKey);
25 };
26}
27
28const cached = createCachedTag();
29// Repeated calls with same values return cached resultType Coercion#
1function typed(strings, ...values) {
2 return strings.reduce((result, str, i) => {
3 let value = values[i];
4
5 // Type coercion based on context
6 if (str.endsWith('$')) {
7 value = typeof value === 'number' ? value.toFixed(2) : value;
8 } else if (str.endsWith('%')) {
9 value = typeof value === 'number' ? (value * 100).toFixed(1) : value;
10 } else if (str.endsWith('#')) {
11 value = typeof value === 'number' ? value.toLocaleString() : value;
12 }
13
14 return result + str.replace(/[$%#]$/, '') + (value ?? '');
15 }, '');
16}
17
18const price = 19.99;
19const rate = 0.156;
20const count = 1234567;
21
22console.log(typed`Price: $${price}, Rate: %${rate}, Count: #${count}`);
23// 'Price: 19.99, Rate: 15.6, Count: 1,234,567'Best Practices#
Use Cases:
✓ HTML/SQL escaping
✓ CSS-in-JS libraries
✓ Query builders
✓ i18n systems
✓ DSL creation
Patterns:
✓ Return transformed strings
✓ Return structured objects
✓ Chain multiple tags
✓ Cache expensive operations
Performance:
✓ strings array is cached by engine
✓ Use Map for result caching
✓ Avoid heavy computation in tags
✓ Consider lazy evaluation
Avoid:
✗ Side effects in tags
✗ Mutating the strings array
✗ Ignoring strings.raw when needed
✗ Over-complex transformations
Conclusion#
Tagged template literals enable powerful string processing by separating static strings from dynamic values. Use them for safe HTML/SQL escaping, building DSLs, creating CSS-in-JS solutions, and implementing i18n systems. The tag function receives the template parts separately, allowing custom processing, validation, and transformation while maintaining clean syntax at the call site.