Every Odoo integrator has a project they underquoted. The client described what they wanted, the estimate seemed reasonable, and then the work started - and the reality turned out to be significantly more complex than the description suggested.
Scope creep is one cause, but undiscovered technical complexity is the more expensive one. A customization that looks like two days of work on a clean Odoo instance can take three weeks on a heavily customized one. This guide covers the questions and technical checks that surface that complexity before you commit to a price.
Step 1: Understand the gap between "what they want" and "what exists"#
Most clients describe desired functionality without knowing what Odoo already does natively. Your first job is to separate three categories:
Already in standard Odoo - the client does not know it exists, or it exists in a module they have not installed. This happens constantly with inventory valuation methods, partner categories, pricelist rules, and automated email templates. Check before quoting any development.
Available in an OCA module - the Odoo Community Association maintains hundreds of production modules that cover gaps between standard Odoo and common business needs. An OCA module you install and configure in a day may cost the client far less than a custom module you build in a week.
Genuinely custom - the functionality does not exist and cannot be assembled from existing modules. This is where estimation risk lives.
The discovery conversation should always walk through these three categories explicitly. Clients who have already spoken to other vendors may have been told "yes, we can build that" without being told it already exists for free.
Step 2: Audit the existing codebase before estimating custom work#
This is the step most integrators skip when scoping for an existing Odoo instance, and it is the most expensive skip.
A customization request on a clean Odoo instance and the same request on an instance with 30 custom modules are not the same project. The existing customizations may:
- Override the model method you need to call or inherit
- Add constraints or computed fields that interact with your changes
- Modify the view structure in ways that break standard field placement
- Add record rules that affect who can create or read the records you are working with
- Have broken or stale code that interacts with the area you are touching
The minimum codebase audit before quoting:
For every model the customization touches, check:
- Which custom modules currently inherit from it (
_inherit = 'model.name') - Which compute methods or
@api.constrainsrun on fields you need to write - Whether
write(),create(), orunlink()are overridden in any custom module - Whether there are custom record rules that affect read or write access
This takes 30–60 minutes on a codebase with an indexed search tool and significantly longer without one. Skipping it means you are estimating based on the Odoo documentation, not on what is actually running in the client's instance.
Step 3: Identify the inheritance chain#
Odoo's model inheritance is additive. Every module that inherits from the same model contributes fields, methods, and overrides that stack. Understanding the full inheritance chain for the models you are touching tells you:
- Whether you are the first to override a method or the fifth (the fifth is harder and riskier)
- Whether existing compute chains will fire in response to your changes
- Whether any currently installed module is doing something you need to undo or work around
The practical question: "Is anyone currently calling super() in write() on this model, and in what order?"
If three modules are currently chained in write() and your customization needs to sit between two of them, that is not a simple _inherit - it is a sequencing problem that adds complexity and risk.
Step 4: Check view architecture complexity#
Odoo views are inherited incrementally using XPath. A form view that has been modified by 8 custom modules is significantly harder to extend correctly than a stock view.
Common view problems that inflate scope:
- Conflicting XPath targets - two modules both try to insert a field at the same location, and a third insertion needs to go between them
- View inheritance that breaks with Odoo version changes - XPath expressions that reference structural elements (
//group,//page) can silently fail if the parent view changes in a patch update - Dynamic views via
attrs- views with complex visibility logic based on field values; adding fields to these views requires understanding the full conditional logic
The check: review the current view XML for each affected form view, count the number of inherited modifications, and look for XPath expressions that target areas near where you need to add fields.
Step 5: Understand the data volume and performance implications#
A customization that works correctly in a test database with 500 records may perform unacceptably in production with 500,000 records. Before quoting any customization that:
- Adds a computed field to a high-volume model
- Adds a
write()hook to a model that is written by batch operations - Creates a new view with a search or filter over a large dataset
- Adds a scheduled action that processes records in a loop
...you need to know the approximate production data volume and whether the existing instance has performance headroom.
Asking the client "how many records do you have in [model]?" is a basic question. Forgetting to ask it is how you build a feature that causes production to time out.
Step 6: Assess security and access rights impact#
Every custom module that creates or modifies records needs to declare access rights. Scoping questions to ask:
- Who should be able to create/read/write/unlink the new records or features?
- Does the access pattern require record-level rules (e.g., salespeople only see their own records)?
- Are there existing custom groups or roles that the new feature needs to respect?
- Will the feature interact with multi-company record rules?
Access rights are consistently underestimated. A module that adds a new model needs at minimum: a res.groups entry (if it introduces a new group), ir.model.access.csv with at least read/create/write/delete permissions per role, and ir.rule records if record-level isolation is required.
Multi-company access rules add significant complexity - verify whether the client runs multi-company before scoping any module that touches models with a company_id field.
Step 7: Identify upgrade risk#
Odoo releases a major version annually. If the client is on Odoo 16 and planning an upgrade to 17, a customization you build today needs to survive that upgrade.
Upgrade risk factors:
- Custom modules that inherit from models that change between versions (check Odoo's upgrade scripts and change logs)
- Views that target elements renamed or moved in the new version
- Python code that uses private ORM methods (
_-prefixed) that are not part of the public API - Stored computed fields that may need recomputation after an upgrade
- Custom stored procedures or raw SQL that references column names
If the client has an upcoming upgrade, ask explicitly whether the customization needs to be migration-safe. If yes, your scope includes writing and testing migration scripts - that is not a free addition.
Estimation heuristics#
These are rough guides, not formulas. Use them to sanity-check estimates before presenting them.
| Customization type | Clean instance | With existing customizations |
|---|---|---|
| New field on existing model (no compute) | 0.5–1 day | 0.5–1 day (usually safe) |
| Computed field with dependencies | 1–2 days | 2–4 days |
Override of write() or create() | 1–2 days | 3–6 days |
| New model with full CRUD UI | 3–5 days | 4–8 days |
| New report (QWeb PDF) | 2–4 days | 2–4 days (usually safe) |
| Wizard (transient model with logic) | 2–4 days | 3–6 days |
| Integration with external system | 5–10 days | 7–15 days |
The multiplier for "with existing customizations" is not a fixed number - it depends entirely on what those customizations are and whether they interact with the area you are touching. The audit in steps 2–4 is what lets you narrow that range.
The pre-quote checklist#
Before submitting a quote for any Odoo customization on an existing instance:
- I have identified whether the feature already exists in standard Odoo or an OCA module
- I have listed every model my customization will touch or create
- I have checked which custom modules currently inherit from those models
- I have reviewed
write(),create(), andunlink()overrides in existing modules - I have reviewed the view XML for each affected form/list view
- I have confirmed the approximate production data volume for affected models
- I have assessed the access rights requirements (groups, ir.model.access, ir.rules)
- I have confirmed whether multi-company is in use and whether my changes interact with company_id fields
- I have asked whether an upgrade is planned and whether migration safety is required
Each unchecked item in this list is an estimation risk. Completing the list before quoting is what separates consistent, predictable project profitability from the ones that go wrong.
ERPeek lets you ask questions like "which custom modules inherit from sale.order.line and override write()?" or "show me all computed fields on stock.picking that depend on a One2many" - in plain language, against the actual codebase. If you are scoping work on an existing Odoo instance, the contact page is the fastest way to try it on a real codebase.

