Frappe Page API Guide (v15)
What is a Page in Frappe?
A Page in Frappe is a UI container that renders a custom interface inside the Desk, built without using a DocType. It allows developers to create fully custom layouts, dashboards, and tools using Jinja, JavaScript, and Python backend logic.
A Page is defined in the file system under an app and loads through Desk routing. It has its own controller, templates, and hooks.
Where are Pages used?
Pages are used when UI requirements cannot be solved with standard DocTypes, List Views, or forms. Common use cases include:
- Analytical dashboards
- Management consoles
- Custom workflow screens
- Reporting pages
- Data visualization
- External service integrations
Custom UI tools (e.g., POS, project boards)
How Pages work in Frappe (v15)
The Page architecture is based on:
- Python controller: Page server logic
- JS controller: UI logic and events
- HTML/Jinja template: rendering
- Route mapping: access from Desk
- Permissions: enforced through Python
Directory Structure:
your_app/
└─ your_app/your_app/page/
└─ your_page/
├─ __init__.py
├─ your_page.py
├─ your_page.js
└─ your_page.html
How to Create a Page in Frappe?
Step 1: Create Page via Bench
The simplest way to create a Page:
bench new-page your_page
This generates the complete scaffold under your app.
Step 2: Access Your Page
Once installed, open the page through:
https://<site>/app/your-page
OR via Desk Search:
Ctrl + K → Your Page Name
Page Components Breakdown
1. Python Controller (your_page.py)
This is the backend script loaded when the page route is accessed. It receives context from the Desk and injects data into the HTML template.
Example:
import frappe
def get_context(context):
context.data = frappe.get_list("Customer", fields=["name"])
2. Client Script (your_page.js)
This defines the page UI behavior, including events, API calls, and DOM changes.
Example:
frappe.pages['your-page'].on_page_load = function(wrapper) {
let page = frappe.ui.make_app_page({
parent: wrapper,
title: 'Your Custom Page',
single_column: true
});
frappe.call({
method: "your_app.your_app.page.your_page.your_page.get_data",
callback: function(r) {
console.log(r.message);
}
});
}
3. HTML Template (your_page.html)
Defines layout content:
<div class="your-page-content">
<h2>Customer List</h2>
<ul>
{% for row in data %}
<li>{{ row.name }}</li>
{% endfor %}
</ul>
</div>
Routing and Access
Page Route
By default, the page route uses the folder name:
/app/your-page
You can register custom routes in hooks.py:
website_route_rules = [
{"from_route": "/dashboard", "to_route": "your-page"}
]
Adding Actions and UI Elements
Frappe provides multiple UI builders to compose interface components:
Add Button
page.add_primary_action("Refresh", () => location.reload());
Add Menu Item
page.add_menu_item("Help", () => window.open("https://docs.frappe.io"));
Add Field
let field = page.add_field({
fieldtype: "Data",
label: "Search",
fieldname: "search"
});
Fetching Data from Python in Page Script
Direct call from JS
frappe.call({
method: "your_app.your_app.page.your_page.your_page.get_context",
args: {},
callback(r) {
console.log(r.message);
}
});
Python Side
@frappe.whitelist()
def get_context():
return frappe.get_all("Customer", fields=["name", "customer_name"])
Page Permission Handling
Permission rules depend on:
- User role assignment
- Page-level permission defined in metadata
- Custom server-side validation
Recommended security flow:
- validate roles in Python
- do not expose sensitive data via client code
- use frappe.whitelist() only when needed
Example:
if "System Manager" not in frappe.get_roles():
frappe.throw("Not Permitted")
Official References (Verified v15)
Docs: https://docs.frappe.io/framework/user/en/api/page
Repo: https://github.com/frappe/frappe/tree/version-15
Folders:
frappe/desk/page/
frappe/website/router.py
frappe/desk/utils.py