13 3. Configuration files
Florian Daloze edited this page 2026-07-21 00:33:19 +02:00
This file contains ambiguous Unicode characters

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 100015000.
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 named NAME (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 for addons_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:

  1. each workspace folder root, then
  2. 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_path explicitly 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:

  1. 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 or settings.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.

  2. Each workspace folder, by walking up its directory tree and loading every odools.toml found.

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 *_merge field — "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: $version can also point at a __manifest__.py — OdooLS reads its version field and keeps the major.minor part (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 OLSxxxxxx where x is the specific error code (See codes from OdooLS)
  • OXC diagnostics for Javascript files are under the format oxc_XXX where 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_XX where XX is the code from tsserver (See errors from tsserver). Example: tsserver_7006