You’ve been brought in to implement an ERP system. The core modules are solid. But the business has specific needs, and you need to extend things. Add fields. Override logic. Build something on top of what’s already there.
So you start digging into the docs and you find the answer: XML overrides. Or worse, you find a “recommended” pattern that’s just monkey-patching dressed up in nicer clothes. You do it because that’s how it works. And it does work. Until the next upgrade, when half your customizations quietly break, and nobody notices until the warehouse can’t ship orders on a Monday morning.
Why Traditional ERP Customization Is a Trap
Most ERP platforms weren’t designed with extensibility in mind from the start. They were designed to be complete products. Customization was bolted on later, and it shows.
The patterns that emerge from this are predictable:
- XML overrides that patch views and logic by referencing internal IDs that can change between versions
- Monkey-patching at the Python or JavaScript level, where you overwrite methods on classes you don’t own
- Database-level hacks that add columns outside the ORM, which breaks the schema management layer entirely
- Fork and maintain strategies, where consultants fork a module and maintain a parallel version forever
Every one of these approaches works short-term. Every one of them becomes a liability the moment anything upstream changes. If you’ve done ERP implementations for more than a couple of years, you’ve inherited someone else’s “temporary” customization that’s now load-bearing.
The real cost isn’t the initial implementation. It’s the maintenance burden that accumulates invisibly over time.
What Proper Model Inheritance Actually Looks Like
The phrase “model inheritance” gets thrown around a lot, but what it actually means in practice varies wildly between systems.
In the worst implementations, inheritance is just a convention. You copy a class, rename it, and hope nothing breaks when the parent changes. There’s no formal contract between parent and child. No guarantee that overriding a method won’t silently swallow behavior you needed.
Real inheritance means the system knows about the relationship. It means:
- Child models automatically receive field changes from parent models
- Overriding a method on a child doesn’t require touching the parent at all
- Multiple modules can each extend the same base model independently, without conflicting with each other
- The ORM understands the hierarchy and generates queries accordingly
This is how Fullfinity approaches it. When you extend a model, you’re working with the system, not around it. You don’t patch. You inherit. You override specific methods or add new fields through proper extension points. The base module stays untouched.
For ERP consultants, this distinction matters enormously. Your customizations survive upgrades because they’re not tangled into the core. For developers, it means you can reason about what a model does without reading every file that’s ever touched it.
The Hidden Problem With XML-Based View Overrides
A lot of ERP systems use XML to define forms, lists, and views. It seems reasonable at first. XML is declarative. You can describe a view without writing code. The problem is that XML overrides are brittle in a specific and painful way.
When you override a view using XML, you’re typically referencing specific elements by their ID or position in a tree. That reference is a hard dependency on the internal structure of the base view. If the base view changes its structure in an update, your override either silently fails, applies to the wrong element, or throws an error.
And because these references are scattered across XML files in different modules, tracing what’s overriding what becomes genuinely difficult. You end up reading XML to debug Python behavior, which is a bad sign.
The better model is separating configuration from override. If you need to modify a form, you should be able to define what you want declaratively without coupling yourself to the internal structure of someone else’s view definition.
Fullfinity uses YAML-based form and view rendering with a component model that’s designed to be composed rather than patched. You can add fields, rearrange layouts, or substitute widgets without writing XML overrides that depend on internal node IDs. It’s a small difference on paper. In practice, it means your front-end customizations don’t break every time the base module ships an update.
Database Migrations and Why “Zero Migration Files” Matters for Customizers
Here’s a scenario that every ERP consultant has lived through. You add a custom field to a model. You write the migration file. It works in development. You deploy to staging. It works there too. Then you deploy to production, and the migration conflicts with something else that ran out of order, or depends on a state that doesn’t exist in production, or simply takes too long and times out.
Migration files are fragile. They represent a snapshot of intent at a specific point in time, and they assume that the world at migration time looks exactly like the world at authoring time. That assumption breaks constantly in real deployments.
The deeper issue is that migrations are a symptom of a mismatch between the ORM’s understanding of the schema and the database’s actual state. If you could keep those two things in sync automatically, you wouldn’t need migration files at all.
Fullfinity’s ORM manages schema changes automatically. When you add a field to a model, the schema updates to match. No migration file. No ordering problem. No deployment ceremony where you hold your breath and hope the ALTER TABLE finishes before the load balancer times out.
For consultants and developers building on top of the platform, this is significant. You can add fields as part of your extension module, and they just work. You don’t have to coordinate your migration sequence with the base system’s migration sequence. You don’t have to worry about what happens if someone installs your module on a database that’s one version behind.
It removes an entire category of deployment failures.
The Access-Aware ORM: Why This Matters More Than You Think
Most ERP systems have access control. Users have roles. Roles have permissions. Certain records are visible to certain people. This is standard.
What’s not standard is how the ORM handles this at query time.
In most systems, access control is enforced at the application layer. The ORM fetches records, and then the application filters them based on permissions. This is fine until you have a large dataset, at which point you’re fetching thousands of records from the database and then discarding most of them in memory. It works. It just doesn’t scale well.
An access-aware ORM enforces permissions at the query level. The SQL that hits the database already incorporates the user’s access scope. You’re not fetching records you don’t need. The database does the filtering, which is what databases are actually good at.
For ERP specifically, this matters because ERP data is often large and permission-scoped in complex ways. A salesperson should see their own orders. A regional manager should see their team’s orders. An executive should see everything. These aren’t just filters you add manually every time you write a query. They’re constraints that should be applied automatically, consistently, every time.
When you’re building custom modules on top of Fullfinity, this system works for your models too. You define access rules once. The ORM applies them everywhere. You don’t have to remember to add permission checks to every custom report, every custom API endpoint, every custom background job.
That consistency is what separates a secure customization from one that leaks data in edge cases nobody thought to test.
Modular Architecture in Practice: Installing What You Actually Need
ERP systems have a reputation for being bloated. You need inventory management, so you install the ERP, and now you also have a full HR module, a project management suite, and a procurement workflow that nobody asked for. The performance suffers. The UI is cluttered. The developers have to learn systems they’ll never use.
Modular architecture is supposed to solve this, but “modular” often just means the features are organized into separate menus, not that they’re actually independent at the code level.
True modularity means each module can be installed or not installed, and the rest of the system doesn’t care. No broken imports. No required dependencies you didn’t want. No features that silently activate because some module decided to extend a shared model.
Fullfinity ships with over 20 modules including CRM, Sales, Inventory, Accounting, and eCommerce. But you install what you need. A business that only needs inventory and accounting doesn’t pay the performance or complexity cost of having CRM and eCommerce loaded.
For developers building custom modules, this architecture is important because it tells you exactly what you can depend on. If you’re building a custom module that extends Inventory, you declare that dependency explicitly. Your module works when Inventory is installed. It doesn’t need to know anything about Sales or CRM. That separation keeps your custom code clean and makes it portable.
What This Means for Multi-Client Consultants
If you’re an ERP consultant managing implementations for multiple clients, you’ve probably built the same customization three or four times for different clients on the same platform. Small variations each time, but mostly the same logic.
Proper modularity lets you package that customization as a reusable module. Install it where you need it. Don’t install it where you don’t. The base platform doesn’t change. Your customization lives in its own module with clean boundaries.
This is how you stop reinventing the wheel for every client.
What “Open Core” Actually Means for Your Extensibility Strategy
Open core is a product model where the foundation is open source, and additional features are available commercially. It’s common in developer tools and enterprise software.
The reason it matters for ERP extensibility is less about licensing and more about what it signals about the codebase. Open core products tend to have well-defined extension points because the business model depends on the community being able to build on top of the platform. If extensions are painful to build, the ecosystem doesn’t grow, and the open core model fails.
Closed systems don’t have this pressure. The vendor controls what you can customize and how. If they don’t support a particular extension pattern, you’re stuck with workarounds.
With Fullfinity’s open core model, the extension patterns are first-class features, not afterthoughts. The platform is designed to be built on. That means the inheritance model is documented and supported. The access-aware ORM works for your models, not just the built-in ones. The YAML-based view system is something you can use in your own modules, not just the base platform.
It also means you can read the source when you need to understand exactly what’s happening. That’s not a small thing. When you’re debugging a complex customization at 11pm before a go-live, being able to read the actual code rather than guessing at black-box behavior makes a real difference.
Conclusion
ERP customization doesn’t have to be the process of building something that works today and breaks in six months. The patterns that cause that breakage are identifiable and avoidable.
Three things to take away from this:
-
Inheritance beats patching. If you’re extending a model by modifying source code you don’t own, you’re accumulating technical debt. Proper model inheritance lets you add behavior without touching the base. Your customizations stay intact when the base changes.
-
Schema management should be automatic. Migration files are a coordination problem. Every time you ship a customization that requires a manual migration step, you’re introducing a failure point. An ORM that manages schema changes automatically removes that entire class of problems.
-
Access control belongs at the query level. Filtering permissions in application code after the database has already done the work is both slower and more error-prone. Access-aware query generation means your custom modules inherit the same security guarantees as the platform itself.
If you’re evaluating ERP platforms for an upcoming implementation, or you’re a developer who’s tired of fighting a system that wasn’t designed to be extended, take a look at what Fullfinity is actually built to do. The architecture decisions aren’t accidental. They’re what makes the difference between a system you can build on and one you’re constantly working around.
And if you want to see more of the thinking behind how Fullfinity is built, the blog has more on the technical decisions that shape the platform.