frontend-development
Skill68 starsChanged 8 days agoMITView on GitHub
Build modern Vue 3 frontend apps using Frappe UI with components, data fetching, and portal pages. Use when creating custom frontends, SPAs, or portal interfaces for Frappe applications.
13 other projects use a version of this.
Adopted by
Most recently taken up by
- its-ash/skillstheir own version, first seen Nov 1, 2026
- liushikuan63/Qoder-Optimizationtheir own version, first seen Nov 1, 2026
- KMaruthi2002/universal-llm-skillstheir own version, first seen Oct 25, 2026
- taichuy/1flowbasetheir own version, first seen Oct 23, 2026
- typv/nest-turbo-startertheir own version, first seen Oct 8, 2026
- ambient-code/platformtheir own version, first seen Oct 8, 2026
Other versions13 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 13
- All-Hands-AI/OpenHands★ 93ksame name
- OpenHands/OpenHands★ 93ksame name
- taichuy/1flowbase★ 292same name
- typv/nest-turbo-starter★ 224same name
- ambient-code/platform★ 131same name
- avibebuilder/claude-prime★ 119same name
- aws-solutions/konductor★ 11same name
- Ampli-Group/agentic-mobile-blueprint★ 4same name
- liushikuan63/Qoder-Optimization★ 2same name
- dhslegen/digital-delivery-team★ 1same name
3 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository13 other files it gives its agents.
- api-developmentSkill
- app-developmentSkill
- desk-customizationSkill
- doctype-developmentSkill
- enterprise-patternsSkill
- frappe-managerSkill
- printing-templatesSkill
- project-triageSkill
- reportsSkill
- routerSkill
- testingSkill
- ui-patternsSkill
- web-formsSkill
The file
We split this skill into the rules it gives an agent
9 rules. 0 are common, 2 are distinctive, and 7 are very specific to this project.
0
Common
Most of the 11 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 319
404 on API calls: Check method path; verify @frappe.whitelist() decorator
- Very specific: line 320
Component not found: Ensure import path is correct.
- Very specific: line 321
Styles broken: Verify TailwindCSS config includes frappe-ui component paths
- Distinctive: line 322
Auth issues: Check session cookie; ensure site URL matches in dev proxy config
- Distinctive: line 338
ALWAYS use Frappe UI for custom frontends: Never use vanilla JS, jQuery, or custom frameworks for app frontends — Frappe UI (Vue 3 + …
- Very specific: line 339
Use FrappeUI components: Prefer <Button>, <Input>, <FormControl> over custom HTML for consistency
- Very specific: line 341
Handle loading states: Always show loading indicators during API calls.
- Very specific: line 342
Validate API responses: Check for errors before accessing data.
- Very specific: line 344
Handle authentication: Check $session.user and redirect to login when needed
“Common” is measured: kept by at least 6 in 10 of the 11 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Frappe Frontend Development
Build modern frontend applications using Frappe UI (Vue 3 + TailwindCSS) and portal pages.
When to use
- Building a custom SPA frontend for a Frappe app
- Using Frappe UI components (Button, Dialog, ListView, etc.)
- Implementing data fetching with Resource, ListResource, DocumentResource
- Creating portal/public-facing pages
- Setting up Vue 3 frontend tooling inside a Frappe app
Inputs required
- App name and whether frontend already exists
- Frontend type (full SPA via Frappe UI, or portal pages)
- Authentication requirements (logged-in users, guest access)
- Key components and data resources needed
Procedure
0) Choose frontend approach
| Approach | When to Use | Stack |
| Frappe UI SPA | Custom app frontend | Vue 3, TailwindCSS, Vite |
| Portal pages | Simple public pages | Jinja + HTML, minimal JS |
| Desk extensions | Admin UI enhancements | Form/List scripts (see desk-customization) |
1) Scaffold Frappe UI frontend
# Inside your Frappe app directory
cd apps/my_app
npx degit frappe/frappe-ui-starter frontend
# Install dependencies
cd frontend
yarn
# Start dev server
yarn dev2) Configure main.js
import { createApp } from 'vue'
import {
FrappeUI,
setConfig,
frappeRequest,
resourcesPlugin,
pageMetaPlugin
} from 'frappe-ui'
import App from './App.vue'
import './index.css'
let app = createApp(App)
// Register FrappeUI plugin (components + directives)
app.use(FrappeUI)
// Enable Frappe response parsing
setConfig('resourceFetcher', frappeRequest)
// Optional: Options API resource support
app.use(resourcesPlugin)
// Optional: Reactive page titles
app.use(pageMetaPlugin)
app.mount('#app')3) Fetch data with Resources
Generic Resource — for custom API calls:
import { createResource } from 'frappe-ui'
let stats = createResource({
url: 'my_app.api.get_dashboard_stats',
params: { period: 'monthly' },
auto: true,
cache: 'dashboard-stats',
transform(data) {
return { ...data, formatted_total: format_currency(data.total) }
},
onSuccess(data) { console.log('Loaded:', data) },
onError(error) { console.error('Failed:', error) }
})
// Properties
stats.data // Response data
stats.loading // Boolean: request in progress
stats.error // Error object if failed
stats.fetched // Boolean: data fetched at least once
// Methods
stats.fetch() // Trigger request
stats.reload() // Re-fetch
stats.submit({ period: 'weekly' }) // Fetch with new params
stats.reset() // Reset stateList Resource — for DocType lists with pagination:
import { createListResource } from 'frappe-ui'
let todos = createListResource({
doctype: 'ToDo',
fields: ['name', 'description', 'status'],
filters: { status: 'Open' },
orderBy: 'creation desc',
pageLength: 20,
auto: true,
cache: 'open-todos'
})
// List-specific API
todos.data // Array of records
todos.hasNextPage // Boolean: more pages
todos.next() // Load next page
todos.reload() // Refresh list
// CRUD operations
todos.insert.submit({ description: 'New task' })
todos.setValue.submit({ name: 'TODO-001', status: 'Closed' })
todos.delete.submit('TODO-001')
todos.runDocMethod.submit({ method: 'send_email', name: 'TODO-001' })Document Resource — for single document operations:
import { createDocumentResource } from 'frappe-ui'
let todo = createDocumentResource({
doctype: 'ToDo',
name: 'TODO-001',
whitelistedMethods: {
sendEmail: 'send_email',
markComplete: 'mark_complete'
},
onSuccess(doc) { console.log('Loaded:', doc.name) }
})
// Document API
todo.doc // Full document object
todo.reload() // Refresh document
// Update fields
todo.setValue.submit({ status: 'Closed' })
// Debounced update (coalesces rapid changes)
todo.setValueDebounced.submit({ description: 'Updated' })
// Call whitelisted methods
todo.sendEmail.submit({ email: 'user@example.com' })
// Delete
todo.delete.submit()4) Use Frappe UI components
<template>
<div class="p-4">
<Button variant="solid" theme="blue" @click="showDialog = true">
Add Todo
</Button>
<ListView :columns="columns" :rows="todos.data">
<template #cell="{ column, row, value }">
<Badge v-if="column.key === 'status'" :theme="value === 'Open' ? 'orange' : 'green'">
{{ value }}
</Badge>
<span v-else>{{ value }}</span>
</template>
</ListView>
<Dialog v-model="showDialog" :options="{ title: 'New Todo' }">
<template #body-content>
<TextInput v-model="newDescription" placeholder="Description" />
</template>
<template #actions>
<Button variant="solid" @click="addTodo">Save</Button>
</template>
</Dialog>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Button, ListView, Badge, Dialog, TextInput, createListResource } from 'frappe-ui'
const showDialog = ref(false)
const newDescription = ref('')
const todos = createListResource({
doctype: 'ToDo',
fields: ['name', 'description', 'status'],
auto: true
})
const columns = [
{ label: 'Description', key: 'description' },
{ label: 'Status', key: 'status', width: 100 }
]
function addTodo() {
todos.insert.submit(
{ description: newDescription.value },
{ onSuccess() { showDialog.value = false; newDescription.value = '' } }
)
}
</script>Available component categories:
| Category | Components |
| Inputs | TextInput, Textarea, Select, Combobox, MultiSelect, Checkbox, Switch, DatePicker, TimePicker, Slider, Password, Rating |
| Display | Alert, Avatar, Badge, Breadcrumbs, Progress, Tooltip, ErrorMessage, LoadingText |
| Navigation | Button, Dropdown, Tabs, Sidebar, Popover |
| Layout | Dialog, ListView, Calendar, Tree |
| Rich Content | TextEditor (TipTap), Charts, FileUploader |
5) Add directives and utilities
<script setup>
import { onOutsideClickDirective, visibilityDirective, debounce } from 'frappe-ui'
const vOnOutsideClick = onOutsideClickDirective
const vVisibility = visibilityDirective
const debouncedSearch = debounce((query) => {
// Search logic
}, 500)
</script>
<template>
<div v-on-outside-click="closeDropdown">...</div>
<div v-visibility="onVisible">Lazy loaded content</div>
</template>6) Configure TailwindCSS
// tailwind.config.js
module.exports = {
presets: [
require('frappe-ui/src/utils/tailwind.config')
],
content: [
'./index.html',
'./src/**/*.{vue,js,ts}',
'./node_modules/frappe-ui/src/components/**/*.{vue,js,ts}'
]
}7) Build for production
# Build frontend assets
cd frontend && yarn build
# Assets are served at /frontend by Frappe8) Portal pages (alternative approach)
For simple public pages without a full SPA:
# In your app's website/ or www/ directory
# my_app/www/my_page.html
{% extends "templates/web.html" %}
{% block page_content %}
<h1>{{ title }}</h1>
<p>Welcome, {{ frappe.session.user }}</p>
{% endblock %}# my_app/www/my_page.py
def get_context(context):
context.title = "My Page"
context.data = frappe.get_all("ToDo", filters={"owner": frappe.session.user})Verification
yarn devstarts without errors- Components render correctly
- Data resources fetch and display data
- CRUD operations work (insert, update, delete)
- Authentication works (login redirect, session handling)
yarn buildcompletes successfully- Production assets serve correctly from Frappe
Failure modes / debugging
- CORS errors: Set
ignore_csrffor local dev; ensure proper CSRF token in production - 404 on API calls: Check method path; verify
@frappe.whitelist()decorator - Component not found: Ensure import path is correct; check
frappe-uiversion - Styles broken: Verify TailwindCSS config includes
frappe-uicomponent paths - Auth issues: Check session cookie; ensure site URL matches in dev proxy config
Escalation
- For Desk UI scripting →
desk-customization - For API endpoint implementation →
api-development - For app architecture →
app-development - For UI/UX patterns from official apps →
ui-patterns
References
- references/frappe-ui.md — Frappe UI framework reference
- references/portal-development.md — Portal pages overview
Guardrails
- ALWAYS use Frappe UI for custom frontends: Never use vanilla JS, jQuery, or custom frameworks for app frontends — Frappe UI (Vue 3 + TailwindCSS) is the standard. This ensures consistency with CRM, Helpdesk, and other official Frappe apps.
- Use FrappeUI components: Prefer
<Button>,<Input>,<FormControl>over custom HTML for consistency - Follow CRM/Helpdesk app shell patterns: For CRUD apps, follow
ui-patternsskill which documents sidebar navigation, list views, form layouts, and routing patterns from official Frappe apps - Handle loading states: Always show loading indicators during API calls; use
resource.loading - Validate API responses: Check for errors before accessing data; handle
excresponses - Configure proxy correctly: Dev server must proxy API calls to Frappe backend
- Handle authentication: Check
$session.userand redirect to login when needed
Common Mistakes
| Mistake | Why It Fails | Fix |
| Missing CORS/proxy setup | API calls fail in development | Configure Vite proxy to forward /api to Frappe site |
| Not handling auth state | App crashes for logged-out users | Check call('frappe.auth.get_logged_user') on mount |
| Wrong resource URLs | 404 errors on API calls | Use createResource with correct method paths |
| Hardcoded site URL | Breaks across environments | Use relative URLs or environment variables |
| Not including CSRF token | POST requests fail | Use frappe.csrf_token or configure session properly |
| Missing TailwindCSS config | Frappe UI styles broken | Include frappe-ui in Tailwind content paths |
| Using vanilla JS/jQuery | Inconsistent UX, maintenance burden | Always use Frappe UI for custom frontends |
| Custom app shell design | Inconsistent with ecosystem | Follow CRM/Helpdesk patterns for navigation, lists, forms |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

