Skip to content

Vue 3 and Frappe: Building a Modern SPA on an ERP Backend

How to put a modern Vue 3 single-page application in front of a Frappe or ERPNext backend: authentication, the REST API, the frappe-ui resource layer, routing, and shipping it as a bundled custom app.

By Karani Geoffrey, Founder & CEO, Upeosoft
In short

You build a Vue 3 SPA on Frappe by treating Frappe as a headless backend: the REST API serves data, session cookies or tokens handle auth, and the frappe-ui library plus its resource layer wire Vue components to doctypes. You then bundle the app and serve it from a Frappe custom app, keeping one deployable unit.

Key takeaways
  • Frappe works well as a headless backend; its REST API exposes every doctype without extra code.
  • frappe-ui gives you Vue 3 components and a resource layer that maps cleanly to Frappe documents and methods.
  • Authentication rides on Frappe's session cookies, so a same-origin SPA inherits login for free.
  • Vue Router with history mode needs a catch-all website route so deep links resolve on refresh.
  • Whitelisted methods let you call custom Python endpoints, not just raw CRUD.
  • Bundle the SPA inside a Frappe custom app so backend and frontend deploy as one unit.

Frappe as a headless backend

The starting insight is that Frappe is already a capable API server. Every doctype is exposed at /api/resource, and any whitelisted Python function is exposed at /api/method, all behind Frappe's authentication and permission engine. That means you can treat Frappe as a headless backend and build any frontend you like against it.

This matters because the built-in desk UI, excellent as it is for internal administration, is unmistakably an ERP interface. When a client needs a branded portal, a streamlined data-capture tool, or a customer-facing app, the desk is the wrong skin. A Vue 3 single-page application lets you own the entire user experience while Frappe keeps doing what it does best: data modelling, permissions, workflows, and business logic.

The division of labour is clean. Vue owns presentation and interaction; Frappe owns data and rules. You get a modern frontend without giving up the mature backend.

The REST API you already have

Frappe's REST API needs no extra code to start using. To read a list of records you call /api/resource/DoctypeName with filters, fields, and pagination as query parameters. To fetch one record you append its name to the path. Creating, updating, and deleting map to the standard POST, PUT, and DELETE verbs on those same paths.

Beyond raw CRUD, /api/method lets you call any whitelisted function, which is how you reach custom business logic that does not correspond to a single doctype. Every one of these calls is checked against the same role and row-level permissions that govern the desk, so your SPA cannot accidentally become a way around the security model. A user who cannot see a record in the desk cannot fetch it through the API either.

This is the quiet advantage of building on Frappe: the API is not a thing you build and secure, it is a thing you consume and it is already secured.

frappe-ui and the resource layer

You can talk to the API with plain fetch, but the Frappe team maintains frappe-ui, a Vue 3 library that makes it far more pleasant. It is two things at once: a Tailwind-based component set, and a reactive data layer purpose-built for Frappe.

The data layer is the part that saves real time. Its resource primitives wrap common operations, a document resource for a single record, a list resource for filtered collections, and a call resource for whitelisted methods, and expose them as reactive objects with loading and error state built in. Instead of writing fetch calls, wiring loading flags, and handling errors by hand in every component, you declare a resource and bind to it.

The result is Vue components that read like they are working with local reactive data while the library handles the HTTP, the credentials, and the state underneath. For a team building on Frappe, adopting frappe-ui removes most of the boilerplate that a hand-rolled API client would accumulate.

  • Document resources bind a single doctype record to reactive component state.
  • List resources handle filtered, paginated collections with loading and error flags.
  • Call resources invoke whitelisted methods for custom logic.
  • Tailwind-based components give a consistent, modern look without a separate design system.

Authentication without token gymnastics

Authentication is where many API-plus-SPA projects overcomplicate things, but Frappe makes it easy when the app is served same-origin. After a user logs in through Frappe, the browser holds a session cookie, and any API request from your SPA that includes credentials is authenticated automatically. There is no token to store, refresh, or leak, because you are reusing the framework's own session.

This is a strong reason to serve the SPA from the Frappe site itself rather than a separate domain. Same origin means the cookie flows naturally and you sidestep an entire category of CORS and token-management work.

When you genuinely need cross-origin or non-browser access, such as a mobile client, Frappe supports API key and secret pairs sent in an Authorization header. That path is available when required, but for a web SPA that lives alongside its backend, session-cookie auth is simpler, safer, and less code.

Building and deploying as a custom app

