Skip to content

Low-code customization (Studio-style)

Odoo Studio is a visual editor exclusive to Enterprise. The customizations it makes (fields, views, automated actions) are ordinary Odoo records stored in the database. This plugin creates those same records over RPC, so you get the usual Studio results in Community too, without the license.

Honest scope

This covers small, low-code changes: a field, a view tweak, a simple automation. It does not replace serious module development (packaged code, migrations, tests, complex business logic). For that, build a module. See Is it like Odoo Studio?.

New in v1.5.0: these recipes need the builder profile

The default permission profile (standard) treats fields, views and automations as customization: odoo_add_field and odoo_add_automation are not listed, and an odoo_create on ir.ui.view is refused. These recipes need the Permission profile builder (or full), which you set in /plugin → odoo-tools → Configure options; then reconnect the server (/mcp). Use builder on sandboxes and staging only: automations run Python on the Odoo server, so for confidentiality builder equals full. Claude never changes the option or a policy file on its own; if the policy refuses a step, it tells you which profile the step needs and stops. See Permission policy.

Two limits still hold under builder: a field whose name looks like a credential (x_api_token) is withheld from reads, and website.page writes follow the delegation rule to its view (ir.ui.view): see Delegation.

Where the changes live

In the instance/database, exactly like Studio, not as module code in a repository. They survive updates as manual customizations. If you need code in a repository, that is module development and falls outside the scope of this plugin.

1. Add a custom field

Use the odoo_add_field tool. It creates a field with state='manual' and forces the required x_ name prefix.

odoo_add_field {
  "model": "res.partner",
  "name": "loyalty_points",
  "label": "Loyalty points",
  "field_type": "integer"
}

The name loyalty_points becomes x_loyalty_points.

Relational example:

odoo_add_field { "model": "res.partner", "name": "account_manager",
  "label": "Account manager", "field_type": "many2one", "relation": "res.users" }

For a selection field, pass selection as [["a","A"],["b","B"]]. After creating the field, add it to a form or list view (step 2) so users can see it.

2. Adjust a view (inherited)

There is no dedicated tool; use odoo_create on ir.ui.view with an inherited arch so you do not overwrite the base view. First find the view to extend:

odoo_search_read { "model": "ir.ui.view",
  "domain": [["model","=","res.partner"],["type","=","form"]],
  "fields": ["name","xml_id"], "limit": 5 }

Then create an inherited view:

odoo_create { "model": "ir.ui.view", "values": {
  "name": "res.partner.form.x_loyalty",
  "model": "res.partner",
  "inherit_id": <base_view_id>,
  "arch_db": "<xpath expr=\"//field[@name='email']\" position=\"after\"><field name=\"x_loyalty_points\"/></xpath>"
}}

Prefer xpath ... position="after|before|inside|replace" instead of rewriting the whole view.

3. Add an automation

Use odoo_add_automation. The Python runs under Odoo's safe_eval; the tool validates it before sending, which matters above all in Odoo online/SaaS, where arbitrary Python is rejected server-side.

odoo_add_automation {
  "model": "crm.lead",
  "name": "Flag hot leads",
  "trigger": "on_create_or_write",
  "code": "for rec in records:\n    if rec.probability and rec.probability > 80:\n        rec.write({'priority': '3'})"
}

safe_eval rules (enforced by the tool)

The code runs under Odoo's safe_eval. Forbidden: import, def, class, return, with, access to names or attributes that start with an underscore or dunder (__), and attribute assignment (rec.field = value compiles to STORE_ATTR and is rejected; use rec.write({...})). The tool rejects those constructs before sending, with a clear remediation, so you find out locally instead of through a server error.

Available names in that context: env, model, record/records, datetime, dateutil, time, UserError (Warning on Odoo 10-12). To return an action, assign action = {...} as the last statement (never return).

These recipes require an administrator

Creating ir.model.fields, ir.ui.view, base.automation (base.action.rule on Odoo 10) and its ir.actions.server requires technical permissions (Settings / Technical). With the least-privilege business user recommended in Security every recipe fails with AccessError: run these recipes as an administrator. Besides the Odoo right, the plugin's permission policy must be at builder or full.

Enterprise vs Community

  • The fields, views and automations created here work in both editions.
  • In Enterprise they will not appear inside the Studio editor (they are not tagged as web_studio customizations), but they behave the same at runtime.
  • The models exclusive to Enterprise (for example documents.document) still cannot be used in Community: the compatibility layer raises a clear error.

Verify (definition of done)

Read the record back and confirm the effect:

  • Field: odoo_fields_get { "model": "<model>" } shows the new x_ field.
  • View: open the form in Odoo (or read the inherited ir.ui.view).
  • Automation: trigger it on a record and read the modified field.

Skill reference: /odoo-tools:odoo-studio-style.