agentsclimarketplace

Frappe impl website

Skill Impertio-Studio/Frappe_Claude_Skill_Package/skills/source/impl/frappe-impl-website

60 deterministic Claude AI skills for Frappe Framework & ERPNext v14-v16 development and operations

Install
npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-impl-website

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

Use when building portal pages, Web Forms, website routes, or configuring themes and SEO in Frappe. Prevents 404 errors from wrong route resolution, broken Web Form submissions, and missing meta tags for SEO. Covers Web Page, Web Form, Portal Settings, Website Settings, website routes, Jinja templates, Blog, Web Template, has_web_view, meta tags, sitemap. Keywords: website, portal, Web Form, Web Page, route, theme, SEO, meta tags, has_web_view, Blog, Web Template, sitemap, customer portal, self-service, public form, web page, website not showing, 404 on portal..

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

10.6 KB, as published. Nobody here has run it

Frappe Website & Portals — Implementation Workflows

Step-by-step workflows for building websites, portals, and public-facing pages. For hooks syntax see frappe-impl-hooks. For Jinja templating see frappe-impl-jinja.

Version: v14/v15/v16 | Note: v15+ uses Bootstrap 5; v14 uses Bootstrap 4.

Quick Decision: Which Page Type?

WHAT do you need?
├── Static content page (About, Terms)     → Web Page DocType or www/ HTML
├── Data entry by external users           → Web Form
├── List of records visible on website     → has_web_view on DocType
├── Blog / news articles                   → Blog Post + Blog Category
├── Custom app with sidebar/toolbar        → Custom Portal Page (www/)
└── Dynamic route with parameters          → website_route_rules in hooks.py

See references/decision-tree.md for the complete decision tree.

Workflow 1: Create a Portal Page (www/)

Portal pages live in your app's www/ directory. The file name becomes the URL route.

  1. Create myapp/www/custom_page.html:
{% extends "templates/web.html" %}
{% block page_content %}
<h1>{{ title }}</h1>
<div>{{ content }}</div>
{% endblock %}
  1. Create matching controller myapp/www/custom_page.py:
import frappe

def get_context(context):
    context.title = "My Custom Page"
    context.content = "Hello World"
    context.no_cache = 1  # ALWAYS set for dynamic content
  1. Result: page available at /custom_page

File types auto-loaded: .html (template), .py (controller), .css (styles), .js (scripts).

Subdirectory pattern — for nested routes:

myapp/www/
├── services/
│   ├── index.html        → /services
│   ├── index.py
│   ├── consulting.html   → /services/consulting
│   └── consulting.py

Context Variables Reference

KeyTypeEffect
titlestrPage title and browser tab
no_cacheboolDisable page caching
no_headerboolHide the page header
no_breadcrumbsboolRemove breadcrumbs
add_breadcrumbsboolAuto-generate from folder structure
show_sidebarboolDisplay web sidebar
sitemapint0 = exclude from sitemap, 1 = include
metatagsdictSEO meta tags (see Workflow 7)

Rule: ALWAYS set no_cache = 1 for pages with user-specific or frequently changing content.

Workflow 2: Create a Web Form

Web Forms let external users submit data that creates Frappe documents.

  1. Navigate to Web Form list → New Web Form
  2. Set Title, select target DocType, set Route (URL slug)
  3. Add fields — ALWAYS match fieldname to the target DocType field names
  4. Configure access:
    • Login Required: uncheck for guest submissions
    • Allow Edit: let users edit their submissions
    • Allow Multiple: let users submit more than once
  5. Save and publish

Guest Submissions

ALLOWING guest submissions?
├── YES → Uncheck "Login Required"
│        → Set "Guest Title" for the submission form
│        → ALWAYS add rate limiting in site_config:
│           "rate_limit": {"web_form": "5/hour"}
│        → ALWAYS validate server-side (guests can bypass JS)
└── NO  → Keep "Login Required" checked (default)

Web Form Custom Script (Client)

frappe.web_form.on("after_load", function() {
    // Runs after form loads in browser
});

frappe.web_form.on("before_submit", function() {
    // Validate before submission — return false to cancel
    let val = frappe.web_form.get_value("email");
    if (!val) {
        frappe.throw("Email is required");
        return false;
    }
});

frappe.web_form.on("after_submit", function() {
    // Redirect or show message after success
    window.location.href = "/thank-you";
});

Web Form Custom Script (Server: Python)

In the Web Form document, add a Python script:

def get_context(context):
    # Add custom context variables for the template
    context.categories = frappe.get_all("Category", fields=["name", "title"])

Rule: NEVER trust client-side validation alone for Web Forms. ALWAYS validate in the target DocType's controller or server script.

Workflow 3: Enable has_web_view on a DocType

This makes individual documents accessible as web pages (e.g., /articles/my-article).

  1. Open DocType → check Has Web View and Allow Guest to View
  2. Set the Route field prefix (e.g., articles)
  3. ALWAYS add these fields to the DocType:
    • route (Data, hidden) — auto-generated URL slug
    • published (Check) — controls visibility
  4. Create templates in the DocType directory:
    • {doctype_name}.html — single record template
    • {doctype_name}_row.html — list item template
  5. In hooks.py, register as website generator:
