Portal Pages in Frappe Framework (Version 15)
Introduction — What Are Portal Pages in Frappe?
In the Frappe Framework, Portal Pages allow you to build dynamic, user-facing web pages that display personalized data to website users based on their login and assigned permissions.
They are the foundation of ERPNext’s user portal, where users can access their documents (e.g., quotations, sales orders, support tickets) in an organized, responsive format.
Portal Pages are stored as a DocType called Portal Page and combine Frappe’s Website module, Jinja templating, and context-based rendering to deliver secure and customizable front-end experiences.
Purpose of Portal Pages in Frappe v15
Portal Pages bridge the gap between back-end data and front-end display.
They serve as custom web interfaces for logged-in users and can be used to:
- Display document lists (e.g., Orders, Invoices, Issues).
- Render individual document details dynamically.
- Add personalized dashboards or reports.
- Integrate business workflows into the website frontend.
How Portal Pages Work in Frappe v15
Each Portal Page in Frappe defines how a specific document type or dataset appears on the website for a user.
When a user logs in to the website:
- Frappe fetches Portal Pages assigned to their roles.
- It filters data based on user permissions (frappe.has_permission).
- The page is rendered using a Jinja template that displays the data.
Portal Pages are configured using the Portal Page DocType and a Jinja HTML template (stored under /www).
Structure of a Portal Page
Each Portal Page is defined by the Portal Page DocType, which includes configuration fields such as:
| Field | Description |
| Title | Display name of the page on the website. |
| Route | URL path (e.g., /orders). |
| Reference DocType | The linked DocType (e.g., Sales Order, Issue). |
| Role | Restricts access based on assigned roles. |
| Template Path | File path to the Jinja HTML template. |
| Condition | Optional filter condition to limit which documents appear. |
| Limit | Maximum number of records shown per page. |
| Order By | Sort order for list view (e.g., creation desc). |
| Dynamic Template | Enables passing custom context data from Python. |
Example — Defining a Portal Page for “Sales Orders”
Step 1: Create a New Portal Page
- Go to Website → Portal Page → New.
- Fill out the following fields:
| Field | Example Value |
| Title | Sales Orders |
| Route | /sales-orders |
| Reference Doctype | Sales Order |
| Role | Customer |
| Condition | customer = frappe.session.user |
| Limit | 10 |
| Order By | creation desc |
Step 2: Create the Corresponding Jinja Template
In your app folder, create a template file under:
my_app/www/sales_orders.html
Example Template:
{% extends "templates/web.html" %}
{% block page_content %}
<h2>Your Sales Orders</h2>
<ul>
{% for order in items %}
<li>
<a href="/sales-order/{{ order.name }}">{{ order.name }}</a> - {{ order.status }}
</li>
{% endfor %}
</ul>
{% endblock %}
When a logged-in customer visits /sales-orders, this template dynamically lists all their Sales Orders.
Step 3: Add a Detail View (Optional)
To display individual Sales Order details, create another template:
my_app/www/sales-order.html
Example:
{% extends "templates/web.html" %}
{% block page_content %}
<h2>Sales Order: {{ doc.name }}</h2>
<p><strong>Status:</strong> {{ doc.status }}</p>
<p><strong>Customer:</strong> {{ doc.customer }}</p>
<p><strong>Grand Total:</strong> {{ doc.grand_total }}</p>
{% endblock %}
This page is automatically linked from the list view when the user clicks an order.
Dynamic Context with get_context()
For advanced use cases, you can control data rendering using Python’s get_context() method in the route’s Python file.
Example:
# File: my_app/www/sales_orders.py
import frappe
def get_context(context):
user = frappe.session.user
context.orders = frappe.get_all(
"Sales Order",
filters={"customer": user},
fields=["name", "status", "grand_total"],
order_by="creation desc"
)
return context
The context dictionary is passed to the Jinja template for rendering dynamic data.
Portal Page Routing in Frappe v15
Portal Pages can be accessed through website routes defined in the Portal Page DocType.
Frappe automatically maps each route to its corresponding template or dynamic context.
- Example Route: /support → Displays list of user issues.
- Detail Route: /support/<issue-name> → Displays individual issue details.
When a user navigates to these URLs, Frappe loads:
- The template specified in the Portal Page.
- The document context (via get_context or automatic lookup).
Example — Portal Page for “Issues” (Support Portal)
Portal Page Configuration:
| Field | Value |
| Title | My Support Tickets |
| Route | /support |
| Reference Doctype | Issue |
| Role | Customer |
| Order By | modified desc |
Template Example:
{% extends "templates/web.html" %}
{% block page_content %}
<h3>Your Support Tickets</h3>
<table class="table table-bordered">
<tr><th>Subject</th><th>Status</th><th>Modified</th></tr>
{% for issue in items %}
<tr>
<td><a href="/support/{{ issue.name }}">{{ issue.subject }}</a></td>
<td>{{ issue.status }}</td>
<td>{{ frappe.format_date(issue.modified) }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
Best Practices for Building Portal Pages
- Use frappe.get_all() instead of direct SQL for fetching records.
- Restrict data visibility using Role and Condition fields.
- Always sanitize user data before rendering.
- Use caching for large datasets.
- Combine with Frappe Web Forms for interactive user input.
- Keep Jinja templates lightweight and reusable.
Troubleshooting Common Issues
| Issue | Cause | Solution |
| Page not visible on website | Role not assigned or user not logged in | Assign appropriate user role |
| Data not displayed | Incorrect filter or missing condition | Verify filters in Condition field |
| Route conflict | Duplicate route name | Ensure route uniqueness across website pages |
| “No Permission” error | User lacks access to linked DocType | Update DocType permissions in Role Permission Manager |
Cross-References and Related Topics
- Web Templates in Frappe Framework (v15)
- Frappe Jinja Templating Guide
- Frappe Website Module (GitHub v15)
- Portal Pages DocType Reference
- Building Dynamic Portals in ERPNext