frappe-printing-templates

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frappe Printing & Templates

Frappe 打印与模板

Create print formats, email templates, and document templates using Jinja in Frappe.
在Frappe中使用Jinja创建打印格式、邮件模板和文档模板。

When to use

适用场景

  • Creating custom print formats for documents
  • Building email templates with dynamic content
  • Generating PDFs from documents
  • Using Jinja templating in web pages
  • Configuring letter heads for branding
  • Using the Print Format Builder
  • 为文档创建自定义打印格式
  • 构建含动态内容的邮件模板
  • 从文档生成PDF
  • 在网页中使用Jinja模板
  • 配置品牌信头
  • 使用打印格式构建器

Inputs required

所需输入

  • Target DocType for the print format
  • Layout requirements (fields, tables, headers)
  • Whether format is standard (version controlled) or custom (DB-stored)
  • Letter Head / branding requirements
  • PDF generation needs
  • 打印格式的目标DocType
  • 布局要求(字段、表格、页眉)
  • 格式类型为标准型(版本控制)还是自定义型(存储于数据库)
  • 信头/品牌化要求
  • PDF生成需求

Procedure

操作步骤

0) Choose format type

0) 选择格式类型

TypeHow to CreateVersion ControlledCustomizable by User
StandardDeveloper Mode, saved as JSONYesNo
Print Format BuilderDrag-and-drop UINo (DB)Yes
Custom HTML (Jinja)Type "new print format" in awesomebarOptionalDepends
类型创建方式版本控制用户可自定义
标准型开发者模式,保存为JSON
打印格式构建器拖拽式界面否(存储于数据库)
自定义HTML(Jinja)在快捷搜索栏输入“new print format”可选视情况而定

1) Create a Jinja print format

1) 创建Jinja打印格式

Create via awesomebar → "New Print Format":
  1. Set a unique name
  2. Link to the target DocType
  3. Set "Standard" = "No" (or "Yes" for dev mode export)
  4. Check "Custom Format"
  5. Set Print Format Type = "Jinja"
  6. Write your Jinja HTML
