Add portal page
Skill prilk-consulting/frappe-agent-kit/skills/add-portal-page
Production-grade Frappe Framework & ERPNext skills, agents, and scaffolding commands for Claude Code
npx -y skills add prilk-consulting/frappe-agent-kit --skill add-portal-pageAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Step-by-step workflow to expose a DocType on the Frappe/ERPNext portal as a list + detail page with access control. Use when the user asks to show documents on the portal, customer portal page, or "let customers see their X". For pattern background and gotchas, see frappe-portal.
SKILL.md
4.2 KB, 948 tokens by cl100k_base, as published. Nobody here has run it
Add a Portal Page for a DocType
End-to-end workflow. Uses the portal-list pattern (right for transaction/user-owned documents — the common case). If the content is publishable (blog-like, public URLs per document), stop and use the WebsiteGenerator pattern from the frappe-portal skill instead.
Usage
Use this skill when:
- "Customers should see their orders / invoices / certificates on the portal"
- Adding a portal list + detail view with per-user access control
- Wiring a new entry into the portal sidebar menu
Steps
1. Decide the access rule first
Who may see which documents? The answer becomes has_website_permission and the list filter. Common shapes:
| Rule | Filter |
|---|---|
| Owner only | {"owner": frappe.session.user} or a user Link field |
| Customer's documents | via portal_users lookup (ERPNext pattern) |
| Role-gated, all docs | role check only — rare, double-check with the user |
2. Controller module — list context + permission
In the DocType's .py module (module level, NOT inside the class):
def get_list_context(context=None):
return {
"show_sidebar": True,
"title": _("My Documents"),
"get_list": get_filtered_list,
"row_template": "templates/includes/<app>/<doctype>_row.html",
"list_template": "templates/includes/list/list.html",
}
def get_filtered_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by=None):
return frappe.get_all(doctype,
filters={"user": frappe.session.user}, # ← step-1 rule
fields=["name", "status", "creation"],
limit_start=limit_start, limit_page_length=limit_page_length,
order_by=order_by or "modified desc")
def has_website_permission(doc, ptype, user=None, verbose=False):
return doc.user == (user or frappe.session.user) # ← step-1 rule
3. hooks.py — three entries
website_route_rules = [
{"from_route": "/<route>", "to_route": "<DocType Name>"},
{"from_route": "/<route>/<path:name>", "to_route": "<route>-detail",
"defaults": {"doctype": "<DocType Name>",
"parents": [{"label": "<Title>", "route": "<route>"}]}},
]
standard_portal_menu_items = [
{"title": "<Title>", "route": "/<route>", "reference_doctype": "<DocType Name>", "role": "<Role>"},
]
has_website_permission = {
"<DocType Name>": "<app>.<module>.doctype.<scrubbed>.<scrubbed>.has_website_permission",
}
Use an existing role (Customer, Supplier) — don't invent one unless genuinely new.
4. Templates — row + detail
- Row template: create a row HTML file named after the doctype inside the app's
templates/includesfolder (name link + status pill — copy the shape from the frappe-portal skill) - Detail page: create a page module + template named after the detail route inside
templates/pages; the Python module MUST callfrappe.has_website_permission(doc)and throwfrappe.PermissionError— the route rule alone protects nothing.
5. Apply & verify
bench --site <site> migrate # syncs portal menu items
bench --site <site> clear-website-cache
Then verify as a real portal user (not Administrator — it bypasses everything):
- Log in as a user with the role →
/<route>lists ONLY their documents - Open a document they own → detail renders
- Manually request a document they do NOT own → must get 403, not the document
- Check the sidebar shows the new menu item
If the list is empty for a legitimate user: they're missing either the role or the portal_users entry on the Customer/Supplier — the classic silent failure.
Rules
- Access control lives in
has_website_permission+ the list filter — never trust the route. - Step 5.3 (negative test) is mandatory. A portal page that leaks across users is a Critical finding in any audit.
- Deeper patterns, ERPNext
portal_usersplumbing, and gotchas: frappe-portal skill.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.