website_generators = ["Article"]
  1. In the controller, implement get_context:
class Article(WebsiteGenerator):
    website = frappe._dict(
        template="templates/generators/article.html",
        condition_field="published",
        page_title_field="title",
    )

    def get_context(self, context):
        context.related = frappe.get_all(
            "Article",
            filters={"published": 1, "name": ("!=", self.name)},
            fields=["title", "route"],
            limit=5,
        )

Rule: ALWAYS include a published check field. NEVER expose unpublished documents to guests.

Workflow 4: Website Route Rules (hooks.py)

Route rules map URL patterns to controllers or pages.

# hooks.py
website_route_rules = [
    # Map parameterized URL to a page
    {"from_route": "/projects/<name>", "to_route": "projects/project"},
    # Map URL prefix to DocType
    {"from_route": "/kb/<path:name>", "to_route": "knowledge-base"},
]

# Redirects (301/304)
website_redirects = [
    {"source": "/old-page", "target": "/new-page"},
    {"source": r"/docs(/.*)?", "target": r"https://docs.example.com\1"},
]

# Homepage for logged-in users (role-based)
role_home_page = {
    "Customer": "orders",
    "Supplier": "rfqs",
}

# Dynamic homepage
get_website_user_home_page = "myapp.utils.get_home_page"

Priority order for homepage: get_website_user_home_page > role_home_page > Portal Settings > Website Settings.

Workflow 5: Blog Setup

  1. Create Blog Category documents (e.g., "News", "Updates")
  2. Create Blog Post documents:
    • Select category, write content (Markdown or Rich Text)
    • Set Published and Published On date
    • Blog route auto-generates as /blog/{slug}
  3. Configure in Website Settings:
    • Set blog title
    • Enable/disable comments

Rule: ALWAYS set Published On date — posts without a date NEVER appear in RSS feeds.

Workflow 6: Website Theme & Custom CSS

Via Website Theme DocType

  1. Navigate to Website Theme → New
  2. Configure: fonts, colors, navbar style, button radius
  3. Add custom CSS in the Custom CSS field
  4. Set as active theme in Website Settings

Via hooks.py

# Inject CSS/JS on all web pages
website_context = {
    "favicon": "/assets/myapp/images/favicon.png",
}

update_website_context = "myapp.overrides.website_context"

# Override base template
base_template = "myapp/templates/custom_base.html"

Workflow 7: SEO: Meta Tags, Open Graph & Sitemap

In portal pages (frontmatter or context)

def get_context(context):
    context.metatags = {
        "title": "My Page Title",
        "description": "Page description for search engines",
        "image": "/assets/myapp/images/og-image.png",
        "og:type": "website",
        "twitter:card": "summary_large_image",
    }

In Web Page DocType

Set meta fields directly: Meta Title, Meta Description, Meta Image.

Sitemap

  • Frappe auto-generates /sitemap.xml from published Web Pages and has_web_view documents
  • Exclude pages: set sitemap = 0 in context or frontmatter
  • Custom robots.txt: set robots_txt path in site_config.json

Rule: ALWAYS set meta description on public pages. NEVER leave it empty — search engines penalize pages without descriptions.

Workflow 8: Guest Access & Security

# site_config.json — rate limiting
{
    "rate_limit": {
        "web_form": "5/hour",
        "api": "100/hour"
    },
    "allowed_referrers": ["https://mysite.com"],
    "allow_cors": "https://mysite.com"
}

Security rules:

  • ALWAYS enable CSRF protection (default). NEVER set ignore_csrf in production
  • ALWAYS rate-limit guest-accessible endpoints
  • ALWAYS sanitize user input in Web Forms (Frappe does this by default for standard fields)
  • NEVER expose internal DocType names in guest-facing URLs without access control

Anti-Patterns

Anti-PatternCorrect Approach
Hard-coding HTML in get_contextUse Jinja templates with context variables
Skipping no_cache on dynamic pagesALWAYS set no_cache = 1 for user-specific content
Guest Web Form without rate limitingALWAYS configure rate limits for guest forms
Missing published field on has_web_viewALWAYS add published check to prevent data leaks
Using website_route_rules for simple redirectsUse website_redirects instead
Putting business logic in www/ controllersKeep in DocType controllers; www/ is for presentation

See references/anti-patterns.md for expanded anti-patterns with examples.

See Also

  • frappe-impl-hooks — Website hooks in detail
  • frappe-impl-jinja — Jinja templating patterns
  • frappe-impl-controllers — DocType controllers (WebsiteGenerator)
  • frappe-syntax-clientscripts — Client-side API for Web Forms
  • references/generators.md — Portal generators, blog system, custom routing patterns
  • references/workflows.md — Extended workflow walkthroughs
  • references/examples.md — Complete code examples
  • references/decision-tree.md — Full decision tree for page types

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.