xiaohongshu
xiaohongshu
erpnext-impl-jinja
Implementation workflows and decision trees for Jinja templates in ERPNext/Frappe. Use when determining HOW to implement Print Formats, Email Templates, Portal Pages, or custom Jinja methods. Covers template type selection, context variables, styling, and V16 Chrome PDF rendering. Triggers: creat...
Full skill instructions
This skill helps you determine HOW to implement Jinja templates. For exact syntax, see erpnext-syntax-jinja.
Version: v14/v15/v16 compatible (with V16-specific features noted)
┌─────────────────────────────────────────────────────────────────────────┐
│ WHAT DO YOU WANT TO CREATE? │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ► Printable document (invoice, PO, report)? │
│ ├── Standard DocType → Print Format (Jinja) │
│ └── Query/Script Report → Report Print Format (JavaScript!) │
│ │
│ ► Automated email with dynamic content? │
│ └── Email Template (Jinja) │
│ │
│ ► Customer-facing web page? │
│ └── Portal Page (www/*.html + *.py) │
│ │
│ ► Reusable template functions/filters? │
│ └── Custom jenv methods in hooks.py │
│ │
│ ► Notification content? │
│ └── Notification Template (uses Jinja syntax) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
⚠️ CRITICAL: Report Print Formats use JAVASCRIPT templating, NOT Jinja!
- Jinja: {{ variable }}
- JS Report: {%= variable %}
WHAT ARE YOU PRINTING?
│
├─► Standard DocType (Invoice, PO, Quotation)?
│ │
│ │ WHERE TO CREATE?
│ ├─► Quick/simple format → Print Format Builder (Setup > Print)
│ │ - Drag-drop interface
│ │ - Limited customization
│ │
│ └─► Complex layout needed → Custom HTML Print Format
│ - Full Jinja control
│ - Custom CSS styling
│ - Dynamic logic
│
├─► Query Report or Script Report?
│ └─► Report Print Format (JAVASCRIPT template!)
│ ⚠️ NOT Jinja! Uses {%= %} and {% %}
│
└─► Letter or standalone document?
└─► Letter Head + Print Format combination
IS THIS A ONE-OFF OR REUSABLE?
│
├─► Site-specific, managed via UI?
│ └─► Create via Setup > Print Format / Email Template
│ - Stored in database
│ - Easy to edit without code
│
├─► Part of your custom app?
│ │
│ │ WHAT TYPE?
│ ├─► Print Format → myapp/fixtures or db records
│ │
│ ├─► Portal Page → myapp/www/pagename/
│ │ - index.html (template)
│ │ - index.py (context)
│ │
│ └─► Custom methods/filters → myapp/jinja/
│ - Registered via hooks.py jenv
│
└─► Template for multiple sites?
└─► Include in app, export as fixture
Setup > Printing > Print Format > New
- DocType: Sales Invoice
- Module: Accounts
- Standard: No (Custom)
- Print Format Type: Jinja
{# ALWAYS include styles at top #}
<style>
.print-format { font-family: Arial, sans-serif; }
.header { background: #f5f5f5; padding: 15px; }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { border: 1px solid #ddd; padding: 8px; }
.text-right { text-align: right; }
.footer { margin-top: 30px; border-top: 1px solid #ddd; }
</style>
{# Document header #}
<div class="header">
<h1>{{ doc.select_print_heading or _("Invoice") }}</h1>
<p><strong>{{ doc.name }}</strong></p>
<p>{{ _("Date") }}: {{ doc.get_formatted("posting_date") }}</p>
</div>
{# Items table #}
<table class="table">
<thead>
<tr>
<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>{{ 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>
{# Totals #}
<div class="text-right">
<p><strong>{{ _("Grand Total") }}:</strong> {{ doc.get_formatted("grand_total") }}</p>
</div>
1. Open a document (e.g., Sales Invoice)
2. Menu > Print > Select your format
3. Check layout, adjust CSS as needed
4. Test PDF generation
Setup > Email > Email Template > New
- Name: Payment Reminder
- Subject: Invoice {{ doc.name }} - Payment Due
- DocType: Sales Invoice
<p>{{ _("Dear") }} {{ doc.customer_name }},</p>
<p>{{ _("This is a reminder that invoice") }} <strong>{{ doc.name }}</strong>
{{ _("for") }} {{ doc.get_formatted("grand_total") }} {{ _("is due.") }}</p>
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
<tr>
<td style="padding: 8px; border: 1px solid #ddd;">
<strong>{{ _("Due Date") }}</strong>
</td>
<td style="padding: 8px; border: 1px solid #ddd;">
{{ frappe.format_date(doc.due_date) }}
</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;">
<strong>{{ _("Outstanding") }}</strong>
</td>
<td style="padding: 8px; border: 1px solid #ddd;">
{{ doc.get_formatted("outstanding_amount") }}
</td>
</tr>
</table>
{% if doc.items %}
<p><strong>{{ _("Items") }}:</strong></p>
<ul>
{% for item in doc.items %}
<li>{{ item.item_name }} ({{ item.qty }})</li>
{% endfor %}
</ul>
{% endif %}
<p>{{ _("Best regards") }},<br>
{{ frappe.db.get_value("Company", doc.company, "company_name") }}</p>
# In Server Script or Controller
frappe.sendmail(
recipients=[doc.email],
subject=frappe.render_template(
frappe.db.get_value("Email Template", "Payment Reminder", "subject"),
{"doc": doc}
),
message=frappe.get_template("Payment Reminder").render({"doc": doc})
)
myapp/
└── www/
└── projects/
├── index.html # Jinja template
└── index.py # Python context
{% extends "templates/web.html" %}
{% block title %}{{ _("Projects") }}{% endblock %}
{% block page_content %}
<div class="container">
<h1>{{ title }}</h1>
{% if frappe.session.user != 'Guest' %}
<p>{{ _("Welcome") }}, {{ frappe.get_fullname() }}</p>
{% endif %}
<div class="row">
{% for project in projects %}
<div class="col-md-4">
<div class="card">
<h3>{{ project.title }}</h3>
<p>{{ project.description | truncate(100) }}</p>
<a href="/projects/{{ project.name }}">{{ _("View Details") }}</a>
</div>
</div>
{% else %}
<p>{{ _("No projects found.") }}</p>
{% endfor %}
</div>
</div>
{% endblock %}
import frappe
def get_context(context):
context.title = "Projects"
context.no_cache = True # Dynamic content
# Fetch data
context.projects = frappe.get_all(
"Project",
filters={"is_public": 1},
fields=["name", "title", "description"],
order_by="creation desc"
)
return context
Visit: https://yoursite.com/projects
# myapp/hooks.py
jenv = {
"methods": ["myapp.jinja.methods"],
"filters": ["myapp.jinja.filters"]
}
# myapp/jinja/methods.py
import frappe
def get_company_logo(company):
"""Returns company logo URL - usable in any template"""
return frappe.db.get_value("Company", company, "company_logo") or ""
def get_address_display(address_name):
"""Format address for display"""
if not address_name:
return ""
return frappe.get_doc("Address", address_name).get_display()
def get_outstanding_amount(customer):
"""Get total outstanding for customer"""
result = frappe.db.sql("""
SELECT COALESCE(SUM(outstanding_amount), 0)
FROM `tabSales Invoice`
WHERE customer = %s AND docstatus = 1
""", customer)
return result[0][0] if result else 0
# myapp/jinja/filters.py
def format_phone(value):
"""Format phone number: 1234567890 → (123) 456-7890"""
if not value:
return ""
digits = ''.join(c for c in str(value) if c.isdigit())
if len(digits) == 10:
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
return value
def currency_words(amount, currency="EUR"):
"""Convert number to words (simplified)"""
return f"{currency} {amount:,.2f}"
{# Methods - called as functions #}
<img src="{{ get_company_logo(doc.company) }}" alt="Logo">
<p>{{ get_address_display(doc.customer_address) }}</p>
<p>Outstanding: {{ get_outstanding_amount(doc.customer) }}</p>
{# Filters - piped after values #}
<p>Phone: {{ doc.phone | format_phone }}</p>
<p>Amount: {{ doc.grand_total | currency_words }}</p>
bench --site sitename migrate
| Template Type | Available Objects |
|---|---|
| Print Format | doc, frappe, _() |
| Email Template | doc, frappe (limited) |
| Portal Page | frappe.session, frappe.form_dict, custom context |
| Notification | doc, frappe |
| Need | Method |
|---|---|
| Format currency/date | doc.get_formatted("fieldname") |
| Format child row | row.get_formatted("field", doc) |
| Translate string | _("String") |
| Get linked doc | frappe.get_doc("DocType", name) |
| Get single field | frappe.db.get_value("DT", name, "field") |
| Current date | frappe.utils.nowdate() |
| Format date | frappe.format_date(date) |
{# ❌ Raw database value #}
{{ doc.grand_total }}
{# ✅ Properly formatted with currency #}
{{ doc.get_formatted("grand_total") }}
{% for row in doc.items %}
{# ❌ Missing currency context #}
{{ row.get_formatted("rate") }}
{# ✅ Has currency context from parent #}
{{ row.get_formatted("rate", doc) }}
{% endfor %}
{# ❌ Not translatable #}
<h1>Invoice</h1>
{# ✅ Translatable #}
<h1>{{ _("Invoice") }}</h1>
<!-- Query/Script Reports use JAVASCRIPT templating -->
{% for(var i=0; i<data.length; i++) { %}
<tr><td>{%= data[i].name %}</td></tr>
{% } %}
{# ❌ N+1 query problem #}
{% for item in doc.items %}
{% set stock = frappe.db.get_value("Bin", ...) %}
{% endfor %}
{# ✅ Prefetch data in controller/context #}
{% for item in items_with_stock %}
{{ item.stock_qty }}
{% endfor %}
| Feature | V14 | V15 | V16 |
|---|---|---|---|
| Jinja templates | ✅ | ✅ | ✅ |
| get_formatted() | ✅ | ✅ | ✅ |
| jenv hooks | ✅ | ✅ | ✅ |
| wkhtmltopdf PDF | ✅ | ✅ | ⚠️ |
| Chrome PDF | ❌ | ❌ | ✅ |
See erpnext-syntax-jinja for detailed Chrome PDF documentation.
| File | Contents |
|---|---|
| decision-tree.md | Complete template type selection |
| workflows.md | Step-by-step implementation patterns |
| examples.md | Complete working examples |
| anti-patterns.md | Common mistakes to avoid |
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience