$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-ops-website-deployDeploy HTML/CSS websites to ERPNext/Frappe (v15/v16) as Web Pages via the REST API. Use this skill whenever a user wants to host a website on ERPNext, deploy HTML mockups to Frappe, create Web Pages programmatically, configure Website Settings, or integrate Frappe's Discussion sy
| 1 | # Deploy Websites on ERPNext/Frappe |
| 2 | |
| 3 | > Patterns for deploying static HTML/CSS websites to ERPNext v15/v16 using Web Pages, Page Builder, and the REST API. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## Critical: ERPNext v16 Does NOT Render main_section |
| 8 | |
| 9 | In Frappe v16, the `main_section` field on a Web Page is stored but **not rendered** in the browser — even with `content_type: "HTML"` or `dynamic_template: 1`. The Page Builder (`page_blocks`) is the primary rendering mechanism. |
| 10 | |
| 11 | **You must use `page_blocks` with a custom Web Template to render HTML content.** |
| 12 | |
| 13 | --- |
| 14 | |
| 15 | ## Decision Tree |
| 16 | |
| 17 | ``` |
| 18 | What do you need? |
| 19 | ├── Deploy HTML pages to ERPNext |
| 20 | │ ├── Step 1: Create "Raw HTML Section" Web Template (one-time) |
| 21 | │ ├── Step 2: Create Web Pages with page_blocks |
| 22 | │ └── Step 3: Configure Website Settings |
| 23 | │ |
| 24 | ├── Add a forum / discussion system |
| 25 | │ └── Use Frappe's built-in Discussion Topic/Reply + Discussions Web Template |
| 26 | │ |
| 27 | ├── Manage CSS |
| 28 | │ ├── Per-page CSS → Web Page `css` field |
| 29 | │ ├── Global CSS → Website Settings `head_html` |
| 30 | │ └── WARNING: Never stack !important overrides (see CSS Management) |
| 31 | │ |
| 32 | └── Configure navigation |
| 33 | └── Website Settings: top_bar_items, footer_items, brand_html, home_page |
| 34 | ``` |
| 35 | |
| 36 | --- |
| 37 | |
| 38 | ## Step 1: Create the Raw HTML Section Web Template |
| 39 | |
| 40 | This is a one-time setup. The template accepts raw HTML and renders it as-is. |
| 41 | |
| 42 | ``` |
| 43 | POST /api/resource/Web%20Template |
| 44 | ``` |
| 45 | |
| 46 | ```json |
| 47 | { |
| 48 | "name": "Raw HTML Section", |
| 49 | "type": "Section", |
| 50 | "template": "{{ values.html_content }}", |
| 51 | "fields": [ |
| 52 | { |
| 53 | "fieldname": "html_content", |
| 54 | "fieldtype": "Text", |
| 55 | "label": "HTML Content" |
| 56 | } |
| 57 | ] |
| 58 | } |
| 59 | ``` |
| 60 | |
| 61 | **Important constraints:** |
| 62 | - The `fieldtype` must be `"Text"` — Frappe rejects `"Code"` for Web Template fields |
| 63 | - Allowed fieldtypes: `Attach Image`, `Check`, `Data`, `Int`, `Link`, `Select`, `Small Text`, `Text`, `Markdown Editor`, `Section Break`, `Column Break`, `Table Break` |
| 64 | - The template uses `{{ values.html_content }}` (with `values.` prefix) to access field data |
| 65 | |
| 66 | --- |
| 67 | |
| 68 | ## Step 2: Create Web Pages with Page Builder |
| 69 | |
| 70 | Each page needs `content_type: "Page Builder"` and its HTML in `page_blocks`. |
| 71 | |
| 72 | ``` |
| 73 | POST /api/resource/Web%20Page |
| 74 | ``` |
| 75 | |
| 76 | ```json |
| 77 | { |
| 78 | "title": "Page Title", |
| 79 | "route": "my-page", |
| 80 | "published": 1, |
| 81 | "show_title": 0, |
| 82 | "full_width": 1, |
| 83 | "content_type": "Page Builder", |
| 84 | "css": "<per-page CSS here>", |
| 85 | "page_blocks": [ |
| 86 | { |
| 87 | "web_template": "Raw HTML Section", |
| 88 | "web_template_values": "{\"html_content\": \"<div>Your HTML here</div>\"}" |
| 89 | } |
| 90 | ] |
| 91 | } |
| 92 | ``` |
| 93 | |
| 94 | **Critical: `web_template_values` is a JSON string, not an object.** Serialize it with `json.dumps()` before sending. |
| 95 | |
| 96 | ### Updating an existing page |
| 97 | |
| 98 | ``` |
| 99 | PUT /api/resource/Web%20Page/{url_encoded_name} |
| 100 | ``` |
| 101 | |
| 102 | To find a page by route: |
| 103 | ``` |
| 104 | GET /api/resource/Web%20Page?filters=[["route","=","my-page"]]&fields=["name"] |
| 105 | ``` |
| 106 | |
| 107 | --- |
| 108 | |
| 109 | ## Step 3: Configure Website Settings |
| 110 | |
| 111 | ``` |
| 112 | PUT /api/resource/Website%20Settings/Website%20Settings |
| 113 | ``` |
| 114 | |
| 115 | ```json |
| 116 | { |
| 117 | "home_page": "home", |
| 118 | "brand_html": "<span style=\"...\">NL</span> My Brand", |
| 119 | "head_html": "<link href=\"fonts.css\" rel=\"stylesheet\">\n<style>/* global CSS */</style>", |
| 120 | "top_bar_items": [ |
| 121 | {"label": "About", "url": "/about", "right": 0} |
| 122 | ], |
| 123 | "footer_items": [ |
| 124 | {"label": "About", "url": "/about"} |
| 125 | ] |
| 126 | } |
| 127 | ``` |
| 128 | |
| 129 | ### head_html: Frappe Wrapper Fixes |
| 130 | |
| 131 | Frappe wraps page content in several divs that add unwanted whitespace. Add these fixes to `head_html`: |
| 132 | |
| 133 | ```html |
| 134 | <style> |
| 135 | .page-header-wrapper { display: none !important; } |
| 136 | .page-breadcrumbs { display: none !important; } |
| 137 | .page-content-wrapper { padding: 0 !important; margin: 0 !important; } |
| 138 | .page_content { padding: 0 !important; margin: 0 !important; } |
| 139 | .webpage-content { padding: 0 !important; margin: 0 !important; } |
| 140 | .web-page-content { padding: 0 !important; margin: 0 !important; max-width: none !important; } |
| 141 | .section.section-padding-top { padding-top: 0 !important; } |
| 142 | .section.section-padding-bottom { padding-bottom: 0 !important; } |
| 143 | .web-template-section { padding: 0 !important; margin: 0 !important; } |
| 144 | main { padding: 0 !important; margin: 0 !important; } |
| 145 | </style> |
| 146 | ``` |
| 147 | |
| 148 | These are the **only** `!important` overrides you should use — they target Frappe's own wrapper el |