Quick Answer
D2C support teams waste 1.5 hours/day clicking through menus to check loyalty points, reset passwords, or view sync logs. Add custom buttons directly to forms: Header Buttons for primary actions, Smart Buttons for related data, Action Menu for bulk operations. One button saves 2 minutes per ticket × 50 tickets = 100 minutes/day.
The Menu-Clicking Problem
Your D2C support team opens a customer record. They need to:
Check Loyalty Points: Click "Sales" → "Loyalty" → Search for customer. (3 clicks)
Send "Reset Password" Email: Go to "Users" → Search user → Action → Send Reset. (4 clicks)
View Recent Shopify Sync Logs: Open a separate logging menu. (3 clicks)
The Result: 2 minutes wasted per ticket navigating menus. With 50 tickets a day, that's 1.5 hours of lost productivity per agent.
The Solution: Add Buttons Directly to the Customer Form
✓ A "Smart Button" showing "500 Points" (Click to view history)
✓ A "Header Button" labeled "Reset Password" (One-click action)
✓ An "Action Menu" item for "Fetch Shopify Logs"
We've implemented 150+ Odoo systems. The interface isn't just about data entry; it's about action. The most efficient teams don't leave the record they are working on. They have big, obvious buttons that do the work for them.
Type 1: Header Buttons (Workflow Actions)
Location: Top left of the form view, inside the <header> tag.
Use Case: Major state changes or primary actions (Confirm, Cancel, Send Email, Approve).
XML Structure
You inject the button into the existing header using xpath.
<!-- views/sale_order_view.xml -->
<record id="view_order_form_inherit_buttons" model="ir.ui.view">
<field name="name">sale.order.form.buttons</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- Add button to the header -->
<xpath expr="//header" position="inside">
<!-- Button calls a Python method -->
<button name="action_check_fraud"
string="Check Fraud Score"
type="object"
class="oe_highlight"
states="draft,sent"
groups="sales_team.group_sale_manager"/>
<!-- Button calls a Window Action (open a view) -->
<button name="%(action_view_fraud_logs)d"
string="View Logs"
type="action"
class="btn-secondary"/>
</xpath>
</field>
</record>
Attributes Explained
| Attribute | Description |
|---|---|
| name | The name of the Python method (if type="object") or the XML ID of the action (if type="action") |
| string | The label users see |
| type | object: Executes Python code | action: Opens a view/window/wizard directly |
| class="oe_highlight" | Makes the button primary (purple/blue). Use sparingly—only one per view usually |
| states | Only visible when the record is in these states (e.g., "draft,sent") |
| groups | Only visible to specific user groups |
Python Logic
# models/sale_order.py
class SaleOrder(models.Model):
_inherit = 'sale.order'
def action_check_fraud(self):
for order in self:
# Logic to check fraud
score = self._call_fraud_api(order)
if score > 80:
order.message_post(body=f"High Fraud Risk! Score: {score}")
else:
order.message_post(body="Fraud Check Passed.")
return True
Type 2: Smart Buttons (Stat Buttons)
Location: Top right of the form sheet (<div class="oe_button_box">).
Use Case: Quick insights and navigation to related records (e.g., "5 Invoices", "3 Tickets", "Active Subscription").
XML Structure
Smart buttons are actually just buttons with specific styling and an icon.
<record id="view_partner_form_inherit_smart" model="ir.ui.view">
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button"
type="object"
name="action_view_loyalty_history"
icon="fa-star">
<!-- Display a value and a label -->
<field string="Points" name="loyalty_points" widget="statinfo"/>
</button>
</div>
</field>
</record>
Python Logic (The "Go To" Action)
Smart buttons usually navigate to a filtered list view.
# models/res_partner.py
class ResPartner(models.Model):
_inherit = 'res.partner'
loyalty_points = fields.Integer(compute='_compute_points')
def action_view_loyalty_history(self):
self.ensure_one()
return {
'name': 'Loyalty History',
'type': 'ir.actions.act_window',
'res_model': 'loyalty.transaction',
'view_mode': 'tree,form',
# Filter the list to show only THIS partner's transactions
'domain': [('partner_id', '=', self.id)],
'context': {'default_partner_id': self.id}, # Auto-fill partner if creating new
}
Why Smart Buttons Rule
They act as both a Report (showing the "500 Points" immediately) and Navigation (one click to see details). They de-clutter the main form while keeping data accessible.
Type 3: Action Menu Items ("Action" Dropdown)
Location: The "Action" gear/cog menu at the top center of the screen.
Use Case: Bulk operations, wizards, or rarely used tools (e.g., "Export to CSV", "Mass Update", "Merge").
XML Structure
These are defined as ir.actions.server records linked to the model.
<!-- data/server_actions.xml -->
<odoo>
<record id="action_mass_mark_priority" model="ir.actions.server">
<field name="name">Mark as Urgent</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_view_types">list,form</field>
<field name="state">code</field>
<field name="code">
# 'records' is the variable containing selected records
records.write({'priority': '1'})
</field>
</record>
</odoo>
Attributes Explained
| Attribute | Description |
|---|---|
| binding_model_id | This binds the action to the model, making it appear in the "Action" menu |
| binding_view_types | list (show in list view checkboxes) or form (show inside a record) or both |
| code | You can write Python directly in the XML (great for simple updates) or call a method |
Real D2C Use Case
You select 50 orders in the list view. Click Action → Print Shipping Labels.
A server action triggers the API calls for all 50 orders and returns a single PDF with all labels merged.
Advanced: Client Actions (Running JavaScript)
Sometimes you need to do something browser-side, like opening a specific URL or triggering a file download.
def action_open_shopify(self):
self.ensure_one()
return {
'type': 'ir.actions.act_url',
'url': f'https://admin.shopify.com/store/my-store/orders/{self.shopify_order_id}',
'target': 'new', # Open in new tab
}
This is incredibly useful for support teams. Add a button "Open in Shopify" to the Odoo order form. One click takes them straight to the source.
Best Practices for Custom Buttons
✓ Don't Overcrowd: If you have more than 3 custom buttons in the header, move the less critical ones to the "Action" menu or a "More Options" dropdown.
✓ Use ensure_one(): In type="object" methods, always call self.ensure_one() if the logic is meant for a single record. Smart buttons often crash if clicked when multiple records are selected.
✓ Permissions: Always use the groups attribute. A "Refund" button should not be visible to the junior sales rep.
✓ Feedback: If a button runs a background process (like "Fetch Data"), use message_post to log the result in the chatter so the user knows it worked.
Button Types Comparison
| Button Type | Location | Best For | Example |
|---|---|---|---|
| Header Button | Top left (inside <header>) | Primary workflow actions | "Confirm", "Send Email", "Approve" |
| Smart Button | Top right (button_box) | Related data & quick navigation | "5 Invoices", "500 Points" |
| Action Menu | Action dropdown (top center) | Bulk operations & rare tools | "Export CSV", "Mass Update" |
Action Items: Audit & Build Your Buttons
Audit Your Forms
❏ Open your most-used record (e.g., Customer or Order)
❏ Identify data you frequently search for in other menus (e.g., "How many tickets does this guy have?")
❏ Identify actions you perform repeatedly (e.g., "Copy address to clipboard")
Build the Buttons
❏ Add a Smart Button for the related data
❏ Add a Header Button for the primary action
❏ Add an Action Menu item for bulk operations
Frequently Asked Questions
What's the difference between Header Buttons and Smart Buttons in Odoo?
Header Buttons (top left) trigger workflow actions like "Confirm" or "Send Email". Smart Buttons (top right) display related data counts ("5 Invoices") and navigate to filtered views. Header = action, Smart = navigation.
How do I add a button that calls a Python method in Odoo?
In your XML view, add <button name="method_name" type="object"/>. In your Python model, define def method_name(self): with your logic. Use self.ensure_one() for single-record methods.
Can I add buttons that only specific user groups can see?
Yes. Use the groups attribute: <button groups="sales_team.group_sale_manager"/>. Only users in that group will see the button. Critical for permissions control.
How much time can custom buttons save my support team?
One custom button saves 2 minutes per ticket. With 50 tickets/day, that's 100 minutes saved daily (1.5+ hours). Over a month, that's 33 hours recovered per agent.
Free UI/UX Customization Session
Stop clicking through endless menus. We'll analyze your team's click-paths, design custom Smart Buttons to surface hidden data, implement "One-Click" workflow buttons, and clean up your interface to focus on what matters. A good interface isn't just pretty; it's fast.
