# Build Custom Filters URL: /docs/tutorials/custom-filters/ Section: tutorials Tags: tutorial, filters, extending -------------------------------------------------------------------------------- Build Custom Filters Create custom template filters for domain-specific transformations. Prerequisites Python 3.14+ Kida installed Basic Python knowledge Step 1: Understand Filters Filters transform values in templates: {{ value | filter_name }} {{ value | filter_name(arg1, arg2) }} A filter is a Python function that takes a value and returns a transformed value. Step 2: Create a Simple Filter from kida import Environment env = Environment() # Simple filter: double a number def double(value): return value * 2 env.add_filter("double", double) Use in template: {{ 21 | double }} {# Output: 42 #} Step 3: Filters with Arguments def truncate_words(value, count=10, end="..."): """Truncate text to a number of words.""" words = str(value).split() if len(words) <= count: return value return " ".join(words[:count]) + end env.add_filter("truncate_words", truncate_words) Use in template: {{ long_text | truncate_words(5) }} {{ long_text | truncate_words(10, end="[more]") }} Step 4: Use the Decorator @env.filter() def format_price(value, currency="$", decimals=2): """Format a number as currency.""" return f"{currency}{value:,.{decimals}f}" @env.filter("money") # Custom name def format_money(value): return f"${value:,.2f}" Step 5: Handle None Values Make filters None-resilient: @env.filter() def slugify(value): """Convert text to URL slug.""" if value is None: return "" import re text = str(value).lower() text = re.sub(r"[^\w\s-]", "", text) return re.sub(r"[-\s]+", "-", text).strip("-") Step 6: Return Markup For filters that return HTML, use Markup: from kida import Markup @env.filter() def highlight(value, term): """Highlight search term in text.""" if not term or not value: return value # Escape the text first escaped = Markup.escape(str(value)) term_escaped = Markup.escape(term) # Replace with highlighted version highlighted = str(escaped).replace( term, f'<mark>{term_escaped}</mark>' ) return Markup(highlighted) Complete Examples Date Formatting from datetime import datetime, date @env.filter() def format_date(value, format="%B %d, %Y"): """Format a date or datetime.""" if value is None: return "" if isinstance(value, str): value = datetime.fromisoformat(value) if isinstance(value, (date, datetime)): return value.strftime(format) return str(value) {{ post.date | format_date }} {{ event.time | format_date("%I:%M %p") }} File Size @env.filter() def filesize(value, binary=False): """Format bytes as human-readable size.""" if value is None: return "0 B" value = float(value) base = 1024 if binary else 1000 suffix = "iB" if binary else "B" for unit in ["", "K", "M", "G", "T"]: if abs(value) < base: return f"{value:.1f} {unit}{suffix}" value /= base return f"{value:.1f} P{suffix}" {{ file.size | filesize }} {{ file.size | filesize(binary=true) }} Pluralization @env.filter() def pluralize(count, singular, plural=None): """Return singular or plural form.""" if plural is None: plural = singular + "s" return singular if count == 1 else plural {{ items | length }} {{ items | length | pluralize("item") }} {# "3 items" or "1 item" #} JSON Syntax Highlighting import json from kida import Markup @env.filter() def json_pretty(value, indent=2): """Pretty-print JSON with HTML formatting.""" formatted = json.dumps(value, indent=indent, default=str) # Could add syntax highlighting here return Markup(f'<pre><code>{Markup.escape(formatted)}</code></pre>') Testing Filters def test_format_price(): env = Environment() @env.filter() def format_price(value, currency="$"): return f"{currency}{value:,.2f}" template = env.from_string("{{ amount | format_price }}") assert template.render(amount=1234.5) == "$1,234.50" template = env.from_string("{{ amount | format_price('€') }}") assert template.render(amount=100) == "€100.00" Best Practices Handle Edge Cases @env.filter() def safe_divide(value, divisor): """Divide with fallback for zero.""" if divisor == 0: return 0 return value / divisor Document Your Filters @env.filter() def initials(name, separator=""): """ Extract initials from a name. Args: name: Full name string separator: Character between initials Returns: Initials string (e.g., "JD" for "John Doe") """ if not name: return "" return separator.join(word[0].upper() for word in name.split() if word) Keep Filters Pure Filters should not have side effects: # ✅ Pure: no side effects @env.filter() def double(value): return value * 2 # ❌ Impure: modifies state @env.filter() def increment_counter(value): global counter counter += 1 # Side effect! return value Next Steps Custom Filters Reference — Full filter API Built-in Filters — Filter reference Custom Tests — Create test functions -------------------------------------------------------------------------------- Metadata: - Word Count: 613 - Reading Time: 3 minutes