Designing your own
Five decisions ERPNext made, each with a real cost, framed as a question for a new design rather than a verdict on this one.
This is a list of decisions to notice, not a scorecard. Each one is real, evidenced elsewhere on this site, and comes with a genuine cost — not a claim that ERPNext chose wrong. If you are designing an equivalent system, decide consciously rather than by default.
String primary keys, no surrogate integer IDs
Observation. The data model in brief: “Because primary keys are strings, a Link field stores the target’s name directly.”
Cost. A rename is not a single-row update — it is a cascade across every table that stores that string. Document rename & cascading reference update documents the machinery that makes that safe.
Question. Do your entities have a natural, human-meaningful key at all — and if not, is a string key still worth the rename cost?
No database-level foreign key constraints
Observation. The data model in brief: “referential integrity is enforced in the application layer.”
Cost. The database will not catch a broken reference or a drifted balance — nothing does, until something checks. Ledger integrity, repost & self-healing tooling exists because application-layer enforcement can, and does, drift from the ledgers it’s meant to keep consistent.
Question. If you skip DB constraints, what plays this tooling’s role for you — and how long can drift go unnoticed before it does?
67 polymorphic Dynamic Links
Observation. Polymorphic links: 67 Dynamic Link fields whose target doctype is chosen at runtime rather than fixed in the schema.
Cost. A polymorphic edge can’t be drawn in a static ERD, checked by a schema tool, or joined without first reading the companion type field’s value.
Question. Each one traded a static guarantee for flexibility — is that worth doing field-by-field, or only where the set of valid targets is small and closed?
214 child tables out of 448 entities
Observation. Modules: 214 of 448 entities are child tables — just under half the schema.
Cost. A child table “has no independent existence — deleting the parent deletes the rows” (Concepts). Every child row’s lifecycle is bound to one parent write; there is no child-table entity you can reference, audit, or migrate independently of its parent.
Question. Which of your own line-item or detail tables genuinely need that binding, and which are actually independent entities wearing a child table’s constraints?
Append-only ledgers, cancellation by reversal
Observation. The data model in brief: ledger rows are never updated in place; a cancellation writes reversing entries.
Cost. General ledger & multi-currency posting names it directly: an immutable, append-only ledger gives a tamper-evident trail “at the cost of unbounded growth and reports needing to net pairs.”
Question. Does your audit and compliance need actually require append-only, or would a mutable row with a separate audit log meet the same bar more cheaply?