About & methodology
Every figure on this site is derived automatically from the public source code of Odoo modules. This page explains where the data comes from and how each indicator is computed, so you can judge how much to trust it. None of it replaces reading the code: the indicators point at what deserves a closer look.
Data collection
A collector walks every repository of the tracked GitHub/GitLab organizations, once per Odoo version, by
checking out that version's branch (18.0, 17.0, ...). It runs periodically
(every 6 hours in the default deployment), so data can lag the repositories by that much.
- A folder is a module when it has a manifest (
__manifest__.py, or__openerp__.pyon old series). The manifest's dictionary is read with an embedded Python interpreter; nothing else of the module is imported. - A missing
licensekey is stored as Odoo's own default: AGPL-3 below 9.0 and LGPL-3 from 9.0. - A module is only re-analyzed when its last commit or the analyzer itself changed; otherwise the previous analysis is kept.
- Each module version (
versionin the manifest) keeps its own snapshot of the analysis, which is what the version selector on a module page switches between. - Required by lists the modules of the same Odoo version, in any repository or organization, that declare the module in
depends(capped at 500).
Code analysis
The source is parsed, never executed: Python files with Python's ast module, XML and CSV data
files with standard parsers. From that the analyzer extracts:
- Models created or extended, with their new fields (type, relation, keyword arguments) and public methods (signature, decorators, docstring).
- Views created or inherited, with their type.
- HTTP endpoints declared with
http.route: paths, resolvedauth, type, methods, CSRF setting and whether the handler usessudo(). - Data records: security groups, access rights (
ir.model.access), record rules, cron jobs, and theirnoupdateflag. - Raw facts for the security and migration checks below (SQL built by string formatting,
eval, deprecated syntax, ...).
Static analysis is best-effort: anything built dynamically at runtime (models named from variables, routes registered in loops, views generated in Python) can be missed or incomplete.
Health score
A 0-100 summary of four facts already shown on the module page, so they don't have to be weighed by hand. Each factor is worth 25 points. It's computed for the selected Odoo version.
| Factor | Measures | Points |
|---|---|---|
| Security | Static-analysis findings of the current module version (see below). | No findings: 25 · only minor ones: 25 minus 3 each, never below 15 · grave ones: 10 for one, 5 for two, 0 for three or more |
| Activity | Months since the last commit by a person (bots excluded, see below) on this version's branch. | ≤6: 25 · 7-12: 20 · 13-24: 12 · 25-36: 6 · older, or bot commits only: 0 |
| Required by | How many modules of the same Odoo version depend on it. | 0: 5 · 1: 15 · 2-4: 20 · 5 or more: 25 |
| Maturity | In how many Odoo versions the module exists within its organization, up to the selected one. | 1: 5 · 2: 12 · 3: 18 · 4 or more: 25 |
- A factor without data (no code analysis, no commit history collected) shows as n/a and is left out: the score is the points obtained over the points available, so missing data never counts as a zero.
- Grades: good from 75, fair from 50, poor below. A module with any grave security finding is never graded good.
- Activity is measured per branch, so older Odoo versions naturally score lower: they get fewer fixes once newer versions exist.
How the thresholds were chosen. Maturity and Required by were checked against what later happened to the 16.0 and 17.0 modules: whether they were migrated to a newer version. That rate climbs from 53% for modules present in a single Odoo version to 92% for those in seven or more, and from 68% for modules nobody depends on to about 95% for five or more dependants, then flattens; the steps above follow those curves. Modules graded good were migrated 90-95% of the time, poor ones about 56%. The activity and security steps, and the 75/50 cutoffs, have no comparable outcome to validate them against.
A heuristic to sort modules for review, not an audit or a quality guarantee.
Security findings
Rule-based checks over the extracted records, endpoints and code facts. They flag patterns that are
usually risky; only a manual review can confirm a real vulnerability. Grave is reserved
for high-precision rules; both kinds are counted on the module page, and grave ones are listed in detail
there in Dev mode. Modules named test_* or in a "Tests" category are skipped: they only exist for
Odoo's own test suites.
| Code | Flags | Severity |
|---|---|---|
acl-global-write | Access right with no group (applies to every user, portal and public included) granting write or delete. | Grave; minor when it only grants create or its id/name says it's meant to be global |
acl-global-read | Access right with no group granting read. | Minor |
acl-public-write | Access right granting write or delete to the portal or public group. | Grave; minor when create-only or explicitly named for that audience |
acl-privilege-escalation | Write access to users, groups, record rules, access rights, models or fields for a group that isn't an administrator group. | Grave |
rule-public-bypass | Always-true record rule for portal/public, unless this module's own access rights deny that group everything. | Grave |
rule-group-bypass | Always-true record rule for another non-manager group. | Minor |
route-user-csrf-off | Logged-in HTTP endpoint accepting state-changing methods with CSRF disabled. | Grave |
route-public-csrf-off | Public endpoint with CSRF disabled (normal for webhooks). | Minor |
route-mass-assignment | Endpoint passing the raw request payload to create()/write(). | Grave on public or sudo() routes, minor otherwise |
route-public-sudo | Public endpoint using sudo(). | Minor |
sql-injection | SQL built by string formatting from a value the caller controls directly: an argument of an RPC-callable method or route, the request payload or the context. | Grave |
sql-injection-review | SQL built by string formatting from any other value. | Minor |
code-eval | eval/exec on non-literal input. | Minor |
unsafe-deserialization | pickle or unsafe YAML loading. | Minor |
shell-injection | Subprocess calls with shell=True. | Minor |
tls-verify-disabled | HTTP requests with certificate verification off (verify=False). | Minor |
html-unsanitized | User-writable fields.Html(sanitize=False): stored XSS for everyone viewing it. | Minor |
Effective access also depends on other modules, global rules, field restrictions and Python checks that a single module's files can't show, which is why even grave findings are worded as review priorities.
Migration notes
A checklist of code that changes meaning across Odoo versions. Each API change is tied to the version where it happened, checked against that version's source, so the note reads "Migrating to X.0" for a module below it (what you'll have to change) and "Since X.0" for one at or after it (code already ignored, broken or deprecated there). Repeats of the same note in one file are folded into one.
| Version | Change |
|---|---|
| 10.0 | The openerp namespace was renamed to odoo. |
| 11.0 | The workflow engine (trg_validate) was removed. |
| 13.0 | @api.multi/@api.one were removed; track_visibility became tracking. |
| 15.0 | QWeb t-raw is deprecated in favour of t-out. |
| 16.0 | The web client no longer calls fields_view_get (override _get_view). |
| 17.0 | attrs/states are rejected in views; name_get no longer feeds display_name. |
| 18.0 | tree views became list; user_has_groups() was removed; group_operator became aggregator; cron jobs lost numbercall/doall. |
| 19.0 | Only __manifest__.py is recognized; _sql_constraints is ignored in favour of models.Constraint. |
Version-independent notes cover code that bypasses or predates the ORM: classes still on the old
osv/orm API or declaring fields as plain dicts (warnings), raw SQL that inserts,
updates or deletes rows (info), and _auto = False models whose init() creates a
real table (warning: its schema is never upgraded by Odoo) or a SQL view (info). Version changes that
break or silently disable code are warnings; deprecations and renames with a working alias are informational.
License review
Odoo modules extend each other through Python inheritance, so each module is treated as a derivative work
of everything in its depends closure. The review compares the manifest licenses of the module
and that whole closure.
- Combined license: the strongest copyleft among the open-source modules involved (AGPL-3 > GPL-3 > GPL-2 > LGPL-3). It's shown only when it's stronger than the module's own license; a proprietary module isn't distributed under its dependencies' copyleft.
- Errors: a proprietary module (OEEL-1, OPL-1, "Other proprietary") depending on GPL/AGPL code or the other way round, and GPL-2-only code combined with (L/A)GPL-3 code.
- Warnings: an LGPL-3 module that needs proprietary modules (commercial licenses required), and a module upgraded to AGPL-3 by its dependencies (AGPL's network clause: users of a hosted instance must be offered the source). An upgrade to a weaker copyleft is informational.
- Info: licenses outside Odoo's documented list (e.g. "Other OSI approved licence"), which can't be judged automatically.
A heuristic to spot problems early, not legal advice.
Committers & activity
- Contributions come from
git logof each module's folder on each version branch, counting only the commits that branch adds over the previous Odoo version's branch, so a commit is credited to the version that introduced it rather than to every later one. - People are identified by the git committer name; the same person with two spellings counts twice.
- Bots and automation accounts (translation platforms, CI, merge and forward-port bots) are excluded from rankings and from the health score's activity factor. They're recognized by name pattern ("... bot", "...-bot", "...[bot]", "Launchpad Translations on behalf of ...", Weblate, ...), not by a fixed list, because they come in dozens of spellings.
Migration pull requests
- An open pull/merge request counts as a migration of a module when its branch follows the OCA convention
{version}-mig-{module}(e.g.18.0-mig-sale_commission). It's listed on the module page while it's open and the module doesn't exist yet for that version. - The average time a migration stays open counts merged requests only. The opening date comes from GitHub/GitLab; the closing one is when the collector noticed the request was gone, so it's only as precise as the refresh interval.
Vulnerabilities
Python dependencies declared in the manifest's external_dependencies are looked up on PyPI,
which reports the known advisories of a release from the
OSV database. Only requirements that fix a
version can be checked: ==X and <=X at X, <X at a published
release below X. Dependencies without a version or with only a lower bound (>=X), and system
(binary) dependencies, are not checked, so "no advisories" never means "safe".
Semantic search
Each module is turned into a vector from its name, technical name, category and the start of its
description, with a multilingual sentence-embedding model (paraphrase-multilingual-MiniLM-L12-v2),
so a query in any supported language can find modules described in another. Results rank by cosine
similarity, boosted when rare query words of four or more letters appear verbatim (names the model has
never seen, such as new regulations).
Limitations
- Only the tracked organizations' public repositories are analyzed; a module's dependencies outside them are unknown here.
- Everything is static: code paths, configuration and other installed modules can change the real behaviour.
- Data is as fresh as the last collector run.
- This is an independent project, not affiliated with Odoo S.A. or the Odoo Community Association. The source code is public: every rule above can be read there.