Tagged templates process template literals with custom functions. Here's how to use them.
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 'processed';
6}
7
8const name = 'Alice';
9const age = 30;
10
11const result = tag`Hello, ${name}! You are ${age} years old.`;
12// Strings: ['Hello, ', '! You are ', ' years old.']
13// Values: ['Alice', 30]
14// result: 'processed'
15
16// strings.raw contains unprocessed strings
17function showRaw(strings) {
18 console.log(strings.raw[0]); // Shows \n literally
19}
20
21showRaw`Line1\nLine2`;Building Custom Tags#
1// Simple interpolation tag
2function simple(strings, ...values) {
3 let result = '';
4 strings.forEach((str, i) => {
5 result += str + (values[i] ?? '');
6 });
7 return result;
8}
9
10// Using reduce
11function interpolate(strings, ...values) {
12 return strings.reduce((result, str, i) => {
13 return result + str + (values[i] ?? '');
14 }, '');
15}
16
17// Same as untagged template literal
18const name = 'Bob';
19console.log(interpolate`Hello, ${name}!`);
20// 'Hello, Bob!'HTML Escaping#
1// Escape HTML to prevent XSS
2function escapeHtml(str) {
3 return str
4 .replace(/&/g, '&')
5 .replace(/</g, '<')
6 .replace(/>/g, '>')
7 .replace(/"/g, '"')
8 .replace(/'/g, ''');
9}
10
11function html(strings, ...values) {
12 return strings.reduce((result, str, i) => {
13 const value = values[i];
14 const escaped = value !== undefined
15 ? escapeHtml(String(value))
16 : '';
17 return result + str + escaped;
18 }, '');
19}
20
21// Usage
22const userInput = '<script>alert("xss")</script>';
23const safe = html`<div>${userInput}</div>`;
24// '<div><script>alert("xss")</script></div>'
25
26// Allow raw HTML when needed
27function rawHtml(value) {
28 return { __html: value };
29}
30
31function html(strings, ...values) {
32 return strings.reduce((result, str, i) => {
33 const value = values[i];
34 if (value?.__html) {
35 return result + str + value.__html;
36 }
37 return result + str + (value !== undefined ? escapeHtml(String(value)) : '');
38 }, '');
39}
40
41const trusted = rawHtml('<strong>Bold</strong>');
42const output = html`<div>${trusted}</div>`;
43// '<div><strong>Bold</strong></div>'SQL Query Builder#
1// Parameterized SQL queries
2function sql(strings, ...values) {
3 const params = [];
4 const query = strings.reduce((result, str, i) => {
5 if (i < values.length) {
6 params.push(values[i]);
7 return result + str + `$${params.length}`;
8 }
9 return result + str;
10 }, '');
11
12 return { query, params };
13}
14
15// Usage
16const userId = 123;
17const status = 'active';
18
19const { query, params } = sql`
20 SELECT * FROM users
21 WHERE id = ${userId}
22 AND status = ${status}
23`;
24
25// query: 'SELECT * FROM users WHERE id = $1 AND status = $2'
26// params: [123, 'active']
27
28// Execute safely
29await db.query(query, params);
30
31// With identifiers
32function sqlWithIdentifiers(strings, ...values) {
33 const params = [];
34 const query = strings.reduce((result, str, i) => {
35 const value = values[i];
36 if (value === undefined) return result + str;
37
38 if (value.__identifier) {
39 // Escape identifier
40 return result + str + `"${value.name.replace(/"/g, '""')}"`;
41 }
42
43 params.push(value);
44 return result + str + `$${params.length}`;
45 }, '');
46
47 return { query, params };
48}
49
50function identifier(name) {
51 return { __identifier: true, name };
52}
53
54const table = identifier('users');
55const column = identifier('email');CSS-in-JS#
1// Styled components pattern
2function css(strings, ...values) {
3 return strings.reduce((result, str, i) => {
4 const value = values[i];
5 if (typeof value === 'function') {
6 // Defer function evaluation
7 return result + str + '__PLACEHOLDER__';
8 }
9 return result + str + (value ?? '');
10 }, '');
11}
12
13// With units
14function px(value) {
15 return `${value}px`;
16}
17
18function cssWithUnits(strings, ...values) {
19 return strings.reduce((result, str, i) => {
20 let value = values[i];
21 if (typeof value === 'number') {
22 value = `${value}px`;
23 }
24 return result + str + (value ?? '');
25 }, '');
26}
27
28const width = 200;
29const styles = cssWithUnits`
30 width: ${width};
31 padding: ${16};
32 margin: ${8};
33`;
34// 'width: 200px; padding: 16px; margin: 8px;'Logging with Context#
1// Debug logging with file/line info
2function debug(strings, ...values) {
3 const message = strings.reduce((result, str, i) => {
4 const value = values[i];
5 const formatted = typeof value === 'object'
6 ? JSON.stringify(value, null, 2)
7 : value;
8 return result + str + (formatted ?? '');
9 }, '');
10
11 const stack = new Error().stack;
12 const caller = stack?.split('\n')[2]?.trim();
13
14 console.log(`[DEBUG] ${message}`);
15 console.log(` at ${caller}`);
16}
17
18const user = { name: 'Alice', id: 1 };
19debug`User logged in: ${user}`;
20
21// Colored logging
22const colors = {
23 reset: '\x1b[0m',
24 red: '\x1b[31m',
25 green: '\x1b[32m',
26 yellow: '\x1b[33m',
27 blue: '\x1b[34m',
28};
29
30function colorize(color) {
31 return (strings, ...values) => {
32 const message = strings.reduce((r, s, i) =>
33 r + s + (values[i] ?? ''), '');
34 return `${colors[color]}${message}${colors.reset}`;
35 };
36}
37
38const red = colorize('red');
39const green = colorize('green');
40
41console.log(red`Error: Something went wrong`);
42console.log(green`Success: Operation completed`);i18n Translation#
1// Translation tag
2const translations = {
3 'en': {
4 'Hello, {0}!': 'Hello, {0}!',
5 'You have {0} messages': 'You have {0} messages',
6 },
7 'es': {
8 'Hello, {0}!': '¡Hola, {0}!',
9 'You have {0} messages': 'Tienes {0} mensajes',
10 },
11};
12
13let currentLocale = 'en';
14
15function t(strings, ...values) {
16 const key = strings.reduce((result, str, i) => {
17 return result + str + (i < values.length ? `{${i}}` : '');
18 }, '');
19
20 let translated = translations[currentLocale]?.[key] ?? key;
21
22 values.forEach((value, i) => {
23 translated = translated.replace(`{${i}}`, String(value));
24 });
25
26 return translated;
27}
28
29// Usage
30const name = 'Alice';
31const count = 5;
32
33currentLocale = 'en';
34console.log(t`Hello, ${name}!`); // 'Hello, Alice!'
35
36currentLocale = 'es';
37console.log(t`Hello, ${name}!`); // '¡Hola, Alice!'
38console.log(t`You have ${count} messages`); // 'Tienes 5 mensajes'GraphQL Queries#
1// GraphQL query builder
2function gql(strings, ...values) {
3 const query = strings.reduce((result, str, i) => {
4 return result + str + (values[i] ?? '');
5 }, '');
6
7 // Parse and validate in development
8 if (process.env.NODE_ENV === 'development') {
9 validateGraphQL(query);
10 }
11
12 return { query, __graphql: true };
13}
14
15const UserFragment = gql`
16 fragment UserFields on User {
17 id
18 name
19 email
20 }
21`;
22
23const GetUser = gql`
24 ${UserFragment}
25
26 query GetUser($id: ID!) {
27 user(id: $id) {
28 ...UserFields
29 }
30 }
31`;
32
33// Usage with client
34const result = await graphqlClient.query(GetUser, { id: '123' });Regular Expressions#
1// Verbose regex with comments
2function regex(strings, ...values) {
3 const pattern = strings.reduce((result, str, i) => {
4 // Remove comments and whitespace
5 const cleaned = str
6 .replace(/#.*/g, '')
7 .replace(/\s+/g, '');
8 return result + cleaned + (values[i] ?? '');
9 }, '');
10
11 return new RegExp(pattern);
12}
13
14const emailRegex = regex`
15 ^ # Start
16 [^\s@]+ # Username (no spaces or @)
17 @ # @ symbol
18 [^\s@]+ # Domain name
19 \. # Dot
20 [^\s@]+ # TLD
21 $ # End
22`;
23
24// With flags
25function regexWithFlags(flags) {
26 return (strings, ...values) => {
27 const pattern = strings.reduce((result, str, i) => {
28 const cleaned = str.replace(/#.*/g, '').replace(/\s+/g, '');
29 return result + cleaned + (values[i] ?? '');
30 }, '');
31 return new RegExp(pattern, flags);
32 };
33}
34
35const caseInsensitive = regexWithFlags('i');Best Practices#
Design:
✓ Return useful values
✓ Handle edge cases
✓ Document expected usage
✓ Consider raw strings
Security:
✓ Escape user input
✓ Use parameterized queries
✓ Validate interpolated values
✓ Mark trusted content explicitly
Performance:
✓ Cache expensive operations
✓ Avoid unnecessary processing
✓ Consider lazy evaluation
✓ Profile complex tags
Avoid:
✗ Mutating input arrays
✗ Side effects in tags
✗ Complex nested tags
✗ Ignoring raw strings
Conclusion#
Tagged template literals enable powerful custom string processing for HTML escaping, SQL queries, CSS-in-JS, translations, and domain-specific languages. The tag function receives strings and values separately, allowing safe interpolation and custom transformations.