ErrLookup › Background articles › UserError: Odoo's user-facing guard exception, and its Supabase and n8n variants

UserError: Odoo's user-facing guard exception, and its Supabase and n8n variants

UserError is a deliberately thrown, user-facing exception that means an application-level guard rejected the operation: a business rule, compliance lock, registration conflict, or input validation fired on purpose, and it is not a bug or a crash. Developers meet it most often in Odoo (odoo.exceptions.UserError, raised by constraints, wizards, and Peppol/IAP proxy connectors), and as a small ApplicationError subclass in Supabase edge functions and n8n workflow code, where it surfaces as a translated dialog message, an HTTP 400 with a structured body, or a CLI error.

Distilled from 307 documented records across 3 repositories.

Background

UserError is produced at the application layer and thrown on purpose. It is not a crash: guard code decides an operation must not proceed and raises this class so the caller receives an explainable rejection instead of silent data damage. In Odoo it is the standard user-facing exception (odoo.exceptions.UserError), raised from constraint checks, ondelete guards, wizard computes, write() preconditions, and Peppol/IAP proxy connector handlers. In Supabase's search-embeddings edge function, UserError extends ApplicationError/Error and an outer catch converts it into an HTTP 400 with a structured JSON body carrying the OpenAI moderation categories. In n8n it appears in the CLI import path for ownership checks and in node configuration validation, where one instance is immediately caught and rethrown as a NodeOperationError that carries the original text as its description.

The family exists to protect invariants the system refuses to violate silently. The Odoo records guard legal immutability (hashed journal entries and restricted audit trails that compliance regimes make irreversible), anti-phishing locks on trusted bank accounts, one-registration-per-participant and one-connection-per-service rules on the Peppol network, and the consistency of payments and reversals across companies and journal types. The Supabase record enforces content policy by submitting the query to OpenAI's Moderation API before generating an embedding; the n8n records enforce ownership integrity on workflow import and valid date configuration. Messages are written for end users - translated where the framework supports it, and frequently interpolating the offending fields, entries, or upstream error codes - because the intended remedy is human action, not automatic recovery.

From the caller's side the shape varies by library. An Odoo client sees the transaction rolled back and a message dialog or RPC error string, for example a list of the exact fields and hashed entry names it tried to modify. A Supabase caller receives a 400 response whose body carries the moderation categories that tripped the flag. An n8n CLI user gets the error printed at import time, while in the Chat Trigger path the UserError itself is re-wrapped, so the class a caller observes can differ from the class that was thrown. The exact presentation is therefore library-specific and even call-path-specific.

One split matters for diagnosis: most UserErrors are deterministic guards - retrying the identical input fails identically, whether it is a moderation flag, a format check, or an immutability lock. But several Odoo Peppol records wrap transport-level failures (DNS failure, connection refused, TLS errors, a 10-second timeout, non-JSON responses) or structured upstream error payloads into UserError as well. The class name alone does not say whether a condition is permanent; the interpolated message and any error code must be read first.

Common causes

What usually fixes it

Go deeper

Documented occurrences

…and 287 more across the corpus — use search.

Honest provenance: generated on 2026-08-15 from AI-assisted analysis of the linked records. See how records are made.