The cleanest way to ship a Vue 3 SPA on Frappe is to bundle it inside a Frappe custom app so the backend and frontend deploy as a single unit. Your custom app holds both the Python, the doctypes, whitelisted methods, and hooks, and the frontend source, with a build step that compiles the Vue app and places the output where Frappe can serve it.

This keeps everything version-controlled together and deployed together. A single bench update pulls new backend and frontend code at once, so you never have a mismatched pair where the UI expects an endpoint the server does not yet have. The frappe-ui project ships a starter setup that wires this build and serve path, which is a sensible place to begin rather than assembling the tooling from nothing.

One deployable unit is the whole point. The SPA is not a separate service to host and coordinate; it is part of the Frappe app, sharing its origin, its auth, and its release cycle.

When this architecture is the right choice

A Vue 3 SPA on Frappe is the right call when you need a bespoke or customer-facing interface but want to keep a mature backend for data, permissions, and logic. Branded portals, focused operational tools, public-facing apps, and anything where the generic desk UI would undersell the product all benefit from this split.

It is not always necessary. For internal admin, back-office data entry, and configuration, the Frappe desk is faster to deliver and perfectly professional, and building a custom SPA there is effort spent re-creating what you already have. Reach for Vue when the interface itself is part of the value, not when you simply want the app to feel modern for its own sake.

At Upeosoft we combine both deliberately: the desk for internal operations, and Vue 3 SPAs for the experiences that clients and their customers actually touch. Using Frappe as the shared backend for both means one data model, one permission system, and one deployment, with a modern frontend exactly where it earns its keep.

Frequently asked questions

Why put a custom Vue frontend on Frappe instead of using the desk UI?

The Frappe desk is excellent for internal admin and data entry, but it looks and behaves like an ERP. When you need a branded, task-focused, or customer-facing experience, a custom Vue 3 SPA gives you full control of the interface while still using Frappe for data, auth, permissions, and business logic. You keep the backend strengths and shed the generic UI.

What is frappe-ui?

frappe-ui is a Vue 3 component library and data layer maintained by the Frappe team, built with Tailwind. Beyond UI components, it provides resource primitives that wrap the REST API, so fetching a document, listing records, or calling a whitelisted method becomes a reactive helper rather than hand-written fetch code. It is the fastest way to connect Vue to Frappe.

How does authentication work for a Vue SPA on Frappe?

If the SPA is served from the same origin as the Frappe site, it inherits Frappe's session cookie after login, so authenticated API calls just work with credentials included. For separate origins or mobile, you can use API key and secret pairs in an Authorization header. Same-origin session auth is simplest and avoids token management entirely.

Do deep links break with Vue Router on Frappe?

They will unless you configure it. With history mode, refreshing a deep link sends the browser to a server path Frappe does not recognise, returning a 404. The fix is a catch-all website route or redirect that serves your SPA's index for any sub-path, letting Vue Router take over on the client. Configure this before testing shareable URLs.

How do I call custom business logic, not just CRUD?

Decorate a Python function with frappe.whitelist() to expose it as a method endpoint, then call it from Vue through the frappe-ui resource layer or a direct request. This is how you trigger workflows, run calculations, or return shaped data that does not map to a single doctype, while still going through Frappe's authentication and permission checks.

Karani Geoffrey
Karani Geoffrey
Founder & CEO, Upeosoft

Karani Geoffrey is the Founder & CEO of Upeosoft, a software and automation company rooted in Kenya. He builds custom software, AI systems, and production-grade ERPNext for businesses across East Africa, and writes about the Kenyan realities - eTIMS, M-Pesa, SHIF, unreliable internet and power - that make or break real systems.

Next step

Want this working in your business?

Upeosoft builds and hardens the systems behind this article - for real Kenyan operations, with eTIMS, M-Pesa and offline realities handled.

Keep reading

For Builders

What Is Frappe? The Framework Behind ERPNext, Explained

Frappe is the full-stack, metadata-driven Python and JavaScript framework that powers ERPNext. Here is what it actually is, how its core pieces fit together, and when it is the right foundation to build on.

8 min readRead article →
For Builders

Why We Build on ERPNext/Frappe Instead of From Scratch

The engineering case for starting from ERPNext and Frappe rather than an empty repository: what you get for free, what it costs, and how to build custom software on top without painting yourself into a corner.

8 min readRead article →
For Builders

Building Offline-First Web Apps: Lessons From Kenya

How to build web apps that keep working when the network does not: service workers, IndexedDB, a durable sync queue, and conflict resolution, drawn from shipping software for real Kenyan connectivity.

8 min readRead article →