Skip to content

Odoo sudo() vs with_user() Guide: Security Context Switching

DeployMonkey Team · March 24, 2026 10 min read

Security Contexts in Odoo

Odoo's ORM enforces access controls based on the current user. Sometimes you need to bypass these controls (sudo) or switch to a different user's context (with_user). Understanding the difference — and the security implications — is critical for writing correct and secure Odoo code.

sudo() — Superuser Mode

What It Does

sudo() switches the recordset's environment to the superuser (SUPERUSER_ID = 1), bypassing all access rules, record rules, and field-level security:

# Regular access — respects user's permissions
partners = self.env['res.partner'].search([])

# Sudo access — sees all records, ignores access rules
all_partners = self.env['res.partner'].sudo().search([])

When to Use sudo()

  • Reading configuration data the current user should not directly access
  • Creating records in models the user has no create access to (e.g., creating log entries)
  • Automated actions that need full access (cron jobs, server actions)
  • Cross-model operations where the user has access to model A but not model B
  • Counting or aggregating data for display without exposing individual records

Common Patterns

# Read config parameter (users normally can't read ir.config_parameter)
api_key = self.env['ir.config_parameter'].sudo().get_param('my.api_key')

# Create audit log entry (user may not have create access)
self.env['my.audit.log'].sudo().create({
    'action': 'login',
    'user_id': self.env.uid,
})

# Search across companies
all_orders = self.env['sale.order'].sudo().search([
    ('state', '=', 'sale'),
])

with_user() — User Impersonation

What It Does

with_user(user) switches the environment to a specific user, applying that user's access rights, record rules, and company context:

# Execute as a specific user
user = self.env['res.users'].browse(target_user_id)
records = self.env['sale.order'].with_user(user).search([])
# Only returns records this user has access to

When to Use with_user()

  • Testing what a specific user can see or do
  • Creating records that should appear as authored by a specific user
  • Processing tasks on behalf of another user
  • Preserving correct authorship in chatter messages and tracking

Common Patterns

# Create sale order as the salesperson (not the admin running the script)
order = self.env['sale.order'].with_user(salesperson).create({
    'partner_id': customer.id,
})

# Post message as the assigned user (not sudo superuser)
record.with_user(assigned_user).message_post(
    body='Task completed.',
)

sudo() vs with_user() Comparison

Aspectsudo()with_user(user)
User contextSUPERUSER_ID (1)Specified user
Access rulesBypassed entirelyApplied for target user
Record rulesBypassed entirelyApplied for target user
Company contextKeeps original companySwitches to user's company
Chatter authorShows as OdooBot/SuperuserShows as target user
Use caseBypass securityAct as another user

Security Best Practices

Minimize sudo() Scope

# BAD — entire method runs as sudo
def process_order(self):
    order = self.sudo()
    order.write({'state': 'confirmed'})
    order.create_invoice()
    order.send_email()
    # Everything bypasses security!

# GOOD — sudo only where needed
def process_order(self):
    self.write({'state': 'confirmed'})
    # Only the log entry needs sudo
    self.env['my.audit.log'].sudo().create({
        'action': 'confirm',
        'order_id': self.id,
    })
    self.create_invoice()  # Normal security
    self.send_email()  # Normal security

Never Return sudo() Records to the User

# BAD — user gets sudo access to the partner
def get_partner(self):
    return self.env['res.partner'].sudo().browse(partner_id)

# GOOD — read the data you need, return without sudo
def get_partner_name(self):
    partner = self.env['res.partner'].sudo().browse(partner_id)
    return partner.name  # Return the value, not the record

Use SUPERUSER_ID for Sale Order Creation

Odoo requires SUPERUSER_ID for certain operations like sale order creation with specific pricing:

from odoo import SUPERUSER_ID

order = self.env['sale.order'].with_user(SUPERUSER_ID).create(vals)

Note: env(user=SUPERUSER_ID) and sudo() are similar but sudo() is the modern API.

Common Pitfalls

  • sudo() leaking — sudo() returns a new recordset. If you store it and pass it around, downstream code runs with elevated privileges unintentionally.
  • Tracking with sudo() — Field tracking under sudo() shows changes as made by Superuser/OdooBot. Use with_user() to preserve the actual user.
  • sudo() in controllers — Extremely dangerous. A public controller using sudo() can expose data to unauthenticated users. Always authenticate first.
  • Forgetting to sudo() — AccessError in automated actions usually means you need sudo(). But audit whether the access is legitimate before adding it.