Secure by default
Every endpoint requires authentication against the central site. The authenticated user is injected into your handler, so there is no unauthenticated code path to forget about.
Hybrid Cloud
Domain-driven Frappe services
The Frappe Microservice Framework lets you decompose the monolith gradually. Each service gets its own database and bounded context, authenticates against the central site, and keeps Frappe-style controllers and document hooks — without a fork of Frappe core.
# Prerequisite: a running Central Site (the Frappe site that# authenticates requests) with the saas_platform app installed —# that is what supplies the tenant_id this framework filters on. pip install frappe-microservice # Or pin it straight from Gitpip install git+https://github.com/vyogotech/erpnext-microservices-lib.git# The framework is configured entirely through the environment,# so the same image runs in every deployment. export FRAPPE_SITE="dev.localhost"export FRAPPE_SITES_PATH="/home/frappe/frappe-bench/sites"export CENTRAL_SITE_URL="http://central-site:8000" # auth providerexport DB_HOST="orders-db" # this service's own DBexport LOG_LEVEL="INFO"export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4317"from frappe_microservice import create_microservice app = create_microservice("orders-service") # Authenticated user is injected for you@app.secure_route("/hello", methods=["GET"])def hello(user): return {"message": f"Hello {user}!"} app.run()from frappe_microservice import create_microservice app = create_microservice("orders-service") # Generates the full REST surface for a DocTypeapp.register_resource("Sales Order") # GET /api/resource/sales-order# POST /api/resource/sales-order# GET /api/resource/sales-order/{name}# PUT /api/resource/sales-order/{name}# DELETE /api/resource/sales-order/{name} app.run()Every endpoint requires authentication against the central site. The authenticated user is injected into your handler, so there is no unauthenticated code path to forget about.
Each service owns its own database — the bounded context principle — so a service can be deployed, scaled and migrated without coordinating with the monolith.
Automatic tenant_id filtering on reads and writes makes cross-tenant leakage a framework-level guarantee rather than a code review checklist item.
register_resource('Sales Order') generates the full REST surface — list, create, read, update, delete — against a real Frappe DocType.
validate, before_insert, after_insert and friends work the way you already expect, without patching Frappe core or the monolith's database.
DocTypes, the ORM and the API conventions carry over, so the code your team already knows how to write keeps working in a distributed topology.
Auth context injection, automatic tenant filtering and Frappe-style controllers in a distributed architecture — so a service extracted from the monolith still looks like Frappe code to the team maintaining it. Configuration is environment-only, and a Containerfile ships with the library for production builds.
A running Central Site — the Frappe site that authenticates requests — with the SaaS Platform app installed on it. SaaS Platform is what adds the tenant_id field the framework filters on, so without it there is no tenant context to inject. Each service then gets its own separate database.
Entirely through environment variables: FRAPPE_SITE, FRAPPE_SITES_PATH, CENTRAL_SITE_URL, DB_HOST, LOG_LEVEL and OTEL_EXPORTER_OTLP_ENDPOINT. That keeps one image runnable across every environment, and a Containerfile ships with the library for production builds.
No. It is designed for gradual decomposition: extract one bounded context at a time into its own service and database, while the monolith keeps serving everything else. The central site stays the identity provider throughout.
The main and version-15 branches target Frappe/ERPNext v15 on Python 3.11 and are stable. A version-16 branch targets v16 on Python 3.14 and is in beta, with a develop branch tracking Frappe develop experimentally.
A tenant-aware database wrapper applies tenant_id filtering to queries automatically, using the tenant context resolved during authentication. Handlers cannot accidentally query outside their tenant because the filter is applied below the handler.
Multi-tenancy on a single site
A Frappe app that lets one site serve many tenants, isolated by row instead of by database.
ExploreKubernetes-native Frappe
A production Kubernetes operator that deploys, scales and heals Frappe and ERPNext on any cluster.
ExploreAI bridge for ERPNext
A Model Context Protocol server that lets Claude, Cursor and any LLM query ERPNext safely.
ExploreWe build and maintain these tools, and we help teams adopt them. Tell us what you are trying to ship and we will tell you honestly whether this is the right piece.
Talk to the team