frappe-syntax-jinja

GitHub

提供Frappe ERPNext系统中Jinja模板(打印格式、邮件通知、门户页面)的语法参考,涵盖上下文变量、过滤器及v14-v16版本特性,防止常见错误。

skills/source/syntax/frappe-syntax-jinja/SKILL.md Impertio-Studio/Frappe_Claude_Skill_Package

Trigger Scenarios

编写ERPNext打印格式 创建动态邮件模板 开发Portal Pages页面 配置系统通知模板

Install

npx skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-syntax-jinja -g -y
More Options

Non-standard path

npx skills add https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package/tree/main/skills/source/syntax/frappe-syntax-jinja -g -y

Use without installing

npx skills use Impertio-Studio/Frappe_Claude_Skill_Package@frappe-syntax-jinja

指定 Agent (Claude Code)

npx skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-syntax-jinja -a claude-code -g -y

安装 repo 全部 skill

npx skills add Impertio-Studio/Frappe_Claude_Skill_Package --all -g -y

预览 repo 内 skill

npx skills add Impertio-Studio/Frappe_Claude_Skill_Package --list

SKILL.md

Frontmatter
{
    "name": "frappe-syntax-jinja",
    "license": "MIT",
    "metadata": {
        "author": "OpenAEC-Foundation",
        "version": "2.0"
    },
    "description": "Use when writing Jinja templates for ERPNext\/Frappe Print Formats, Email Templates, and Portal Pages. Covers template syntax, context variables, filters, macros, and v16 Chrome PDF rendering. Prevents common mistakes with doc context and child table iteration. Keywords: Jinja, print format, email template, portal page, template syntax, PDF, v14-v16, template syntax, Jinja example, print format code, how to show child table in print.\n",
    "compatibility": "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16."
}

Frappe Jinja Templates Syntax

Deterministic Jinja reference for Print Formats, Email Templates, Notification Templates, and Portal Pages in Frappe v14/v15/v16.


When to Use This Skill

