Back to Blog
JavaScriptTemplatesES6Strings

JavaScript Tagged Template Literals

Master JavaScript tagged template literals for custom string processing, SQL queries, and DSLs.

B
Bootspring Team
Engineering
January 26, 2020
6 min read

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, '&amp;') 5 .replace(/</g, '&lt;') 6 .replace(/>/g, '&gt;') 7 .replace(/"/g, '&quot;') 8 .replace(/'/g, '&#39;'); 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>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</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.

Share this article

Help spread the word about Bootspring