This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
OdooLS detects your Odoo setup from odools.toml configuration files. They use TOML syntax and work the same across every supported IDE (VS Code, PyCharm, Zed), so a config can be shared across a team.
You can run OdooLS with no config at all, a single minimal file, or a layered multi-profile setup — pick the level that fits your project.
Quick start
Zero config
Open a workspace and OdooLS auto-detects your setup, creating a profile named "default". See Auto-detection for the exact rules — in short, it looks for the Odoo sources and any addon directories in (or just below) your workspace folders.
This is enough for the common case: open a src/ folder containing odoo/, enterprise/, and design-themes/, and everything is found automatically. It works the same if you open odoo/, enterprise/, and design-themes/ as separate workspace folders — OdooLS scans every folder you open.
A minimal config file
Auto-detection has limits: maybe you opened only an addons directory, want a specific Python interpreter, or your sources live outside the workspace. In that case, create an odools.toml in your workspace root:
[[config]]
name = "My Config"
odoo_path = "/home/odoo/community"
addons_paths = ["/home/odoo/enterprise"]
python_path = "/home/odoo/venv/bin/python"
Each [[config]] block is a profile. Anything you don't set is auto-detected or falls back to its default.
Duplicate workspace folder names are allowed, but you can't target them with
${workspaceFolder:NAME}— that pattern only resolves names that are unique, and is skipped for any name shared by several folders. All folders are still scanned regardless.
Configuration reference
| Key | Type | Default | Description |
|---|---|---|---|
name |
string |
"default" |
Profile name, used to switch profiles in your IDE. |
extends |
string |
— | Inherit from another profile (see Profiles). |
odoo_path |
string |
auto-detected | Odoo source path (must contain odoo/release.py). |
addons_paths |
string[] |
auto-detected | Addon directories to load. |
addons_merge |
"merge" | "override" |
"merge" |
How addons_paths combines with a parent profile. |
python_path |
string |
detected python3 |
Python interpreter path, or a command name like "python3". |
stdlib |
string |
— | Custom typeshed/stdlib path (end it with /). |
additional_stubs |
string[] |
— | Extra type-stub directories. |
additional_stubs_merge |
"merge" | "override" |
"merge" |
How additional_stubs combines with a parent profile. |
additional_languages |
string[] |
— | Extra i18n language codes, e.g. ["fr_BE", "es"]. Base codes are added automatically ("fr_BE" also adds "fr"). Always merged with parents. |
file_cache |
bool |
true |
Cache workspace files on disk. |
diag_missing_imports |
"all" | "only_odoo" | "none" |
"all" |
Which missing-import diagnostics to report. |
ac_filter_model_names |
bool |
true |
Autocomplete only model names from module dependencies. |
auto_refresh_delay |
integer |
1000 |
Delay (ms) before refreshing after a file change. Clamped to 1000–15000. |
no_typeshed_stubs |
bool |
false |
Disable the built-in typeshed stubs. |
diagnostic_settings |
table |
— | Override the severity of individual diagnostics (see Diagnostics). |
diagnostic_filters |
table[] |
— | Show/hide diagnostics per file (see Diagnostics). |
tsserver_command |
string |
"tsserver" |
Provide custom command to run tsserver (OdooLS >1.5.0). |
disable_javascript |
bool |
false |
Turn off frontend-asset processing entirely: no tsserver process, and JS/TS files and Owl XML templates from manifest asset bundles are not parsed, so no related diagnostics or features (OdooLS >1.5.0). |
ts_check |
bool |
false |
Turn off tsserver diagnostics in Js files. This is equivalent to checkJS in a tsconfig.js (OdooLS >1.5.0). |
"$version", "$base" |
string |
— | Variables for paths, used by versioning. Must be quoted (TOML keys with $ require it). |
Paths accept variables. In any path value you can use:
${userHome}— your home directory${workspaceFolder}— the current workspace folder${workspaceFolder:NAME}— the folder namedNAME(dropped, not fatal, if it matches zero or several folders — recorded so your IDE's config panel can flag it)
Invalid values are dropped, not fatal. A path that doesn't exist or fails validation (an odoo_path without odoo/release.py, an addon entry with no modules) is dropped — but recorded so your IDE's config panel can flag it. Check the server logs if a path isn't taking effect.
Some changes restart the server. Editing odoo_path, addons_paths, python_path, additional_stubs, stdlib, no_typeshed_stubs, tsserver_command, disable_javascript, or a version ($version/$base) triggers a full restart. Everything else applies live.
Auto-detection
OdooLS infers odoo_path and addons_paths from your workspace folders. User-set values always win — auto-detection only runs for a field you didn't set.
New in 1.5.0: auto-detection now searches below your workspace folders — immediate children for
odoo_path, and a recursive scan foraddons_paths. Earlier versions only checked the workspace folder roots themselves.
Odoo path
A directory is a valid Odoo path if it contains odoo/release.py. When odoo_path is unset, OdooLS checks:
- each workspace folder root, then
- their immediate children (one level deep) — only if no root matched.
Error: if more than one folder or child qualifies, OdooLS reports "More than one workspace folder or subfolder is a valid odoo_path" — set
odoo_pathexplicitly to resolve it.
Addon paths
A directory is a valid addon path if it directly contains at least one module (a subdirectory with a __manifest__.py). When addons_paths is unset, OdooLS:
- includes each workspace folder root that is itself an addon path, and
- recursively scans each folder (up to 10 levels deep) for more addon paths.
When a directory qualifies, OdooLS stops descending into it (its children are modules, not further roots). Modules, hidden directories, and noise like node_modules, __pycache__, venv, and dist are never scanned.
Combining manual and detected paths
Setting addons_paths to any value — even [] — turns off auto-detection. To keep both, add the $autoDetectAddons token to your list:
addons_paths = ["/path/to/custom/addons", "$autoDetectAddons"]
The token is replaced with the auto-detected paths and merged with the ones you wrote.
Multiple config files and profiles
Where OdooLS reads config from
OdooLS builds your configuration from several sources, in this order:
-
An explicit config file, if you set one — processed first. This is a single file outside the workspace tree:
- CLI: pass
--config-path=PATH(and optionally--selected-config=NAME) - VS Code: set
Odoo.serverConfigPath(in the settings UI orsettings.json) - Zed: set it under
"lsp"/"odoo"/"binary"/"arguments"— see the Zed README
PyCharm doesn't currently expose this — it lets you pick a profile from the Odoo status bar, but not point at a config file outside the workspace.
- CLI: pass
-
Each workspace folder, by walking up its directory tree and loading every
odools.tomlfound.
These sources are merged, not overriding — the explicit file doesn't replace your workspace files, and being processed first doesn't make it win. When the same profile appears in more than one source, list fields are unioned and scalar fields must agree (a conflict is an error — see Across multiple workspace folders).
Configuration file priority within a workspace
When walking up a single workspace folder's tree, every odools.toml is merged with the closest file winning:
- scalar fields (e.g.
python_path): the closest config wins, - list fields (e.g.
addons_paths): combined per the merge rules.
Relative paths resolve against the directory of the file they appear in. This lets you keep generic settings high up (e.g. in your home directory) and override them per project.
Profiles and inheritance
Each [[config]] block is a profile. With several profiles, your IDE lets you switch between them. A profile can inherit from another with extends:
[[config]]
name = "project A"
extends = "base_setup"
addons_paths = ["./project_a"]
The parent can live in the same file or any odools.toml further up the tree. extends is resolved per workspace folder (after merging that folder's files), then the results from different workspace folders are merged.
Errors: extending a profile that doesn't exist is an error, and circular chains (A extends B, B extends A) are detected and rejected.
How values are merged
When a child inherits from a parent:
- Scalars (
python_path,file_cache, …): child wins; if the child doesn't set it, the parent's value is used. addons_paths/additional_stubs: controlled by their*_mergefield —"merge"(default) concatenates parent + child,"override"keeps only the child's list.additional_languages: always concatenated (no override).diagnostic_settings: merged per code — child overrides the parent for the same code.diagnostic_filters: accumulated — both parent and child filters apply.
Across multiple workspace folders
When the same profile name appears in several workspace folders, they merge — but scalars must agree:
- list fields are unioned,
- scalar fields must hold the same value everywhere, or OdooLS reports "Conflict detected in 'profile' for key 'field': 'value1' vs 'value2'."
Example
📁 home/user/
├── 📄 odools.toml
├── 📁 community/ (odoo source)
└── 📁 my_addons/
├── 📄 odools.toml
├── 📁 project_a/
└── 📁 project_b/
/home/user/odools.toml — shared base:
[[config]]
name = "base_setup"
file_cache = false
odoo_path = "./community"
/home/user/my_addons/odools.toml — one profile per project:
[[config]]
name = "project A"
extends = "base_setup"
addons_paths = ["./project_a"]
[[config]]
name = "project B"
extends = "base_setup"
addons_paths = ["./project_b"]
Open my_addons and pick "project A" or "project B" — no need to open community separately. To skip the manual choice, use a dynamic path instead:
[[config]]
name = "Current project"
extends = "base_setup"
addons_paths = ["${workspaceFolder}"]
Now any folder you open directly under my_addons is loaded as its addon path automatically.
Working with multiple versions
If you keep several Odoo versions side by side, OdooLS can handle versions for you with two mechanisms. Use one or the other — if both are set, detection wins (it runs first and overrides $version).
In both, ${version} and ${base} are path variables that expand to the values of $version and $base.
Tip:
$versioncan also point at a__manifest__.py— OdooLS reads itsversionfield and keeps themajor.minorpart (e.g."18.0.1.0.0"→"18.0").
Splitting: one profile per version directory
${splitVersion} scans a directory for version-numbered subdirectories and creates one profile per version, so you don't duplicate profiles by hand.
📁 home/user/my_addons/project_a/
├── 📁 17.0/
└── 📁 18.0/
[[config]]
name = "Current project"
"$version" = "${workspaceFolder}/${splitVersion}"
addons_paths = ["${workspaceFolder}/${version}"]
OdooLS scans the resolved directory for version-named subdirectories (matching ^(\D+~)?\d+\.\d+$, so 17.0, 18.0, saas~16.1, …) and generates a profile per match, named {original}-{version} (e.g. Current project-17.0, Current project-18.0). The original profile becomes a template. Open project_a and pick the version you want.
Detection: read the version from the path
${detectVersion} reads the version out of the workspace path instead of splitting. Put it at the end of $base; OdooLS strips the token, matches the rest against the open folder's path, and takes the next path component as the version.
📁 home/user/
├── 📁 17.0/
│ ├── 📁 odoo/
│ └── 📁 enterprise/
└── 📁 18.0/
├── 📁 odoo/
└── 📁 enterprise/
[[config]]
name = "default"
"$base" = "/home/user/${detectVersion}"
odoo_path = "${base}/odoo"
addons_paths = ["${base}/enterprise"]
Open any folder under /home/user/17.0/ or /home/user/18.0/ (however deeply nested) and OdooLS resolves ${base} and ${version} to that version automatically.
Diagnostics
Changing severity
Override the severity of individual diagnostics — or disable them — with diagnostic_settings:
[config.diagnostic_settings]
OLS03001 = "Info"
OLS02001 = "Disabled"
Allowed values (case-sensitive): Error, Warning, Info, Hint, Disabled. Across inheritance, child settings override parent settings for the same code. The full list of codes is in diagnostic_codes_list.rs.
Filtering by file
diagnostic_filters controls which diagnostics appear in which files. Add one or more [[config.diagnostic_filters]] blocks:
[[config.diagnostic_filters]]
codes = ["OLS.*"]
paths = ["**/my_folder_1/*", "**/account*/*"]
path_type = "notin"
| Key | Type | Required | Description |
|---|---|---|---|
paths |
string[] |
yes | Glob patterns for matching files. Supports template variables (${workspaceFolder}, ${version}, …). |
codes |
string[] |
no | Regexes matching diagnostic codes to filter. Invalid regexes fail config loading. |
types |
string[] |
no | Severities to filter: "Error", "Warning", "Info", "Hint". ("Disabled" is not valid here.) |
path_type |
"in" | "notin" |
no | Apply the filter inside (in, default) or outside (notin) the matched paths. |
path_type = "in": hide the matching diagnostics in the matched files.path_type = "notin": hide them everywhere except the matched files.
So the example above hides OLS.* diagnostics everywhere except my_folder_1 and account* modules — i.e. only those show OLS diagnostics. Filters accumulate across inheritance: parent and child filters all apply.
Notes about diagnostic codes
Diagnostic codes exported by OdooLS are under three formats:
- Python diagnostics are under the format
OLSxxxxxxwhere x is the specific error code (See codes from OdooLS) - OXC diagnostics for Javascript files are under the format
oxc_XXXwhere XXX is the code from OXC (See Rules from OXC). Example:oxc_eslint(no-unused-vars) - Typescript diagnostics for Javascript and owl-xml files are under the format
tsserver_XXwhere XX is the code from tsserver (See errors from tsserver). Example: tsserver_7006