Skip to main content

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:

  1. Frappe fetches Portal Pages assigned to their roles.
  2. It filters data based on user permissions (frappe.has_permission).
  3. 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

  1. Go to Website → Portal Page → New.
  2. 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:

  1. The template specified in the Portal Page.
  2. 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

Click to rate this post!
[Total: 1 Average: 5]