Start with one deployment
A single Frappe/ERPNext install. Cheap to run, easy to upgrade, and completely unable to tell one paying customer from another. Everything after this is about fixing that without forking the framework.
Mohamed Khaled Shoaib · Giza, Egypt
Three years building multi-tenant SaaS on the Frappe Framework and ERPNext — and the tax-authority integrations that have to prove every invoice they ever sent. Python, enforcement on the server, ledgers that reconcile.
The shape of the work
Most of what I build is a variation on this diagram. Scroll it through.
A single Frappe/ERPNext install. Cheap to run, easy to upgrade, and completely unable to tell one paying customer from another. Everything after this is about fixing that without forking the framework.
Per-tenant configuration and dynamic user access on top of the same codebase. The hard part is not routing requests — it is making sure tenant B can never read, list, or infer a row belonging to tenant A.
A plans → modules → features entitlement model, checked on the server
for every request: module permissions, feature gating, licensed-user limits, DocType
customisations. Hiding a button in the UI is not access control.
Invoices get serialised, digitally signed, and submitted to the Egyptian ETA and Saudi ZATCA portals — with submission-status reconciliation, because the failure mode here is a fine, not a 500.
Selected work
Most of this lives in private repositories — client and product code I can describe but not hand over. Commit counts are mine, not the team's.
A headless commerce backend for ERPNext, plus the React storefront that consumes it over plain HTTP. Twelve doctypes model the catalogue tree, carts, submittable orders with a status log, coupons with per-customer limits, moderated reviews with verified-purchase detection, wishlists and addresses.
The decision I am proudest of: store credit does not live in the customer balance. ERPNext's customer balance is the accounts-receivable ledger — putting top-ups there pollutes ageing reports and credit limits, and forces every storefront request to aggregate GL entries just to show a number. The wallet instead caches a balance beside an immutable transaction log with reason codes and before/after snapshots.
Saudi tax-authority e-invoicing built into ERPNext and shipped as infrastructure, not just an app — its own Docker image, Kubernetes manifests, a migration script and translation handling. Documents are serialised, signed, submitted, and then reconciled against what the portal says it actually received.
Why reconciliation is the feature: a submission that silently fails is indistinguishable from one that succeeded until an auditor asks. Every document carries its submission state, so the gap between "we sent it" and "they have it" is always visible.
A provider-agnostic AI assistant for ERPNext where the model is never allowed to act. It can only emit a structured tool call; the server validates and executes it as the logged-in user. No code, SQL, shell or HTTP is ever exposed as a tool.
The AI proposes; the server disposes. Every call walks one audited path — kill switch
read uncached, tool registry, schema validation, an agent allow-list, then the user's
native Frappe permissions, never ignore_permissions. The allow-list can
only narrow what a user could already do. Submit, cancel and delete sit behind a confirmation
gate; delete is off by default; every call lands in an append-only log with secrets masked.
Two separate bodies of work on the same problem — making one ERPNext deployment sellable to many customers. A SaaS control layer with a plans → modules → features entitlement engine and Stripe integrated end to end: provisioning, webhooks, daily synchronisation, upgrade/downgrade detection, renewals and grace periods, plus a self-service trial that provisions itself, warns, then locks the system on expiry with a guided subscribe path.
Enforcement moved server-side: module permissions, feature gating and licensed-user limits are checked in the backend rather than hidden in the UI — a real security gap, not a cosmetic one. The desk experience around it was rebuilt across roughly 7,500 lines of JavaScript and CSS, with unit tests and GitHub Actions CI. On the platform side I am the main contributor: over a hundred commits across tenant provisioning, the control plane and the billing surface.
A Vue 3 + frappe-ui single-page app served alongside the Desk, built the same way Frappe CRM and HRMS are: a standalone Vite frontend talking to Frappe over REST and sharing the login session. Alongside it, a TypeScript desk theme for my employer — around sixty commits of permission-aware navigation, responsive layout and component work.
Upgrade-safe by construction: it does not fork frappe or erpnext and does not modify the Desk, so a framework upgrade cannot silently break it — the failure mode that makes most ERPNext theming a liability.
A long run of ERPNext apps, each solving one operational problem properly. Contract management — around seventy commits on contract lifecycle, terms and approval routing. Prepaid cards and wallets — gift cards, recharges, redemptions, transfers, and the accounting entries behind them. A supplier payment voucher that pays many suppliers from one bank account, posts the GL entries and settles against open purchase invoices. Telephony brought into the CRM as call events, click-to-call and contact sync. Plus payment-gateway and national payment-scheme integrations, e-commerce platform connectors, HR and attendance services, fleet, real estate and delivery modules, and the Docker and server tooling the whole estate runs on.
How I work
Permissions, entitlements and limits belong in the backend. If the only thing stopping a user is a hidden button, it is not stopping them.
Compliance work gets built assuming someone will one day ask for proof of every submission. Reconciliation is a feature, not an afterthought.
Caching a slow report hides the problem and serves stale answers. The dashboards I build are fast because the queries are.
RTL layouts and non-technical users are requirements in this region, not polish applied at the end of a project.
Experience
Main contributor to the company's multi-tenant SaaS platform and its core ERPNext customisation layer. Contract management, ETA and ZATCA e-invoicing, a TypeScript desk theme, HR and attendance services, fleet, real estate and delivery modules, payment and e-commerce integrations, and a custom socket integration in the Supervisor module for backend-driven real-time process control.
The SaaS Control Layer: Stripe subscription lifecycle, server-side entitlement enforcement, and role-profile-driven executive dashboards across six business domains.
A React SPA over a Frappe API for a charitable foundation — token auth, role-scoped data, domain workflows with approval routing, and an Arabic/RTL interface built for non-technical staff.
Module customisation, custom themes for web applications, and automation scripts that removed manual steps from operational workflows.
ERPNext extensions for multiple clients in Saudi Arabia, third-party API integration, and query optimisation on PostgreSQL and MariaDB.
Stack
Next
I am open to remote, hybrid and contract work — especially Frappe/ERPNext, multi-tenant SaaS, billing systems, or anything that has to answer to a regulator.