Skip to main content

odoo-db

CLI tool for Odoo database management. Connects to local PostgreSQL via Unix socket (peer auth — no credentials needed). Designed for developers running Odoo locally.

Installation

uv tool install git+https://github.com/trobz/odoo-db

Or for development:

git clone https://github.com/trobz/odoo-db
cd odoo-db
make install                       # install deps + pre-commit hooks
uv tool install --editable .       # make `odoo-db` available globally

Usage

odoo-db [OPTIONS] COMMAND [DB]

Global options:

Option Default Description
--output-file - (stdout) Write output to file
--output-format text Output format: text, json, prometheus
--log-level WARNING Logging level: DEBUG, INFO, WARNING, ERROR
--log-file logs/odoo-db.log Log file path (auto-created)
--include-sensitive-information off PII master switch: unmask identifying data (e.g. attachment filenames) in any command that redacts it by default

Commands:

Command Description
list List all Odoo DBs with version and neutralization status
modules <db> List installed modules with version
crons <db> List active scheduled actions (--all also includes inactive ones). --running shows crons currently held by an Odoo worker (RowShareLock on ir_cron) — transient debug data, not bundled into prepare-audit. --include-code adds the python source of each state='code' cron (ignored with --running, which already always shows it)
jobs <db> Queue job counts by state (requires queue_job module)
params <db> [pattern] Show ir_config_parameter keys and values. Optional pattern narrows to keys containing it (case-insensitive substring). Values of secret-bearing keys are masked ******** by default; the global --include-sensitive-information reveals them
mail <db> Audit outbound mail configuration: whether the database is neutralized (database.is_neutralized, the single most common reason mail never leaves an Odoo database — flagged up front, with Odoo's own inserted stub relay named in its own summary line rather than mistaken for a real one); the ir_config_parameter keys mail cares about (mail.bounce.alias, mail.catchall.alias/.domain, mail.default.from, plus Trobz's default_email and mail.default.from_filter); on Odoo 17+, the per-company mail.alias.domain records that actually control bounce/catchall/default-from routing now (the first 4 of those ICP keys became legacy in v17 and are hidden from the CLI's text output once they can no longer affect routing — shown side by side with a note otherwise, on a pre-17 database where they're the only mechanism or a 17+ one where a company still has no alias domain assigned while a legacy key still holds a value; Odoo never clears those keys even on a database that migrated fine, so a leftover value alone doesn't mean the migration is stuck — the JSON output always keeps the full list, since this tool's main job is comparing it across a v16-to-v19 migration; mail.default.from_filter stays live at runtime and is not part of that migration); company/system(OdooBot)/admin partner emails, resolved via ir_model_data so a renamed admin login or a deleted record still shows up (as (record missing)) instead of silently vanishing — shown as-is (organizational mailboxes, not individual PII), flagged if still at Odoo default (case-insensitively); outgoing ir.mail_server relays, named in a summary line (not a per-row column) when the name/host matches a known test-mail catcher like mailhog or a well-known managed relay like Google Workspace or Microsoft 365 — a positive confirmation, not just the absence of the other flag; and mass_mailing install state. Ported from an odooly/API-based check to direct SQL — none of it needs auth. SMTP username/password are masked by default; the global --include-sensitive-information reveals them
check-sensitive-information <db> Surface secrets the database still carries — the question to answer before a dump leaves the building, and the one neutralizing a copy does not: neutralize clears what a database can still do, and only for modules shipping a neutralize.sql, which a client's own module never does. Three sections: secret-bearing ir_config_parameter keys that actually hold a value (a boolean one like auth_signup.reset_password is a checkbox, not a password, and is dropped, as are the few named core keys that match the substring while holding no secret — a policy number (auth_password_policy.minlength) or a key that is public by design and handed to the browser (cf.turnstile_site_key, recaptcha_public_key), each one's secret sibling still listed); ir.mail_server rows a dump would leak — a stored relay credential, or a known production relay host even with none (such a relay often authenticates by IP allowlist, so the host itself is the finding); archived rows included, unlike the mail audit's active-only counts, since a disabled relay still ships its password in the dump, while Odoo's neutralization stub and the known test catchers are dropped as having no credential to leak; what neutralize.sql should have cleared and did not — led by base's own two statements, the strongest signal since their tables exist on every database: any cron still active other than autovacuum_job, and any mail server still active that isn't the invalid stub or a known catcher; then the per-module rows (an enabled payment provider on both the pre-16 and post-16 table name, an IAP token without +disabled, an active fetchmail server, a template pinned to a real relay, WhatsApp/VoIP/bank-feed/certificate credentials)) — shown with the database's own is_neutralized claim, since the same list is a leftover on a copy claiming neutralization and an inventory on a production database; and tables whose name matches a credential store (*api_key*, *api_config*, *api_instance*, *api_url*), with row count, owning module ((none) = custom/orphan, read those first) and which of their columns look secret-bearing. Values masked by default; the global --include-sensitive-information reveals them. Detection is by name and best-effort both ways: a credential in a table named nothing like one is not found, and a hit is a candidate to read, not a verdict
users <db> List active users with connection status
groups <db> List res.groups (category, name, share flag). --include-users adds each group's member logins. --include-acls adds per-group model access rights and record rules, plus top-level global_acls/global_rules for rows with no group at all (apply to every user — excluded from prior output, now the highest-value rows in a permission audit)
roles <db> List res.users.role (requires OCA base_user_role; prints a message if not installed). --include-users adds currently-enabled assigned users' logins. --include-groups adds the role's full resolved group set (its own group plus all directly and transitively implied groups)
role-drift <db> Detect drift between assigned res.users.role and actual res.groups membership (requires OCA base_user_role). Reports missing_groups (role grants a group the user doesn't actually have — sync gap) and extra_groups (user physically holds a role's own marker group but no role assignment — active or via another assigned role's implied closure — explains it: stale privilege from a removed/expired role, or a group granted by hand), per user. Baseline groups implied by roles but not exclusive to any of them (e.g. "Internal User") never count as drift. Each user is keyed by user_id; login (PII) is included only with the global --include-sensitive-information flag. --output-format prometheus exposes a odoo_db_role_drift_users gauge for alerting
locks <db> Show active PostgreSQL locks
stats <db> Per-table record counts and sizes by year (--years N, --top N). Tables with 0-byte heap are reported as empty without running count(*)
bloat <db> Estimate table + index bloat — space reclaimable by VACUUM FULL / REINDEX / a dump+restore migration (autovacuum never returns it). Uses pgstattuple for exact figures when the extension is installed and the relation fits under --exact-max-scan (MB), else a cheap statistical estimate; each row is tagged exact/est. Also flags high dead-tuple ratios, stale autovacuum, and unused indexes (idx_scan = 0)
studio <db> Show Studio customizations: custom models, models extended with Studio fields, and studio-flagged record counts by type
not-odoo <db> Show non-Odoo database objects: custom views, triggers, functions, and stored procedures. Triggers/functions are tagged recognized (known infra: unaccent, queue_job_notify, …) or custom
attachments <db> Read-only ir.attachment storage audit: repartition (storage location, by-model, mimetype family, size distribution, growth by year, largest files) plus cleanup/archive candidates (uninstalled-model orphans, regenerable asset bundles, duplicate checksums, aged transient, DB-stored bulk). --validate-orphans adds dead-res_id detection for the heaviest models. Sizes are file_size sums (reliable on any backend; payloads never read). Filenames are redacted by default — pass --include-individual-filenames (or the global --include-sensitive-information) to show them
prepare-audit <db> Bundle summary + modules + model_owners + orphan_tables + users_by_year + stats + not-odoo + studio_customizations into <db>.json (in the current directory) for /odoo-dev:audit-db (--years N, --top N; --top 0 means all tables). orphan_tables flags tables not owned by any installed module (reason: uninstalled_module or no_ownership_data). Every table in stats.tables and orphan_tables carries functional_group (first underscore component) for display-time grouping by functional area. users_by_year is an aggregate {year: count} of active users by create_date year — zero PII so the file can ship without an NDA. studio_customizations includes custom model list, extended model list, and studio-flagged record counts by type. The deep attachment audit is intentionally a separate command (attachments), not bundled here
dump <db> Dump a database with pg_dump custom format (-Fc). Writes to ./<db>.pgdump by default (--file PATH to override); --force overwrites; --verbose streams pg_dump progress
restore <backup> Restore a pg_dump file with pg_restore (--no-owner -x). Target DB defaults to the backup filename stem (--db NAME to override); --force drops an existing DB first; --jobs N parallelises restore; --verbose streams pg_restore progress. --reset-passwords sets every res_users.password to --password PWD (or a random 16-char one) after restore — only on Odoo DBs, otherwise warns and skips

Examples

# List all local Odoo databases
odoo-db list

# Verbose: also show module count and user count
odoo-db list --verbose

# Output as JSON
odoo-db --output-format json list

# Export prometheus metrics to file
odoo-db --output-format prometheus --output-file /tmp/odoo.prom list

# Show installed modules for a specific database
odoo-db modules my_db

# Show queue jobs
odoo-db jobs my_db

# Show all config parameters
odoo-db params my_db

# Show config parameters with "mail" in the key
odoo-db params my_db mail

# Reveal masked secret values (database.secret, api keys, ...)
odoo-db --include-sensitive-information params my_db

# What secrets does this database still carry? (before sharing a dump)
odoo-db check-sensitive-information my_db

# Audit outbound mail configuration (config keys, key addresses, relays, mass_mailing)
odoo-db mail my_db

# Same, revealing masked addresses and SMTP passwords
odoo-db --include-sensitive-information mail my_db

# List access groups, with members and ACLs
odoo-db groups my_db --include-users --include-acls

# List user roles (base_user_role), with resolved group sets
odoo-db --output-format json roles my_db --include-users --include-groups

# Per-table stats: record counts and sizes for last 3 years
odoo-db stats my_db

# Top 10 tables, last 5 years
odoo-db stats my_db --top 10 --years 5

# Estimate table + index bloat (reclaimable space)
odoo-db bloat my_db

# Debug mode with full logging
odoo-db --log-level debug list

# Show Studio customizations (custom models, extended models, flagged records)
odoo-db studio my_db

# Show non-Odoo objects: custom views, triggers, functions, stored procedures
odoo-db not-odoo my_db

# Export not-odoo report as JSON
odoo-db --output-format json not-odoo my_db

# Audit ir.attachment storage (repartition + cleanup candidates)
odoo-db attachments my_db

# Also validate dead-record orphans, and show real filenames (PII)
odoo-db --include-sensitive-information attachments my_db --validate-orphans

# Full attachment audit as JSON
odoo-db --output-format json attachments my_db

# Prepare an audit bundle (writes ./my_db.json in the current directory)
odoo-db prepare-audit my_db

# Custom output path
odoo-db --output-file /tmp/audit.json prepare-audit my_db

# Dump a database (writes ./my_db.pgdump)
odoo-db dump my_db

# Dump to a custom path, overwriting any existing file
odoo-db dump my_db --file /tmp/my_db.pgdump --force

# Restore into a database derived from the backup filename
odoo-db restore /tmp/my_db.pgdump

# Restore into a specific DB with 4 parallel jobs, dropping any existing DB
odoo-db restore /tmp/my_db.pgdump --db my_db_copy --force -j 4

# Restore + reset every res_users password to a random one (prints it)
odoo-db restore /tmp/my_db.pgdump --db my_db_copy --force --reset-passwords

Dev

make install   # Install deps + pre-commit hooks
make check     # Lint, format, type-check
make test      # Run tests
make docs      # Build the Zensical documentation site (site-docs/)
make docs-serve  # Serve the docs locally

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

odoo_db-1.22.0.tar.gz (85.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

odoo_db-1.22.0-py3-none-any.whl (81.9 kB view details)

Uploaded Python 3

File details

Details for the file odoo_db-1.22.0.tar.gz.

File metadata

  • Download URL: odoo_db-1.22.0.tar.gz
  • Upload date:
  • Size: 85.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for odoo_db-1.22.0.tar.gz
Algorithm Hash digest
SHA256 136b1f69f4ab0acae93a0ae8944443f426f577994910545391ebc8e1d25b724d
MD5 1e4cae3780e444732c7b87ac85d517a6
BLAKE2b-256 ff577a274fbe16260fd6c81dace1e390cb098672b7e40293a7eab666d777170e

See more details on using hashes here.

Provenance

The following attestation bundles were made for odoo_db-1.22.0.tar.gz:

Publisher: release.yaml on trobz/odoo-db

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file odoo_db-1.22.0-py3-none-any.whl.

File metadata

  • Download URL: odoo_db-1.22.0-py3-none-any.whl
  • Upload date:
  • Size: 81.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for odoo_db-1.22.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a3ae426a2576c75c77d94f9171bdf7adf9e9b8bd33febd29cc90674b5a0fdb7d
MD5 ca9084269468e1ecf503447746612600
BLAKE2b-256 6135c19d368b6db1c942591f3e819abbf7d6b21dc1924be17b603b9c692156d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for odoo_db-1.22.0-py3-none-any.whl:

Publisher: release.yaml on trobz/odoo-db

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.24.0

2 files

1.23.0

2 files

This release

1.22.0 This release

2 files

1.21.0

2 files

1.20.0

2 files

1.19.0

2 files

1.18.0

2 files

1.17.0

2 files

1.16.0

2 files

1.15.0

2 files

1.14.1

2 files

1.14.0

2 files

1.13.0

2 files

1.12.1

2 files

1.12.0

2 files

1.11.1

2 files

1.11.0

2 files

1.10.0

2 files

1.9.2

2 files

1.9.1

2 files

1.9.0

2 files

1.8.0

2 files

1.7.0

2 files

1.6.0

2 files

1.5.0

2 files

1.4.0

2 files

1.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page