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_studiocustomizations), 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 newx_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.