How Odoo Loads XML Files
When you install or upgrade a module, Odoo processes every XML file listed in __manifest__.py in order. Understanding this loading process helps you debug missing record errors and ensure correct load order.
The loading sequence:
Module install/upgrade triggered
↓
Odoo reads __manifest__.py → gets ordered list of data files
↓
For each file (in order):
1. lxml parses the XML → XMLSyntaxError if invalid
2. Odoo walks each <record> element
3. Resolves ref="..." to integer IDs
4. Evaluates eval="..." Python expressions
5. Creates or updates the ir.model.data row (XML ID registry)
6. Creates or updates the target model row
↓
Module loaded
Python code showing what happens internally (simplified):
import lxml.etree as etree
with open('views/book_views.xml', 'rb') as f:
tree = etree.parse(f) # Step 1 — parse XML
root = tree.getroot() # <odoo> element
for record_el in root.iter('record'):
model_name = record_el.get('model') # e.g. 'ir.ui.view'
xml_id = record_el.get('id') # e.g. 'view_book_form'
# Odoo creates/updates the row, registers the XML ID
Common load-time errors:
| Error | Cause |
|---|---|
XMLSyntaxError: Opening and ending tag mismatch | Missing or wrong closing tag |
XMLSyntaxError: EntityRef: expecting ';' | Unescaped & — use & |
XMLSyntaxError: attributes construct error | Unquoted attribute value |
ValueError: External ID not found in the system | ref="module.id" points to a record not yet loaded |
KeyError: 'field_name' | Field declared in view doesn't exist on the model |
Load order is critical. Odoo processes manifest data files top-to-bottom. A file that ref=-references a record from a later file will fail. Safe order:
'data': [
'security/ir.model.access.csv', # 1. security first (models must exist)
'views/book_views.xml', # 2. views
'views/actions.xml', # 3. actions (may reference views)
'views/menus.xml', # 4. menus (reference actions)
'data/book_config.xml', # 5. configuration data last
],
Data and Demo Files
{
'name': 'Library Management',
'data': [
'security/ir.model.access.csv',
'views/book_views.xml',
'views/menus.xml',
'data/mail_template.xml',
'report/book_report.xml',
],
'demo': [
'demo/demo_books.xml',
'demo/demo_members.xml',
],
}
data/ — loaded on every module install AND every upgrade. Use for: views, menus, actions, security, email templates, sequences, default configuration.
demo/ — loaded ONLY when the database was created with demo data enabled. Use for: sample records for exploration and testing. Never rely on demo data for module functionality.
noupdate="1" — wrap records that users are expected to modify:
<odoo>
<!-- Updated on every upgrade (default) -->
<record id="view_book_form" model="ir.ui.view">
...
</record>
<!-- Created once, never overwritten on upgrade -->
<data noupdate="1">
<record id="sequence_library_book" model="ir.sequence">
<field name="name">Library Book Sequence</field>
<field name="code">library.book</field>
<field name="prefix">LIB/%(year)s/</field>
<field name="padding">4</field>
</record>
<record id="mail_template_overdue" model="mail.template">
<field name="name">Book Overdue Notice</field>
...
</record>
</data>
</odoo>
Use noupdate="1" for: email templates (users customise wording), sequences (users set numbering), default configuration values. Do NOT use it for: access rights, record rules, view definitions (those must update).
External IDs (XML IDs)
Every <record> element has an id attribute — its external identifier. Odoo stores these in ir.model.data and resolves them to database integer IDs at load time. This lets you reference records without hardcoding IDs that differ between installations.
<!-- Define an external ID -->
<record id="action_library_book" model="ir.actions.act_window">
<field name="name">Books</field>
<field name="res_model">library.book</field>
<field name="view_mode">list,form</field>
</record>
<!-- Reference within same module (no prefix needed) -->
<menuitem id="menu_library_books"
name="Books"
action="action_library_book"/>
<!-- Reference from another module (prefix required) -->
<menuitem id="menu_ext_books"
action="library_management.action_library_book"/>
Python access:
# Get the record object
action = self.env.ref('library_management.action_library_book')
# Get just the integer ID
action_id = self.env.ref('library_management.action_library_book').id
# Safe lookup (returns None if not found instead of raising)
action = self.env.ref('library_management.action_library_book', raise_if_not_found=False)
ref vs eval attributes:
<!-- ref: resolves XML ID to integer ID, assigns to Many2one field -->
<field name="parent_id" ref="base.group_user"/>
<!-- eval: evaluates a Python expression at load time -->
<field name="active" eval="True"/>
<field name="groups_id" eval="[(4, ref('base.group_user'))]"/>
<field name="date" eval="(datetime.date.today()).strftime('%Y-%m-%d')"/>
<delete> tag — remove a record during module upgrade:
<delete id="old_menu_item" model="ir.ui.menu"/>
<delete model="ir.ui.menu" search="[('name', '=', 'Legacy Menu')]"/>
Naming conventions:
| Record type | Convention | Example |
|---|---|---|
| Form view | view_{model}_form | view_library_book_form |
| List view | view_{model}_list | view_library_book_list |
| Kanban view | view_{model}_kanban | view_library_book_kanban |
| Search view | view_{model}_search | view_library_book_search |
| Window action | action_{model} | action_library_book |
| Root menu | menu_{module}_root | menu_library_root |
| Sub-menu | menu_{model} | menu_library_books |
| Email template | mail_template_{name} | mail_template_book_overdue |
Odoo View Types Overview
All Odoo views are <record> elements with model="ir.ui.view". The arch field holds the view XML. The model field names which Odoo model the view belongs to.
| View type | <arch> tag | Purpose |
|---|---|---|
| Form | <form> | View and edit one record |
| List | <list> (was <tree>) | Browse many records in a table |
| Kanban | <kanban> | Card-based board, ideal for pipelines |
| Search | <search> | Filters, group-by options, search panel |
| Pivot | <pivot> | Spreadsheet-style aggregation |
| Graph | <graph> | Bar, line, or pie chart |
| Calendar | <calendar> | Records plotted on a date/time calendar |
| Activity | <activity> | Activity schedule per record |
A window action controls which views are available and in what order:
<record id="action_library_book" model="ir.actions.act_window">
<field name="name">Books</field>
<field name="res_model">library.book</field>
<field name="view_mode">list,kanban,form</field>
<!-- Optional: set a default domain or context -->
<field name="domain">[]</field>
<field name="context">{'search_default_active': 1}</field>
</record>
Form View
The form view is the most complex view — it's the full-screen editor for a single record. Annotated structure:
<record id="view_library_book_form" model="ir.ui.view">
<field name="name">library.book.form</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<form string="Book">
<!-- HEADER: action buttons + state progression bar -->
<header>
<button name="action_confirm" string="Confirm"
type="object" class="oe_highlight"
invisible="state != 'draft'"/>
<button name="action_return" string="Return"
type="object"
invisible="state != 'borrowed'"/>
<button name="action_cancel" string="Cancel"
type="object"
invisible="state in ['cancelled', 'returned']"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,confirmed,borrowed,returned"/>
</header>
<!-- SHEET: main white content card -->
<sheet>
<!-- Title row -->
<div class="oe_title">
<h1>
<field name="name" placeholder="Book title…"/>
</h1>
</div>
<!-- Two-column group -->
<group>
<group string="Book Details" name="book_details">
<field name="author_id"/>
<field name="isbn"/>
<field name="publisher_id"/>
<field name="publish_date"/>
</group>
<group string="Library Info" name="library_info">
<field name="category_id"/>
<field name="available_copies"/>
<field name="price" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="currency_id" invisible="1"/>
</group>
</group>
<!-- One2many embedded list (book copies/lines) -->
<field name="copy_ids" string="Copies">
<list editable="bottom">
<field name="barcode"/>
<field name="location_id"/>
<field name="state"/>
</list>
</field>
<!-- Tabbed secondary content -->
<notebook>
<page string="Description" name="description">
<field name="description" widget="html"/>
</page>
<page string="Tags" name="tags">
<field name="tag_ids" widget="many2many_tags"/>
</page>
</notebook>
</sheet>
<!-- CHATTER: messages, activities, followers -->
<!-- Requires mail.thread + mail.activity.mixin on the model -->
<chatter/>
</form>
</field>
</record>
Conditional attributes — control visibility, editability, and required-ness based on field values:
<!-- invisible: hide based on record state (client-side) -->
<field name="return_date" invisible="state != 'borrowed'"/>
<!-- readonly: make non-editable in certain states -->
<field name="author_id" readonly="state != 'draft'"/>
<!-- required: enforce a value in certain states -->
<field name="return_reason" required="state == 'cancelled'"/>
<!-- Combining: invisible on a button -->
<button name="action_borrow" string="Borrow"
type="object"
invisible="state != 'confirmed' or available_copies == 0"/>
Common widgets:
| Widget | Field types | Effect |
|---|---|---|
statusbar | Selection/Many2one | Progress bar showing stages |
priority | Selection (0-3) | Star rating |
state_selection | Selection | Kanban state traffic light (grey/green/red) |
many2many_tags | Many2many | Pill-style coloured tags |
many2one_avatar | Many2one (res.users) | Avatar image next to name |
html | Html/Text | Rich text WYSIWYG editor |
monetary | Float | Formatted currency with currency symbol |
handle | Integer | Drag handle for manual reordering in lists |
binary | Binary | File upload/download button |
color_picker | Integer | Color selector (0–11 Odoo palette) |
badge | Selection/Char | Coloured badge instead of plain text |
List View
<record id="view_library_book_list" model="ir.ui.view">
<field name="name">library.book.list</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<list string="Books"
decoration-danger="available_copies == 0"
decoration-warning="available_copies < 3"
decoration-muted="active == False"
multi_edit="1"
default_order="name asc">
<field name="name"/>
<field name="author_id"/>
<field name="category_id" optional="show"/>
<field name="available_copies"
decoration-danger="available_copies == 0"
decoration-warning="available_copies < 3"
sum="Total Copies"/>
<field name="price" sum="Total Value" optional="hide"/>
<field name="state" widget="badge"
decoration-success="state == 'available'"
decoration-warning="state == 'borrowed'"
decoration-danger="state == 'lost'"/>
<field name="publish_date" optional="hide"/>
<!-- Inline action button -->
<button name="action_quick_borrow" string="Borrow"
type="object" icon="fa-book"
invisible="state != 'available'"/>
</list>
</field>
</record>
Row decoration — color the entire row:
| Attribute | Row colour |
|---|---|
decoration-danger | Red |
decoration-warning | Yellow/amber |
decoration-success | Green |
decoration-info | Blue |
decoration-muted | Grey/faded |
decoration-bf | Bold |
decoration-it | Italic |
The expression uses Python syntax. Field names reference the current row's values.
Key list attributes:
multi_edit="1"— select multiple rows, edit one field, apply the change to all selected rowseditable="bottom"oreditable="top"— enable inline editing without opening a form view; new rows added at bottom or topoptional="show"— column is visible by default but user can hide it;optional="hide"— hidden by defaultsum="Label"/avg="Label"on numeric<field>— shows aggregate in footer
Kanban View
<record id="view_library_book_kanban" model="ir.ui.view">
<field name="name">library.book.kanban</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<kanban default_group_by="category_id"
class="o_kanban_small_column">
<!-- Declare all fields used in the template -->
<field name="name"/>
<field name="author_id"/>
<field name="category_id"/>
<field name="available_copies"/>
<field name="state"/>
<field name="priority"/>
<field name="kanban_state"/>
<field name="color"/>
<!-- Column-top progress bar (shows state distribution) -->
<progressbar field="state"
colors='{"available": "success", "borrowed": "warning", "lost": "danger"}'/>
<!-- Card template (Odoo 17+: t-name="card") -->
<templates>
<t t-name="card">
<div t-attf-class="oe_kanban_color_#{record.color.raw_value}">
<div class="oe_kanban_details">
<strong class="o_kanban_record_title">
<field name="name"/>
</strong>
<div class="o_kanban_record_body">
<span class="text-muted">
<field name="author_id"/>
</span>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<field name="priority" widget="priority"/>
<span t-if="record.available_copies.raw_value > 0"
class="badge text-bg-success">
<t t-esc="record.available_copies.value"/> available
</span>
</div>
<div class="oe_kanban_bottom_right">
<field name="kanban_state"
widget="state_selection"/>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
Key concepts:
default_group_by="field_name"— groups cards into columns by a Many2one or Selection field<progressbar field="...">— coloured bar at the top of each column based on a field's distributiont-name="card"— the card template (Odoo 17+). Older:t-name="kanban-box"record.field_name.raw_value— the raw database value (integer, string, boolean)record.field_name.value— the formatted display stringt-attf-class="literal #{expr}"— string template for dynamic CSS class names
Search View
The search view does not display data — it defines what appears in the search bar (filter buttons, group-by options, facets).
<record id="view_library_book_search" model="ir.ui.view">
<field name="name">library.book.search</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<search string="Search Books">
<!-- Fields searched when user types in the search bar -->
<field name="name" string="Title"/>
<field name="author_id" string="Author"/>
<field name="isbn"/>
<field name="tag_ids" string="Tag"
filter_domain="[('tag_ids.name', 'ilike', self)]"/>
<separator/>
<!-- One-click filter buttons -->
<filter string="Available" name="available"
domain="[('state', '=', 'available')]"/>
<filter string="Borrowed" name="borrowed"
domain="[('state', '=', 'borrowed')]"/>
<filter string="My Borrowings" name="my_books"
domain="[('borrower_id', '=', uid)]"/>
<!-- Date filter (Odoo generates This Week / This Month etc.) -->
<filter string="Published Date" name="published"
date="publish_date"/>
<separator/>
<!-- Group-by options -->
<group expand="0" string="Group By">
<filter string="Category" name="group_category"
context="{'group_by': 'category_id'}"/>
<filter string="Author" name="group_author"
context="{'group_by': 'author_id'}"/>
<filter string="State" name="group_state"
context="{'group_by': 'state'}"/>
<filter string="Publish Month" name="group_month"
context="{'group_by': 'publish_date:month'}"/>
</group>
<!-- Search panel: sidebar with category filters -->
<searchpanel>
<field name="category_id" select="one"
icon="fa-book" string="Category"/>
<field name="state" select="multi"
icon="fa-circle" string="Status"/>
</searchpanel>
</search>
</field>
</record>
Key search view elements:
<field name="...">— adds the field to full-text search. When user types in the bar and selects this field, Odoo searches using anilikedomain by default.filter_domainwithself—selfis replaced with the typed value. Use for cross-field searches.<filter domain="...">— one-click button that applies a domain permanently<filter date="field_name">— Odoo auto-generates "This Week", "This Month", "This Quarter", "This Year" sub-filters<group context="{'group_by': 'field'}">— adds a group-by option; date fields support granularity::day,:week,:month,:quarter,:year<searchpanel>— left sidebar withselect="one"(radio) orselect="multi"(checkboxes)
Activating filters by default — add to the action's context:
<field name="context">{'search_default_available': 1, 'search_default_group_category': 1}</field>
XPath View Inheritance
XPath lets you modify an existing view from another module without editing the original. This is how Odoo's modular architecture works — a sales module can add a field to a base contact form without touching the contacts module.
XPath expression anatomy:
// field [@name='partner_id']
↑ ↑ ↑
| | predicate — filter by attribute value
| element name to match
// = search anywhere in the document
/ = search only immediate children of root
Position values:
| Position | What happens |
|---|---|
after | New XML inserted immediately after the matched element |
before | New XML inserted immediately before the matched element |
inside | New XML appended as last child of the matched element |
replace | Matched element removed and replaced with new XML |
attributes | Attributes of the matched element modified (use <attribute> children) |
Full example — extending the sale order form from a custom module:
<record id="view_sale_order_form_inherit_library" model="ir.ui.view">
<field name="name">sale.order.form.library</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- Add a field after partner_id -->
<xpath expr="//field[@name='partner_id']" position="after">
<field name="library_card_id" string="Library Card"/>
</xpath>
<!-- Add a new page to the notebook -->
<xpath expr="//notebook" position="inside">
<page string="Library Info" name="library_info">
<group>
<field name="borrowed_book_ids" widget="many2many_tags"/>
<field name="library_notes"/>
</group>
</page>
</xpath>
<!-- Make a field invisible -->
<xpath expr="//field[@name='client_order_ref']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<!-- Replace a field with a different one -->
<xpath expr="//field[@name='payment_term_id']" position="replace">
<field name="custom_payment_id" string="Custom Payment"/>
</xpath>
<!-- Add a button to the header -->
<xpath expr="//header/button[last()]" position="after">
<button name="action_library_sync" string="Sync Library"
type="object" icon="fa-refresh"/>
</xpath>
</field>
</record>
Shorthand syntax — for fields and buttons, you can use the element tag directly instead of a full XPath expression:
<!-- Shorthand: fine for simple cases -->
<field name="partner_id" position="after">
<field name="library_card_id"/>
</field>
<!-- Use full XPath when: -->
<!-- 1. The target is not a field (e.g. <group>, <notebook>, <page>, <div>) -->
<!-- 2. The field name appears multiple times in the view -->
<!-- 3. You need positional selectors like [last()] or [1] -->
Always give your <group>, <page>, and <notebook> elements a name attribute. This makes them targetable with a stable XPath: //group[@name='billing'] instead of //sheet/group[2] (which breaks if any module adds a group earlier).
XML Validation
Well-formed vs valid:
- Well-formed — follows XML syntax rules (tags close, attributes quoted, one root, correct nesting). Required — the module won't load if XML is malformed.
- Valid — well-formed AND conforms to a schema defining which elements are allowed and where. Odoo validates view XML against RNG schema files in
odoo/addons/base/views/.
Run Odoo with XML validation in development:
# Enables strict view validation (slower but catches schema violations)
python odoo-bin -c odoo.conf --dev=xml
# Upgrade a module with validation
python odoo-bin -c odoo.conf -u my_module --dev=xml
Check XML syntax locally before loading (catch errors fast without restarting Odoo):
# Install xmllint
sudo apt install libxml2-utils # Ubuntu/Debian
# Validate a file (no output = valid; error shows file:line)
xmllint --noout views/book_views.xml
# Example error output:
# views/book_views.xml:23: parser error : Opening and ending tag mismatch
Common XML errors and fixes:
<!-- ERROR: unescaped & in domain -->
<filter domain="[('amount', '>', 100) and ('state', '=', 'open')]"/>
<!-- FIX: use & -->
<filter domain="[('amount', '>', 100)]"/>
<!-- ERROR: self-closing tag not closed -->
<field name="name">
<!-- FIX: self-close or add closing tag -->
<field name="name"/>
<!-- ERROR: missing closing tag -->
<group>
<field name="name"/>
<field name="email"/> <!-- This is outside the group! -->
<!-- FIX -->
<group>
<field name="name"/>
</group>
<field name="email"/>
QWeb — Odoo's XML Template Engine
QWeb is Odoo's template engine built on top of XML. It adds t-* directive attributes to standard HTML elements to produce dynamic output. QWeb is used for PDF reports, portal pages, website pages, and email templates — NOT for the main OWL-based UI views (form, list, kanban).
| Directive | Purpose |
|---|---|
t-esc="expr" | Output HTML-escaped value — safe for user data |
t-raw="expr" | Output raw HTML — only for trusted content |
t-if="condition" | Render element only if condition is true |
t-elif="condition" | Else-if branch |
t-else | Else branch |
t-foreach="list" t-as="item" | Loop; also gives item_index, item_size, item_first, item_last |
t-call="template.xmlid" | Include another template (like a function call) |
t-set="var" t-value="expr" | Assign a variable |
t-att-href="expr" | Dynamic attribute value |
t-attf-class="literal #{expr}" | String-template for mixed literal/dynamic attributes |
t-field="record.fieldname" | Render a field with Odoo formatting (currency, date, etc.) |
The <t> element is an invisible wrapper — it applies directives without adding any element to the output:
<!-- t-foreach without adding a wrapper div -->
<t t-foreach="order.line_ids" t-as="line">
<tr>
<td><t t-esc="line.product_id.name"/></td>
<td><t t-esc="line.quantity"/></td>
<td><t t-field="line.price_subtotal"/></td>
</tr>
</t>
Minimal PDF report template:
<template id="report_book_document">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<div class="page">
<div class="row">
<div class="col-6">
<h2><t t-esc="doc.name"/></h2>
<p>Author: <t t-field="doc.author_id"/></p>
</div>
<div class="col-6 text-end">
<p t-if="doc.isbn">ISBN: <t t-esc="doc.isbn"/></p>
</div>
</div>
<table class="table table-sm">
<thead>
<tr>
<th>Copy #</th>
<th>Location</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.copy_ids" t-as="copy">
<tr>
<td><t t-esc="copy.barcode"/></td>
<td><t t-esc="copy.location_id.name"/></td>
<td><t t-esc="copy.state"/></td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</t>
</template>
Registering the report action:
<record id="action_report_book" model="ir.actions.report">
<field name="name">Book Card</field>
<field name="model">library.book</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">library_management.report_book_document</field>
<field name="binding_model_id" ref="model_library_book"/>
</record>
QWeb is separate from OWL's template syntax. OWL also uses t-* directives but operates on JavaScript objects in the browser. QWeb runs on the Python server side and produces an HTML string (for portal/website/email) or a PDF (for reports). Don't confuse the two.
📋 Key Points
- Odoo loads XML files top-to-bottom in
__manifest__.pyorder. Aref=pointing to a record in a later file causes "External ID not found" — always load security before views, views before actions, actions before menus. data/files load on every install+upgrade.demo/files load only when demo data is enabled. Usenoupdate="1"for records users are expected to modify.- External IDs let you reference records reliably with
ref="module.id"in XML andself.env.ref()in Python, without hardcoding database integer IDs. - Odoo has 8+ view types: form (single record), list (table), kanban (cards), search (filters/group-by), plus pivot, graph, calendar, activity.
- Form view:
<header>for buttons+statusbar,<sheet>for content,<group>for columns,<notebook>for tabs,<chatter/>for messaging. Useinvisible/readonly/requiredwith Python expressions. - List view:
decoration-*colors rows,sum/avgfor footer aggregates,optionalfor user-toggleable columns,multi_editfor bulk changes. - Kanban view: group by Many2one column,
<progressbar>for column status bars, card template int-name="card". - Search view:
<field>for full-text search,<filter>for quick filters,<group>for group-by options,<searchpanel>for sidebar categories. - XPath inheritance:
<inherit_id ref="module.view_id">+<xpath expr="..." position="...">modifies any existing view without touching the original module. - QWeb:
t-*directives on XML elements produce dynamic HTML/PDF output — used for reports, portal pages, website, and email templates (not for form/list/kanban UI).
FAQ
<tree> and <list> in Odoo views? +They are the same view type. Odoo renamed <tree> to <list> in Odoo 17 for semantic clarity. Both tags still work in Odoo 17+. Any module you find using <tree> is targeting Odoo 16 or earlier, but the tag still parses correctly.
noupdate="1" on data records? +Use it for records users will customise after installation — email templates (they'll change the wording), sequences (they'll change the numbering prefix), and default company settings. Avoid it for views, access rights, and server actions — those must be updatable so security fixes and feature changes apply correctly on module upgrade.
Three common reasons: (1) the element doesn't have the attribute you're filtering on — the field might be named differently in the target view; (2) the element appears inside a sub-template that's rendered separately; (3) another module's inherited view changed the structure between the original view and your xpath. Use developer mode's "Edit View" feature (Settings > Technical > Views) to inspect the compiled arch before writing your XPath.
docs in a QWeb report template? +docs is a standard variable automatically passed to every QWeb report template. It is a recordset of the records being printed — for example, if you print 3 sale orders, docs is a recordset of those 3 orders. The t-foreach="docs" t-as="doc" loop iterates over each one to produce one page per record.
<searchpanel> to a list view? +Yes — the search view defines the searchpanel and it works with both list and kanban views. It does not work with form, pivot, or graph views. The select="one" option gives radio buttons (filter by one value at a time); select="multi" gives checkboxes (filter by multiple values simultaneously).