jinja
<div class="print-format">
    <h1>{{ doc.name }}</h1>
    <p><strong>{{ _("Customer") }}:</strong> {{ doc.customer }}</p>
    <p><strong>{{ _("Date") }}:</strong> {{ frappe.format_date(doc.transaction_date) }}</p>

    <table class="table table-bordered">
        <thead>
            <tr>
                <th>{{ _("Item") }}</th>
                <th>{{ _("Qty") }}</th>
                <th class="text-right">{{ _("Rate") }}</th>
                <th class="text-right">{{ _("Amount") }}</th>
            </tr>
        </thead>
        <tbody>
            {% for item in doc.items %}
            <tr>
                <td>{{ item.item_name }}</td>
                <td>{{ item.qty }}</td>
                <td class="text-right">{{ frappe.format(item.rate, {'fieldtype': 'Currency'}) }}</td>
                <td class="text-right">{{ frappe.format(item.amount, {'fieldtype': 'Currency'}) }}</td>
            </tr>
            {% endfor %}
        </tbody>
        <tfoot>
            <tr>
                <td colspan="3" class="text-right"><strong>{{ _("Total") }}</strong></td>
                <td class="text-right"><strong>{{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}</strong></td>
            </tr>
        </tfoot>
    </table>

    {% if doc.terms %}
    <div class="terms">
        <h4>{{ _("Terms & Conditions") }}</h4>
        <p>{{ doc.terms }}</p>
    </div>
    {% endif %}
</div>

<style>
    .print-format { font-family: Arial, sans-serif; }
    .print-format h1 { color: #333; }
    .print-format table { width: 100%; margin-top: 20px; }
</style>
通过快捷搜索栏创建 → “New Print Format”:
  1. 设置唯一名称
  2. 关联目标DocType
  3. 设置“Standard”为“No”(若要在开发者模式下导出则设为“Yes”)
  4. 勾选“Custom Format”
  5. 设置打印格式类型为“Jinja”
  6. 编写Jinja HTML代码
jinja
<div class="print-format">
    <h1>{{ doc.name }}</h1>
    <p><strong>{{ _("Customer") }}:</strong> {{ doc.customer }}</p>
    <p><strong>{{ _("Date") }}:</strong> {{ frappe.format_date(doc.transaction_date) }}</p>

    <table class="table table-bordered">
        <thead>
            <tr>
                <th>{{ _("Item") }}</th>
                <th>{{ _("Qty") }}</th>
                <th class="text-right">{{ _("Rate") }}</th>
                <th class="text-right">{{ _("Amount") }}</th>
            </tr>
        </thead>
        <tbody>
            {% for item in doc.items %}
            <tr>
                <td>{{ item.item_name }}</td>
                <td>{{ item.qty }}</td>
                <td class="text-right">{{ frappe.format(item.rate, {'fieldtype': 'Currency'}) }}</td>
                <td class="text-right">{{ frappe.format(item.amount, {'fieldtype': 'Currency'}) }}</td>
            </tr>
            {% endfor %}
        </tbody>
        <tfoot>
            <tr>
                <td colspan="3" class="text-right"><strong>{{ _("Total") }}</strong></td>
                <td class="text-right"><strong>{{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}</strong></td>
            </tr>
        </tfoot>
    </table>

    {% if doc.terms %}
    <div class="terms">
        <h4>{{ _("Terms & Conditions") }}</h4>
        <p>{{ doc.terms }}</p>
    </div>
    {% endif %}
</div>

<style>
    .print-format { font-family: Arial, sans-serif; }
    .print-format h1 { color: #333; }
    .print-format table { width: 100%; margin-top: 20px; }
</style>

2) Use Frappe Jinja API

2) 使用Frappe Jinja API

Data fetching in templates:
jinja
{# Fetch a document #}
{% set customer = frappe.get_doc('Customer', doc.customer) %}
{{ customer.customer_name }}

{# List query (ignores permissions) #}
{% set open_orders = frappe.get_all('Sales Order',
    filters={'customer': doc.customer, 'status': 'To Deliver and Bill'},
    fields=['name', 'grand_total'],
    order_by='creation desc',
    page_length=5) %}

{# Permission-aware list query #}
{% set my_tasks = frappe.get_list('Task',
    filters={'owner': frappe.session.user}) %}

{# Single value lookup #}
{% set company_abbr = frappe.db.get_value('Company', doc.company, 'abbr') %}

{# Settings value #}
{% set timezone = frappe.db.get_single_value('System Settings', 'time_zone') %}
Formatting:
jinja
{{ frappe.format(50000, {'fieldtype': 'Currency'}) }}
{{ frappe.format_date('2025-01-15') }}
{{ frappe.format_date(doc.posting_date) }}
Session and context:
jinja
{{ frappe.session.user }}
{{ frappe.get_fullname() }}
{{ frappe.lang }}
{{ _("Translatable string") }}
URLs:
jinja
<a href="{{ frappe.get_url() }}/app/sales-order/{{ doc.name }}">View Order</a>
模板中的数据获取:
jinja
{# 获取文档 #}
{% set customer = frappe.get_doc('Customer', doc.customer) %}
{{ customer.customer_name }}

{# 列表查询(忽略权限) #}
{% set open_orders = frappe.get_all('Sales Order',
    filters={'customer': doc.customer, 'status': 'To Deliver and Bill'},
    fields=['name', 'grand_total'],
    order_by='creation desc',
    page_length=5) %}

{# 权限感知型列表查询 #}
{% set my_tasks = frappe.get_list('Task',
    filters={'owner': frappe.session.user}) %}

{# 单值查询 #}
{% set company_abbr = frappe.db.get_value('Company', doc.company, 'abbr') %}

{# 设置值查询 #}
{% set timezone = frappe.db.get_single_value('System Settings', 'time_zone') %}
格式化:
jinja
{{ frappe.format(50000, {'fieldtype': 'Currency'}) }}
{{ frappe.format_date('2025-01-15') }}
{{ frappe.format_date(doc.posting_date) }}
会话与上下文:
jinja
{{ frappe.session.user }}
{{ frappe.get_fullname() }}
{{ frappe.lang }}
{{ _("Translatable string") }}
URL:
jinja
<a href="{{ frappe.get_url() }}/app/sales-order/{{ doc.name }}">View Order</a>

3) Build email templates

3) 构建邮件模板

jinja
Dear {{ doc.customer_name }},

Your order {{ doc.name }} has been confirmed.

Items:
{% for item in doc.items %}
- {{ item.item_name }} x {{ item.qty }}
{% endfor %}

Total: {{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}

Thank you,
{{ frappe.get_fullname() }}
jinja
Dear {{ doc.customer_name }},

Your order {{ doc.name }} has been confirmed.

Items:
{% for item in doc.items %}
- {{ item.item_name }} x {{ item.qty }}
{% endfor %}

Total: {{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}

Thank you,
{{ frappe.get_fullname() }}

4) Generate PDFs programmatically

4) 程序化生成PDF

python
import frappe
python
import frappe

Generate PDF

Generate PDF

pdf_content = frappe.get_print( doctype="Sales Invoice", name="SINV-001", print_format="Custom Invoice", as_pdf=True )
pdf_content = frappe.get_print( doctype="Sales Invoice", name="SINV-001", print_format="Custom Invoice", as_pdf=True )

Attach PDF to document

Attach PDF to document

frappe.attach_print( doctype="Sales Invoice", name="SINV-001", print_format="Custom Invoice", file_name="invoice.pdf" )
frappe.attach_print( doctype="Sales Invoice", name="SINV-001", print_format="Custom Invoice", file_name="invoice.pdf" )

Send with email

Send with email

frappe.sendmail( recipients=["customer@example.com"], subject="Your Invoice", message="Please find attached your invoice.", attachments=[{ "fname": "invoice.pdf", "fcontent": pdf_content }] )
undefined
frappe.sendmail( recipients=["customer@example.com"], subject="Your Invoice", message="Please find attached your invoice.", attachments=[{ "fname": "invoice.pdf", "fcontent": pdf_content }] )
undefined

5) Configure Letter Head

5) 配置信头

  1. Navigate to Letter Head list → New
  2. Upload company logo and header image
  3. Set as default for the company
  4. Letter Head appears automatically on print formats
  1. 进入信头列表 → 新建
  2. 上传公司Logo和页眉图片
  3. 设置为公司默认信头
  4. 信头会自动显示在打印格式中

6) Use Jinja filters

6) 使用Jinja过滤器

jinja
{{ doc.customer_name|upper }}        {# UPPERCASE #}
{{ doc.notes|truncate(100) }}        {# Truncate text #}
{{ doc.description|striptags }}      {# Remove HTML #}
{{ doc.html_content|safe }}          {# Render raw HTML (trusted only!) #}
{{ items|length }}                   {# Count items #}
{{ items|first }}                    {# First item #}
{{ names|join(', ') }}               {# Join list #}
{{ amount|round(2) }}               {# Round number #}
{{ value|default('N/A') }}          {# Default if undefined #}
{{ data|tojson }}                    {# Convert to JSON #}
jinja
{{ doc.customer_name|upper }}        {# 大写 #}
{{ doc.notes|truncate(100) }}        {# 截断文本 #}
{{ doc.description|striptags }}      {# 移除HTML标签 #}
{{ doc.html_content|safe }}          {# 渲染原始HTML(仅信任内容使用!) #}
{{ items|length }}                   {# 统计数量 #}
{{ items|first }}                    {# 第一个元素 #}
{{ names|join(', ') }}               {# 连接列表 #}
{{ amount|round(2) }}               {# 四舍五入保留两位小数 #}
{{ value|default('N/A') }}          {# 未定义时显示默认值 #}
{{ data|tojson }}                    {# 转换为JSON #}

7) Template inheritance and macros

7) 模板继承与宏

jinja
{# macros/fields.html #}
{% macro field_row(label, value) %}
<tr>
    <td class="label"><strong>{{ _(label) }}</strong></td>
    <td>{{ value }}</td>
</tr>
{% endmacro %}

{# In print format #}
{% from "macros/fields.html" import field_row %}
<table>
    {{ field_row("Customer", doc.customer_name) }}
    {{ field_row("Date", frappe.format_date(doc.posting_date)) }}
    {{ field_row("Total", frappe.format(doc.grand_total, {'fieldtype': 'Currency'})) }}
</table>
jinja
{# macros/fields.html #}
{% macro field_row(label, value) %}
<tr>
    <td class="label"><strong>{{ _(label) }}</strong></td>
    <td>{{ value }}</td>
</tr>
{% endmacro %}

{# 在打印格式中使用 #}
{% from "macros/fields.html" import field_row %}
<table>
    {{ field_row("Customer", doc.customer_name) }}
    {{ field_row("Date", frappe.format_date(doc.posting_date)) }}
    {{ field_row("Total", frappe.format(doc.grand_total, {'fieldtype': 'Currency'})) }}
</table>

Verification

验证项

  • Print format renders correctly in Print View
  • All fields display with proper formatting
  • PDF generation works without errors
  • Email templates render with correct data
  • Letter Head appears on printed documents
  • Translations work in templates (
    _()
    )
  • No XSS risks from unescaped content
  • 打印格式在打印视图中渲染正常
  • 所有字段显示格式正确
  • PDF生成无错误
  • 邮件模板渲染数据正确
  • 信头显示在打印文档上
  • 模板中的翻译功能正常(
    _()
  • 无未转义内容导致的XSS风险

Failure modes / debugging

故障排查

  • Template syntax error: Check Jinja delimiters (
    {{ }}
    ,
    {% %}
    ); look for unclosed blocks
  • Field not rendering: Verify field name matches DocType schema; check child table access pattern
  • PDF generation fails: Check wkhtmltopdf installation; verify print format Jinja is valid
  • Styling issues in PDF: Use inline styles; avoid complex CSS; test with Print View first
  • Permission error in template: Use
    frappe.get_all
    (no permission check) vs
    frappe.get_list
  • 模板语法错误:检查Jinja分隔符(
    {{ }}
    {% %}
    );查找未闭合的代码块
  • 字段未渲染:确认字段名称与DocType schema匹配;检查子表访问方式
  • PDF生成失败:检查wkhtmltopdf是否安装;验证打印格式的Jinja代码是否有效
  • PDF样式问题:使用内联样式;避免复杂CSS;先在打印视图中测试
  • 模板权限错误:区分使用
    frappe.get_all
    (无权限检查)和
    frappe.get_list
    (有权限检查)

Escalation

升级路径

  • For app-level hooks and structure →
    frappe-app-development
  • For DocType schema questions →
    frappe-doctype-development
  • 应用级钩子与结构问题 → 联系
    frappe-app-development
  • DocType schema问题 → 联系
    frappe-doctype-development

References

参考资料

  • references/jinja.md — Jinja templating and Frappe Jinja API
  • references/printing.md — Print formats and PDF generation
  • references/jinja.md — Jinja模板与Frappe Jinja API
  • references/printing.md — 打印格式与PDF生成

Guardrails

注意事项

  • Test with actual data: Always preview with real documents; edge cases break templates
  • Handle missing fields gracefully: Use
    {{ doc.field or '' }}
    or
    {% if doc.field %}
  • Use
    get_url()
    for images
    : Never hardcode URLs; use
    {{ frappe.utils.get_url() }}/files/...
  • Escape user content: Use
    {{ value | e }}
    for user-generated content to prevent XSS
  • Keep styling inline: PDF generators don't support external CSS; use inline
    style
    attributes
  • 使用真实数据测试:始终用真实文档预览;边缘场景会导致模板失效
  • 优雅处理缺失字段:使用
    {{ doc.field or '' }}
    {% if doc.field %}
  • 图片使用
    get_url()
    :切勿硬编码URL;使用
    {{ frappe.utils.get_url() }}/files/...
  • 转义用户内容:对用户生成的内容使用
    {{ value | e }}
    以防止XSS攻击
  • 样式使用内联方式:PDF生成器不支持外部CSS;使用内联
    style
    属性

Common Mistakes

常见错误

MistakeWhy It FailsFix
Wrong Jinja syntaxTemplate error, blank outputUse
{{ }}
for output,
{% %}
for logic; check closing tags
Missing filtersRaw data displayedUse
frappe.format()
or
frappe.format_date()
for formatting
Hardcoded URLsImages/links break across sitesUse
{{ frappe.utils.get_url() }}
for absolute URLs
Accessing child table wrongEmpty or errorUse
{% for item in doc.items %}
not
doc.child_table_name
Complex CSS in print formatStyling lost in PDFUse inline styles, simple layouts,
<table>
for structure
Not handling None values
'None'
string in output
Use
{{ value or '' }}
or
{% if value %}
错误失败原因修复方案
Jinja语法错误模板报错,输出空白输出用
{{ }}
,逻辑用
{% %}
;检查闭合标签
未使用过滤器显示原始数据使用
frappe.format()
frappe.format_date()
进行格式化
硬编码URL跨站点时图片/链接失效使用
{{ frappe.utils.get_url() }}
生成绝对URL
子表访问方式错误输出空白或报错使用
{% for item in doc.items %}
而非
doc.child_table_name
打印格式使用复杂CSSPDF中样式丢失使用内联样式、简单布局,用
<table>
构建结构
未处理空值输出显示
'None'
字符串
使用
{{ value or '' }}
{% if value %}