string.Template is a lightweight way to build text with replaceable fields in Python. Instead of embedding complex logic inside a string, you define placeholders such as $name and provide their values separately. This approach works well for notifications, email bodies, reports, configuration-driven messages, and text edited by people who should not need to understand full Python expression syntax.
What string.Template is
The Template class belongs to Python’s standard string module. It recognizes placeholders beginning with a dollar sign. A field can be written as $name or ${name}. Braces are useful when the identifier touches other characters.
from string import Template
model = Template("Hello, $name! Your plan is ${plan}Premium.")
text = model.substitute(name="Ana", plan="Python ")
print(text)The final text is produced without manual concatenation. This reduces punctuation mistakes and keeps the message readable. For broader background, see Python strings and Python f-strings.
substitute versus safe_substitute
substitute requires every referenced field. If a key is missing, Python raises KeyError. That strict behavior is useful when incomplete data should stop the operation immediately.
model = Template("Order $code for $customer")
model.substitute(code="A-10") # KeyError: customersafe_substitute, on the other hand, leaves an unresolved placeholder in the output. It is useful for previews, template editors, drafts, and workflows where data arrives in stages.
draft = model.safe_substitute(code="A-10")
print(draft) # Order A-10 for $customerThe word “safe” does not mean that the result is automatically safe for HTML, SQL, a shell, or another output context. It only avoids an exception for a missing mapping key. Output-specific validation and escaping are still required.
When to prefer Template over f-strings
F-strings are usually the best option when developers control the source code and need expressions, numeric format specifications, or function calls. Template is a better fit when the text lives outside the code in a file, database, CMS, or administration panel.
Its intentionally limited syntax is an advantage. A person editing a message cannot place arbitrary Python expressions inside a placeholder. The application controls the data mapping, while the template controls only presentation. This separation makes content easier to review, translate, and reuse.
For file-based templates, the guides about reading text files and working with JSON provide useful supporting techniques.
Using dictionaries as mappings
substitute accepts a mapping as its first argument. That makes integration with API results, form data, and JSON documents straightforward.
data = {"product": "Python Course", "price": "$99"}
model = Template("$product is available for $price")
print(model.substitute(data))You can combine a mapping with keyword arguments. Keyword arguments take priority, which allows a base dictionary to provide defaults while a specific call overrides selected values.
defaults = {"company": "Academify", "channel": "website"}
message = Template("$company provides support through the $channel")
print(message.substitute(defaults, channel="WhatsApp"))Validating placeholders
Recent Python versions provide is_valid() and get_identifiers(). The first checks whether the template syntax is valid. The second returns the placeholder names found in the text.
model = Template("Hello $name, order $code")
if not model.is_valid():
raise ValueError("Invalid template")
identifiers = model.get_identifiers()
allowed = {"name", "code"}
unknown = set(identifiers) - allowed
if unknown:
raise ValueError(f"Unknown fields: {unknown}")This validation is valuable before saving a user-edited template. The application can reject unknown fields, show a clear message, and ensure that every language version uses the expected internal variables. Validation at creation time is usually better than discovering a broken template while sending a customer notification.
Changing the delimiter
You may subclass Template and choose another delimiter when the dollar sign already has a domain-specific meaning.
class AtTemplate(Template):
delimiter = "@"
model = AtTemplate("Hello @name")
print(model.substitute(name="Carlos"))The matching pattern can also be customized, but that should be done carefully. A complicated placeholder language increases maintenance cost, documentation needs, and opportunities for parsing errors. Keeping the standard syntax or changing only the delimiter is sufficient for most projects.
Building an email renderer
A practical design stores message bodies in files and keeps validation in Python code. The function below loads a UTF-8 template, checks its syntax, restricts identifiers, and then renders the final result.
from pathlib import Path
from string import Template
def render(path, data):
content = Path(path).read_text(encoding="utf-8")
model = Template(content)
if not model.is_valid():
raise ValueError("The template syntax is invalid")
unknown = set(model.get_identifiers()) - set(data)
if unknown:
raise ValueError(f"Unsupported fields: {unknown}")
return model.substitute(data)This design lets content teams edit wording without modifying the rendering function. The code remains responsible for encoding, validation, approved fields, logging, and error handling. See pathlib in Python for more file-handling patterns.
Security considerations
Template does not evaluate Python expressions, which makes it safer than executing user-provided format expressions. However, it is not a universal sanitization layer. Values inserted into HTML should be escaped. SQL statements should always use the database driver’s parameter mechanism. Shell commands should use argument lists rather than a rendered command string.
Pass the smallest possible mapping. If an object contains private data, do not expose every attribute to a user-controlled template. Build a dedicated dictionary containing only approved values. This prevents a template from requesting information it was never meant to display.
Also decide whether unresolved placeholders should be allowed. A production invoice or transactional email should usually use strict substitute. A visual preview may use safe_substitute so editors can see which fields remain incomplete.
Testing templates
Tests should cover valid rendering, missing keys, repeated placeholders, escaped dollar signs with $$, identifiers next to other characters, and invalid placeholder syntax.
def test_payment_message():
model = Template("$name paid $$ $amount")
result = model.substitute(name="Lu", amount="20")
assert result == "Lu paid $ 20"Multilingual projects should test every translation with the same approved identifier set. The visible wording changes, but internal field names can remain stable. This keeps the application mapping independent from the selected language.
Formatting values before rendering
Template does not provide the rich numeric formatting syntax found in f-strings. Format values before placing them in the dictionary.
price = 129.9
data = {"price": f"${price:,.2f}", "quantity": str(2)}
message = Template("Quantity: $quantity — Total: $price")
print(message.substitute(data))This is often a clean design because business formatting stays in tested code while the content model remains simple. The same principle applies to dates, percentages, localized money, and optional values.
Common mistakes
A common mistake is using safe_substitute everywhere and allowing incomplete placeholders to reach customers. Another is passing a large dictionary with unnecessary secrets or internal fields. Teams may also assume that Template escapes HTML or protects SQL, which it does not.
Avoid turning the template syntax into a programming language. If you need loops, conditionals, inheritance, filters, and automatic HTML escaping, a dedicated engine such as Jinja may be more appropriate. Template is strongest when the task is straightforward variable replacement.
Best practices
Use descriptive placeholder names such as $customer_name. Document available fields near the editor. Validate templates when they are created or updated. Keep destination-specific escaping outside the template. Prefer strict substitution for finalized output and safe substitution for previews only.
Version templates with the application or maintain a revision history in the database. A small wording change can accidentally remove a required placeholder, so automated validation should run whenever content is deployed.
Conclusion
string.Template provides a small, readable, and intentionally limited solution for configurable text. It does not replace a full template engine, but it is an excellent fit for notifications, email messages, reports, and simple files. By validating identifiers, exposing only approved values, formatting data before rendering, and applying output-specific escaping, you can create editable templates without mixing presentation with executable logic.
Official references: Python template strings and html.escape.