USE when:

  • Creating or modifying Print Formats (Jinja-based)
  • Writing Email Templates with dynamic fields
  • Building Portal Pages (www/*.html) with Python controllers
  • Writing Notification Templates (system/email/SMS)
  • Registering custom Jinja methods or filters via hooks.py

DO NOT USE for:

  • Report Print Formats — they use JavaScript templating ({%= %}), NOT Jinja
  • Client Scripts — see frappe-syntax-clientscripts
  • Server Scripts — see frappe-syntax-serverscripts

Decision Tree: Which Template Type?

Need a printable document?
├─ YES → Is it for a Query/Script Report?
│        ├─ YES → Use JS Template ({%= %}), NOT Jinja
│        └─ NO  → Use Jinja Print Format
└─ NO  → Is it for email?
         ├─ YES → Is it triggered by workflow/notification?
         │        ├─ YES → Notification Template (Jinja)
         │        └─ NO  → Email Template (Jinja)
         └─ NO  → Is it a web page?
                  ├─ YES → Portal Page (www/*.html + .py controller)
                  └─ NO  → frappe.render_template() for ad-hoc rendering

Quick Reference: Jinja Syntax

Syntax Purpose Example
{{ }} Output expression {{ doc.name }}
{% %} Control statement {% if doc.status == "Paid" %}
{# #} Comment {# This is a comment #}
{{ _("text") }} Translation {{ _("Invoice") }}
{{ val | filter }} Filter {{ name | default("N/A") }}

CRITICAL: Jinja vs JS Template Syntax

Aspect Jinja (Print Formats) JS Template (Report Print Formats)
Output {{ expression }} {%= expression %}
Code block {% statement %} {% js_code %}
Language Python JavaScript
Context doc, frappe data, filters

NEVER use Jinja syntax in Report Print Formats. NEVER use {%= %} in standard Print Formats.


Context Objects by Template Type

Print Formats

Object Description
doc The document being printed (full Document object)
frappe Frappe module (whitelisted methods only)
frappe.utils Utility functions
_() Translation function
doc.items, doc.taxes Child table accessors (by fieldname)

Email Templates

Object Description
doc The linked document (when triggered from a DocType)
frappe Frappe module (limited)
_() Translation function

Notification Templates

Object Description
doc The document that triggered the notification
frappe Frappe module
_() Translation function

Portal Pages (www/*.html)

Object Description
frappe Frappe module
frappe.session.user Current authenticated user
frappe.form_dict Query parameters from URL
frappe.lang Current language code
Custom context Set via get_context(context) in .py controller

Full details: references/context-objects.md


Essential Methods (Whitelisted in Jinja)

Formatting: ALWAYS Use for Display

{# ALWAYS use get_formatted() for fields in Print Formats #}
{{ doc.get_formatted("posting_date") }}
{{ doc.get_formatted("grand_total") }}

{# Child table rows — ALWAYS pass parent doc for currency context #}
{% for row in doc.items %}
    {{ row.get_formatted("rate", doc) }}
    {{ row.get_formatted("amount", doc) }}
{% endfor %}

{# General formatting with explicit fieldtype #}
{{ frappe.format(value, {'fieldtype': 'Currency'}) }}
{{ frappe.format_date(doc.posting_date) }}

Document Retrieval

{# Full document — use only when multiple fields needed #}
{% set customer = frappe.get_doc("Customer", doc.customer) %}

{# Single field — ALWAYS prefer over get_doc for one field #}
{% set abbr = frappe.db.get_value("Company", doc.company, "abbr") %}

{# List of records (no permission check) #}
{% set tasks = frappe.get_all("Task",
    filters={"status": "Open"},
    fields=["title", "due_date"],
    order_by="due_date asc",
    page_length=10) %}

{# List with permission check (portal pages) #}
{% set orders = frappe.get_list("Sales Order",
    filters={"customer": doc.customer},
    fields=["name", "grand_total"]) %}

Translation: REQUIRED for All User-Facing Strings

<h1>{{ _("Invoice") }}</h1>
<p>{{ _("Total: {0}").format(doc.get_formatted("grand_total")) }}</p>

System & Session

{{ frappe.get_url() }}
{{ frappe.get_fullname() }}
{{ frappe.get_fullname(doc.owner) }}
{{ frappe.db.get_single_value("System Settings", "time_zone") }}
{% if frappe.session.user != "Guest" %}...{% endif %}

Full method reference: references/methods-reference.md


Control Structures

Conditionals

{% if doc.status == "Paid" %}
    <span class="paid">{{ _("Paid") }}</span>
{% elif doc.status == "Overdue" %}
    <span class="overdue">{{ _("Overdue") }}</span>
{% else %}
    <span>{{ doc.status }}</span>
{% endif %}

Loops with Child Tables

{% for item in doc.items %}
<tr>
    <td>{{ loop.index }}</td>
    <td>{{ item.item_name }}</td>
    <td>{{ item.get_formatted("amount", doc) }}</td>
</tr>
{% else %}
<tr><td colspan="3">{{ _("No items") }}</td></tr>
{% endfor %}

Loop Variables

Variable Description
loop.index 1-indexed position
loop.index0 0-indexed position
loop.first True on first iteration
loop.last True on last iteration
loop.length Total number of items

Variables

{% set total = 0 %}
{% set name = doc.customer_name | default("Unknown") %}

Filters

Filter Example Notes
default {{ val | default("N/A") }} ALWAYS use for optional fields
length {{ items | length }} Count items
join {{ names | join(", ") }} Join list to string
truncate {{ text | truncate(100) }} Truncate with ellipsis
escape {{ input | escape }} HTML-escape (default behavior)
safe {{ html | safe }} Render raw HTML — NEVER for user input
round {{ num | round(2) }} Round number
lower / upper {{ text | upper }} Case conversion

Full filter reference: references/filters-reference.md


Custom Jinja Methods & Filters via hooks.py

hooks.py Registration

# hooks.py
jenv = {
    "methods": [
        "myapp.jinja.methods"       # Module with callable functions
    ],
    "filters": [
        "myapp.jinja.filters"       # Module with filter functions
    ]
}

Custom Method

# myapp/jinja/methods.py
import frappe

def get_company_logo(company):
    """Returns company logo URL. Called as get_company_logo() in Jinja."""
    return frappe.db.get_value("Company", company, "company_logo") or ""
<img src="{{ get_company_logo(doc.company) }}" alt="Logo">

Custom Filter

# myapp/jinja/filters.py
def nl2br(text):
    """Convert newlines to <br> tags. Used as {{ text | nl2br }}."""
    return (text or "").replace("\n", "<br>")
{{ doc.notes | nl2br | safe }}

Details: references/methods.md


Print Format Patterns

Minimal Print Format Template

<style>
    .print-header { background: #f5f5f5; padding: 15px; }
    .item-table { width: 100%; border-collapse: collapse; }
    .item-table th, .item-table td { border: 1px solid #ddd; padding: 8px; }
    .text-right { text-align: right; }
</style>

<div class="print-header">
    <h1>{{ doc.select_print_heading or _("Invoice") }}</h1>
    <p>{{ doc.name }} — {{ doc.get_formatted("posting_date") }}</p>
</div>

<table class="item-table">
    <thead>
        <tr>
            <th>#</th>
            <th>{{ _("Item") }}</th>
            <th class="text-right">{{ _("Qty") }}</th>
            <th class="text-right">{{ _("Amount") }}</th>
        </tr>
    </thead>
    <tbody>
        {% for row in doc.items %}
        <tr>
            <td>{{ loop.index }}</td>
            <td>{{ row.item_name }}</td>
            <td class="text-right">{{ row.qty }}</td>
            <td class="text-right">{{ row.get_formatted("amount", doc) }}</td>
        </tr>
        {% endfor %}
    </tbody>
</table>

<p><strong>{{ _("Grand Total") }}: {{ doc.get_formatted("grand_total") }}</strong></p>

Page Breaks

/* v14/v15 (wkhtmltopdf) */
.page-break { page-break-before: always; }

/* v16 (Chrome PDF) — ALWAYS prefer break-* in v16 */
.page-break { break-before: page; }

Full examples: references/examples.md | Patterns: references/patterns.md


V16: Chrome PDF Rendering

Aspect v14/v15 (wkhtmltopdf) v16 (Chrome)
CSS Support Limited CSS3 Full modern CSS
Flexbox/Grid Partial Full support
Page breaks page-break-* break-* preferred
Fonts System fonts only Web fonts supported

V16 Configuration

// site_config.json
{
    "pdf_engine": "chrome",
    "chrome_path": "/usr/bin/chromium"
}

Portal Page Pattern

www/projects/index.html

{% extends "templates/web.html" %}
{% block title %}{{ _("Projects") }}{% endblock %}

{% block page_content %}
<h1>{{ _("Projects") }}</h1>
{% for project in projects %}
    <h3>{{ project.title }}</h3>
    <p>{{ project.description | default("") | truncate(150) }}</p>
{% else %}
    <p>{{ _("No projects found.") }}</p>
{% endfor %}
{% endblock %}

www/projects/index.py

import frappe

def get_context(context):
    context.title = "Projects"
    context.no_cache = True
    context.projects = frappe.get_all("Project",
        filters={"is_public": 1},
        fields=["name", "title", "description"],
        order_by="creation desc")
    return context

Full structure: references/structure.md | Templates: references/templates.md


Critical Rules

ALWAYS

  1. Use _() for ALL user-facing strings
  2. Use get_formatted() for currency, date, and numeric fields
  3. Use default() filter for optional/nullable fields
  4. Pass parent doc to child row get_formatted("field", doc)
  5. Use frappe.db.get_value() when you need only one field
  6. Keep calculations in Python controllers, not Jinja templates

NEVER

  1. Execute database queries inside loops (N+1 problem)
  2. Use | safe on user-supplied input (XSS vulnerability)
  3. Use Jinja syntax in Report Print Formats (they require JS {%= %})
  4. Use frappe.get_doc() when frappe.db.get_value() suffices
  5. Hardcode strings without _() translation wrapper
  6. Disable safe_render without security review

Anti-patterns with fixes: references/anti-patterns.md


Reference Files

File Contents
references/syntax.md Jinja syntax reference (tags, filters, tests, loops)
references/methods.md Custom Jinja methods/filters via hooks
references/context-objects.md Available objects per template type
references/filters-reference.md All standard and custom Frappe filters
references/methods-reference.md All frappe.* methods available in Jinja
references/examples.md Complete Print Format, Email, Portal examples
references/anti-patterns.md Common mistakes and correct alternatives
references/templates.md Template structure patterns
references/patterns.md Conditional rendering, loops, child tables
references/structure.md File structure for template types

See Also

  • frappe-syntax-hooks — jenv configuration in hooks.py
  • frappe-impl-printformat — Print Format implementation patterns
  • frappe-errors-serverscripts — Server-side error handling

Version History

  • 36cfa80 Current 2026-08-20 10:18

Same Skill Collection

skills/source/agents/frappe-agent-interpreter/SKILL.md
skills/source/agents/frappe-agent-validator/SKILL.md
skills/source/core/frappe-core-api/SKILL.md
skills/source/core/frappe-core-database/SKILL.md
skills/source/core/frappe-core-permissions/SKILL.md
skills/source/core/frappe-core-utils/SKILL.md
skills/source/ops/frappe-ops-bench/SKILL.md
skills/source/ops/frappe-ops-cloud/SKILL.md
skills/source/syntax/frappe-syntax-clientscripts/SKILL.md
skills/source/syntax/frappe-syntax-controllers/SKILL.md
skills/source/syntax/frappe-syntax-customapp/SKILL.md
skills/source/syntax/frappe-syntax-hooks/SKILL.md
skills/source/syntax/frappe-syntax-scheduler/SKILL.md
skills/source/syntax/frappe-syntax-whitelisted/SKILL.md
skills/source/agents/frappe-agent-architect/SKILL.md
skills/source/agents/frappe-agent-debugger/SKILL.md
skills/source/agents/frappe-agent-migrator/SKILL.md
skills/source/core/frappe-core-cache/SKILL.md
skills/source/core/frappe-core-files/SKILL.md
skills/source/core/frappe-core-logging/SKILL.md
skills/source/core/frappe-core-notifications/SKILL.md
skills/source/core/frappe-core-search/SKILL.md
skills/source/core/frappe-core-translation/SKILL.md
skills/source/core/frappe-core-workflow/SKILL.md
skills/source/errors/frappe-errors-api/SKILL.md
skills/source/errors/frappe-errors-clientscripts/SKILL.md
skills/source/errors/frappe-errors-controllers/SKILL.md
skills/source/errors/frappe-errors-database/SKILL.md
skills/source/errors/frappe-errors-hooks/SKILL.md
skills/source/errors/frappe-errors-permissions/SKILL.md
skills/source/errors/frappe-errors-serverscripts/SKILL.md
skills/source/impl/frappe-impl-clientscripts/SKILL.md
skills/source/impl/frappe-impl-controllers/SKILL.md
skills/source/impl/frappe-impl-customapp/SKILL.md
skills/source/impl/frappe-impl-hooks/SKILL.md
skills/source/impl/frappe-impl-integrations/SKILL.md
skills/source/impl/frappe-impl-jinja/SKILL.md
skills/source/impl/frappe-impl-reports/SKILL.md
skills/source/impl/frappe-impl-scheduler/SKILL.md
skills/source/impl/frappe-impl-serverscripts/SKILL.md
skills/source/impl/frappe-impl-ui-components/SKILL.md
skills/source/impl/frappe-impl-website/SKILL.md
skills/source/impl/frappe-impl-whitelisted/SKILL.md
skills/source/impl/frappe-impl-workflow/SKILL.md
skills/source/impl/frappe-impl-workspace/SKILL.md
skills/source/ops/frappe-ops-app-lifecycle/SKILL.md
skills/source/ops/frappe-ops-backup/SKILL.md
skills/source/ops/frappe-ops-deployment/SKILL.md
skills/source/ops/frappe-ops-frontend-build/SKILL.md

Metadata

Files
0
Version
36cfa80
Hash
4a7507ab
Indexed
2026-08-20 10:18

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-21 15:17
浙ICP备14020137号-1 $방문자$