Skip to content
Python Releases

Python 3.14 t-strings: build templates you can sanitize before rendering

Python 3.14 t-strings: build templates you can sanitize before rendering I have seen teams ship XSS bugs because someone “just used an f-string” in the wrong place. Python 3.14 template string literals (t-strings) help because they produce a Template you can inspect and sanitize before you turn it into a plain str, but only if […]

Jack Pauley December 10, 2025 6 min read

Python 3.14 t-strings: build templates you can sanitize before rendering

I have seen teams ship XSS bugs because someone “just used an f-string” in the wrong place.

Python 3.14 template string literals (t-strings) help because they produce a Template you can inspect and sanitize before you turn it into a plain str, but only if you actually run a renderer pipeline.

What t-strings are (and what they are not)

t-strings look like f-strings, but they do not immediately render.

You write t”Hello {user}” and get a string.templatelib.Template object that contains static text segments plus Interpolation objects, so you can escape, validate, or bind parameters before you output anything.

  • They do not make output “safe” by default: you must still escape HTML, bind SQL params, and apply formatting rules in your renderer.
  • They do not behave exactly like f-strings: conversions and format specs live on the interpolation object. Your renderer decides what to do with them.
  • They are not drop-in strings: lots of APIs expect str, so render at the boundary and pass strings downstream.

Setup: confirm you are on Python 3.14.2+

Check your runtime first.

Run python –version and make sure you are on Python 3.14.2 or newer, then import string.templatelib from the standard library.

Create a t-string, then inspect its parts

This bit surprised me the first time.

If you print a t-string, you do not get the final message. You get a structured object you can iterate, and each part will either be a plain string segment or an interpolation you must handle.

  • Create: t”Hello {user}” returns a Template, not str.
  • Inspect: iterate the Template and branch on Interpolation vs static text.
  • Important caveat: iteration skips empty string segments in the official API, so if you need exact segment layout, prefer the Template’s dedicated accessors (check the docs for the names on your exact 3.14.x build).

A minimal safe HTML renderer (the example you will actually reuse)

So.

Here’s the pattern that keeps you out of trouble: treat every interpolation as hostile until you escape it for the output context. I default to HTML-escaping because it is the place I see people get hurt most often.

Renderer contract: static text passes through, interpolation values get coerced to string, then escaped, then concatenated.

  • Input: Template plus a dict of values (the context).
  • Escape rule: HTML-escape every interpolated value unless you intentionally mark it safe.
  • Output: plain str you can hand to Flask/Django/your HTTP client.

If you skip the renderer and call str() on random objects mid-template, you did not buy safety. You bought a new way to make a mess.

Common failure modes (I keep seeing these)

It will break at first.

That’s fine. Most breakages come from confusing “template” with “string,” or from trying to use t-strings as a universal replacement for Jinja2-style templates.

  • You expected a str but got a Template: render explicitly at your module boundary. Do not let Template objects leak into libraries that only accept strings.
  • Your interpolations contain None, ints, or objects: coerce in one place (the renderer). Decide whether you want str(value), repr(value), or strict typing that raises.
  • You tried to use it for SQL: do not build SQL strings by concatenation. Use t-strings to build a structured representation, then bind parameters in your DB driver.
  • You benchmarked and saw a slowdown: profile first, then cache compiled templates or memoize expensive escaping functions. Do not guess.

When I would not use t-strings

Sometimes you should just not.

If you need template inheritance, loops, conditionals, i18n blocks, or designers editing templates, use a real template engine. I do not trust “we’ll keep it simple” as a long-term plan.

  • Complex HTML pages: use Jinja2/Django templates. Keep t-strings for small fragments where you want strict control.
  • Ultra-hot paths: measure before you convert everything. You can keep f-strings for internal, non-user-facing strings.
  • Mixed-version libraries: if you must support Python 3.13 and 3.14, gate the feature or provide a fallback path.

What to do next

Pick one risky string.

