frappe-frontend-development

Frappe Frontend Development

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "frappe-frontend-development" with this command: npx skills add lubusin/agent-skills/lubusin-agent-skills-frappe-frontend-development

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

  1. 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 frappe-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 dev

  1. 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')

  1. 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 state

List 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()

  1. Use Frappe UI components

<template> <div class="p-4"> <Button variant="solid" theme="blue" @click="showDialog = true"> Add Todo </Button>

    &#x3C;ListView :columns="columns" :rows="todos.data">
        &#x3C;template #cell="{ column, row, value }">
            &#x3C;Badge v-if="column.key === 'status'" :theme="value === 'Open' ? 'orange' : 'green'">
                {{ value }}
            &#x3C;/Badge>
            &#x3C;span v-else>{{ value }}&#x3C;/span>
        &#x3C;/template>
    &#x3C;/ListView>

    &#x3C;Dialog v-model="showDialog" :options="{ title: 'New Todo' }">
        &#x3C;template #body-content>
            &#x3C;TextInput v-model="newDescription" placeholder="Description" />
        &#x3C;/template>
        &#x3C;template #actions>
            &#x3C;Button variant="solid" @click="addTodo">Save&#x3C;/Button>
        &#x3C;/template>
    &#x3C;/Dialog>
&#x3C;/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

  1. 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>

  1. 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}' ] }

  1. Build for production

Build frontend assets

cd frontend && yarn build

Assets are served at /frontend by Frappe

  1. 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 dev starts without errors

  • Components render correctly

  • Data resources fetch and display data

  • CRUD operations work (insert, update, delete)

  • Authentication works (login redirect, session handling)

  • yarn build completes successfully

  • Production assets serve correctly from Frappe

Failure modes / debugging

  • CORS errors: Set ignore_csrf for 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-ui version

  • Styles broken: Verify TailwindCSS config includes frappe-ui component paths

  • Auth issues: Check session cookie; ensure site URL matches in dev proxy config

Escalation

  • For Desk UI scripting → frappe-desk-customization

  • For API endpoint implementation → frappe-api-development

  • For app architecture → frappe-app-development

  • For UI/UX patterns from official apps → frappe-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 frappe-ui-patterns skill 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 exc responses

  • Configure proxy correctly: Dev server must proxy API calls to Frappe backend

  • Handle authentication: Check $session.user and 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

frappe-doctype-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

frappe-app-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

frappe-api-development

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

frappe-printing-templates

No summary provided by upstream source.

Repository SourceNeeds Review