Skip to content

Odoo 17 attrs Removal: Complete Migration Guide

DeployMonkey Team · March 22, 2026 10 min read

What Changed

In Odoo 17, the attrs attribute was completely removed from XML views. This was the single biggest breaking change in years because attrs was used everywhere — in virtually every custom module and every view customization.

Before (Odoo 16 and earlier)

<field name="discount"
       attrs="{'invisible': [('state', '!=', 'draft')],
              'readonly': [('state', '==', 'done')],
              'required': [('type', '=', 'service')]}"/>

After (Odoo 17+)

<field name="discount"
       invisible="state != 'draft'"
       readonly="state == 'done'"
       required="type == 'service'"/>

Key Differences

AspectOld (attrs)New (direct)
SyntaxPython domain: [('field', '=', 'value')]Python expression: field == 'value'
AND[('a', '=', 1), ('b', '=', 2)]a == 1 and b == 2
OR['|', ('a', '=', 1), ('b', '=', 2)]a == 1 or b == 2
NOT[('field', '!=', True)]not field
IN[('state', 'in', ['a', 'b'])]state in ('a', 'b')
Boolean[('active', '=', True)]active
Negation[('active', '=', False)]not active

Migration Examples

Simple invisible

<!-- Old -->
<field name="notes" attrs="{'invisible': [('state', '!=', 'draft')]}"/>

<!-- New -->
<field name="notes" invisible="state != 'draft'"/>

Multiple conditions (AND)

<!-- Old -->
<field name="discount" attrs="{'invisible': [('state', '!=', 'draft'), ('is_manager', '=', False)]}"/>

<!-- New -->
<field name="discount" invisible="state != 'draft' and not is_manager"/>

OR conditions

<!-- Old -->
<field name="notes" attrs="{'invisible': ['|', ('state', '=', 'done'), ('state', '=', 'cancel')]}"/>

<!-- New -->
<field name="notes" invisible="state == 'done' or state == 'cancel'"/>

<!-- Or more concisely -->
<field name="notes" invisible="state in ('done', 'cancel')"/>

Combined invisible + readonly

<!-- Old -->
<field name="partner_id"
       attrs="{'invisible': [('type', '=', 'internal')],
              'readonly': [('state', '!=', 'draft')]}"/>

<!-- New -->
<field name="partner_id"
       invisible="type == 'internal'"
       readonly="state != 'draft'"/>

On groups/pages/buttons

<!-- Old -->
<group attrs="{'invisible': [('state', '=', 'draft')]}">
    ...
</group>

<!-- New -->
<group invisible="state == 'draft'">
    ...
</group>

<!-- Buttons -->
<button name="action_confirm" string="Confirm"
        invisible="state != 'draft'"/>

Automated Migration

Claude Code can migrate entire modules automatically:

# Prompt:
"Migrate all attrs= attributes in my module's XML views
from Odoo 16 domain syntax to Odoo 17 direct attribute syntax.
Convert invisible, readonly, and required attrs."

Claude Code reads all XML files, identifies every attrs= usage, converts the domain syntax to expression syntax, and replaces them — across all files in one pass.

Common Mistakes

  • Forgetting to convert OR operators'|' prefix in domains becomes or in expressions
  • Wrong boolean syntax[('field', '=', True)] becomes field (not field == True)
  • String quoting — Use single quotes inside double-quoted attribute: invisible="state == 'draft'"
  • Inherited views — If you inherit a core view that had attrs, your xpath might also need updating

Getting Started

If you are still on Odoo 16, plan your migration now. The attrs change affects every custom module. Use Claude Code for automated migration and test on a DeployMonkey staging instance before applying to production.