Find the place where user input enters HTML or logs, replace that one f-string with a t-string, and write a unit test that passes <script>alert(1)</script> as input and asserts the output escaped it. Other stuff in this release: dependency bumps, some image updates, the usual.

Keep Reading

Frequently Asked Questions

What are t-strings in Python 3.14? Template string literals (t-strings) are a new feature in Python 3.14 that look like f-strings but produce a Template object instead of a plain string. You write t"Hello {user}" and get a structured object you can inspect, sanitize, and render on your own terms. They live in the string.templatelib module.

Are t-strings a drop-in replacement for f-strings? No. T-strings produce a Template object, not a str. Most APIs that expect strings will break if you pass a Template directly. You need to run a renderer that converts the Template to a string after applying your escaping or sanitization logic. Think of them as f-strings with a mandatory processing step.

Do t-strings automatically prevent XSS or SQL injection? No. T-strings give you the opportunity to sanitize by separating static text from interpolated values, but they do not escape anything by default. You must write or use a renderer that handles HTML escaping, SQL parameter binding, or whatever your output context requires. The safety comes from your pipeline, not from the syntax itself.

What Python version do I need for t-strings? You need Python 3.14.0 or newer. The recommended minimum is Python 3.14.2, which fixes several crash regressions in the 3.14 line. Run python --version to check, then import string.templatelib to verify the module is available.

Related Reading

Try t-strings yourself

Template strings (t-strings) are new in Python 3.14. Here is how they work in practice:

# Basic t-string usage (Python 3.14+)
from string.templatelib import Template

name = "world"
greeting = t"Hello, {name}!"
# greeting is a Template object, NOT a string yet

# Inspect the template before rendering
print(type(greeting))      # <class 'string.templatelib.Template'>
print(greeting.strings)    # ('Hello, ', '!')
print(greeting.values)     # ('world',)

# Render it when you are ready
print(str(greeting))       # Hello, world!

Security: sanitize before rendering

This is the killer feature. Unlike f-strings, t-strings let you inspect and sanitize interpolated values before they become part of the output:

from string.templatelib import Template
import html

def safe_html(template: Template) -> str:
    parts = []
    for i, s in enumerate(template.strings):
        parts.append(s)
        if i < len(template.values):
            parts.append(html.escape(str(template.values[i])))
    return "".join(parts)

# User input that could be an XSS attack
user_input = '<script>alert("xss")</script>'
result = safe_html(t"<p>Welcome, {user_input}</p>")
print(result)  # XSS neutralized!

SQL injection prevention

Same pattern works for SQL queries. Build parameterized queries from t-strings:

from string.templatelib import Template

def safe_sql(template: Template) -> tuple:
    sql_parts = []
    params = []
    for i, s in enumerate(template.strings):
        sql_parts.append(s)
        if i < len(template.values):
            sql_parts.append("?")
            params.append(template.values[i])
    return "".join(sql_parts), params

# Usage
user_id = "'; DROP TABLE users; --"
query, params = safe_sql(t"SELECT * FROM users WHERE id = {user_id}")
print(query)   # SELECT * FROM users WHERE id = ?
print(params)  # The injection becomes a harmless parameter

Check if your Python version is approaching end-of-life with the Dependency EOL Scanner. See the full Python support timeline at our Python Release Tracker, or pick the right Python version for your project with the Version Picker.

Official resources:

🛠️ Interactive Tool

Is your Python still supported?

Open in new tab ↗

🛠️ Try These Free Tools

📦 Dependency EOL Scanner

Paste your dependency file to check for end-of-life packages.

🗺️ Upgrade Path Planner

Plan your upgrade path with breaking change warnings and step-by-step guidance.

❤️ Tech Stack Health Scorecard

Select your technologies and get an instant A-F health grade with live badges.

See all free tools →

Stay Updated

Get the best releases delivered monthly. No spam, unsubscribe anytime.

By subscribing you agree to our Privacy Policy.