# Custom Filters URL: /docs/extending/custom-filters/ Section: extending Tags: extending, filters -------------------------------------------------------------------------------- Custom Filters Filters transform values in template expressions. Basic Filter from kida import Environment env = Environment() def double(value): return value * 2 env.add_filter("double", double) Template usage: {{ 21 | double }} {# Output: 42 #} Decorator Syntax @env.filter() def double(value): return value * 2 @env.filter("twice") # Custom name def my_double(value): return value * 2 Filter Arguments @env.filter() def truncate_words(value, count=10, end="..."): words = str(value).split() if len(words) <= count: return value return " ".join(words[:count]) + end Template usage: {{ text | truncate_words(5) }} {{ text | truncate_words(10, end="[more]") }} Keyword Arguments @env.filter() def format_number(value, decimals=2, separator=","): formatted = f"{value:,.{decimals}f}" if separator != ",": formatted = formatted.replace(",", separator) return formatted Template usage: {{ price | format_number }} {{ price | format_number(decimals=0) }} {{ price | format_number(separator=".") }} Handling None Make filters None-resilient: @env.filter() def upper_safe(value): if value is None: return "" return str(value).upper() Returning Markup For HTML output, return Markup to prevent double-escaping: from kida import Markup @env.filter() def bold(value): escaped = Markup.escape(str(value)) return Markup(f"<b>{escaped}</b>") @env.filter() def link(value, url): escaped_text = Markup.escape(str(value)) escaped_url = Markup.escape(str(url)) return Markup(f'<a href="{escaped_url}">{escaped_text}</a>') Batch Registration filters = { "double": lambda x: x * 2, "triple": lambda x: x * 3, "reverse": lambda x: x[::-1], } env.update_filters(filters) Common Patterns Currency Formatting @env.filter() def currency(value, symbol="$", decimals=2): if value is None: return "" return f"{symbol}{value:,.{decimals}f}" Date Formatting from datetime import datetime @env.filter() def format_date(value, pattern="%Y-%m-%d"): if value is None: return "" if isinstance(value, str): value = datetime.fromisoformat(value) return value.strftime(pattern) Text Slugification import re @env.filter() def slugify(value): if value is None: return "" text = str(value).lower() text = re.sub(r"[^\w\s-]", "", text) return re.sub(r"[-\s]+", "-", text).strip("-") Pluralization @env.filter() def pluralize(count, singular, plural=None): if plural is None: plural = singular + "s" return singular if count == 1 else plural Usage: {{ items | length }} {{ items | length | pluralize("item") }} Best Practices Keep Filters Pure # ✅ Pure: no side effects @env.filter() def process(value): return value.upper() # ❌ Impure: modifies external state counter = 0 @env.filter() def count_calls(value): global counter counter += 1 # Side effect return value Handle Edge Cases @env.filter() def safe_divide(value, divisor): if divisor == 0: return 0 # Or raise error return value / divisor Document Filters @env.filter() def initials(name, separator=""): """ Extract initials from a name. Args: name: Full name string separator: Character between initials Returns: Initials (e.g., "JD" for "John Doe") """ if not name: return "" return separator.join( word[0].upper() for word in name.split() if word ) See Also Built-in Filters — All built-in filters Custom Filters Tutorial — Step-by-step guide Filter Syntax — Using filters in templates -------------------------------------------------------------------------------- Metadata: - Word Count: 426 - Reading Time: 2 minutes