Snippets: Reusable Drag-and-Drop Building Blocks
Snippets are the pre-built content blocks (hero banners, pricing tables, image galleries, call-to-action sections) that users drag onto pages in the website editor. Developers can create custom snippets to extend the palette.
| Snippet part | Role |
|---|---|
| Template (QWeb/HTML) | the block's markup |
| Options | editor controls (colors, layout toggles) |
| JS (optional) | interactivity (sliders, counters) |
| Registration | adds it to the editor's snippet menu |
What a snippet is: at heart, a chunk of HTML/QWeb markup registered so it appears in the website editor's drag-and-drop panel. Once dropped, a business user can edit its text and images inline and tweak it via options (editor controls you define — background color, alignment, number of columns) without touching code. Building a custom snippet means three pieces: the template markup, an options definition (the toggles shown when the block is selected), and — if it needs behavior like a carousel or animated counter — a small OWL/JavaScript widget that activates on the public page. You then register it into the snippets menu, optionally under a category. Why they're powerful: a developer builds a snippet once, and non-technical users reuse it across many pages, editing content freely — reusable, on-brand blocks without repeated dev work. Two contexts for the JS: editor-time options (how it's configured) versus public-time behavior (what visitors see); keep them separate. Best practices: make snippets responsive (they'll be used everywhere), keep options simple and meaningful, use semantic accessible markup, and test both in the editor and on the live page. Snippets are the bridge between developer-built components and user-driven content editing.
🏋️ Practical Exercise
- Drag a built-in snippet onto a page.
- Edit snippet text and images.
- Create a custom snippet template.
- Register the snippet.
- Add options to a snippet.
🔥 Challenge Exercise
Create a custom website snippet (a reusable content block) with its QWeb template, register it so it appears in the editor’s Blocks panel, and add a customization option. Explain what a snippet is and how the drag-and-drop editor uses it.
- Snippet XML structure and how to register a building block
- Making zones editable with
oe_structure - Adding snippet options (color pickers, layout selectors)
- Dynamic snippets that load data from Odoo models
- JavaScript snippet initialization
Snippet XML Structure
A snippet consists of two parts: the snippet template itself, and an entry in the snippets panel. Both live in the same XML file, typically views/snippets/:
<!-- 1. The snippet HTML template -->
<template id="s_book_highlight" name="Book Highlight">
<section class="s_book_highlight pt-5 pb-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h2>Featured Book</h2>
<p class="lead">Discover our top pick this month.</p>
<a href="/books" class="btn btn-primary">Browse Library</a>
</div>
<div class="col-lg-6">
<img src="/web/image/library.book/1/cover_image"
class="img-fluid rounded" alt="Featured Book"/>
</div>
</div>
</div>
</section>
</template>
<!-- 2. Register it in the snippets panel -->
<template id="snippets" inherit_id="website.snippets">
<xpath expr="//div[@id='snippet_content']" position="before">
<t t-snippet="my_module.s_book_highlight"
t-thumbnail="/my_module/static/description/book_highlight_thumb.png"/>
</xpath>
</template>
The thumbnail image (160×120px recommended) shows in the editor's block palette. Store it in your module's static/description/ folder.
oe_structure – Editable Zones
Areas where editors can drag more snippets are marked with the oe_structure class. Add an id attribute to persist changes to the database:
<section class="s_book_highlight">
<div class="container">
<!-- Editable zone: editors can drag snippets here -->
<div class="oe_structure oe_empty" id="oe_structure_book_highlight_1"/>
</div>
</section>
The oe_empty class shows a placeholder message ("Drag building blocks here") when the zone is empty. When an editor drops a snippet here and saves, Odoo stores the HTML in an ir.ui.view record keyed to this id.
Snippet Options
Options let editors configure snippet appearance from the sidebar without editing HTML. Register options by targeting the snippet's CSS class:
<template id="s_book_highlight_options" inherit_id="website.snippet_options">
<xpath expr="." position="inside">
<div data-js="BookHighlight" data-selector=".s_book_highlight">
<!-- Background style option -->
<we-select string="Background">
<we-button data-select-class="bg-light">Light</we-button>
<we-button data-select-class="bg-dark text-white">Dark</we-button>
<we-button data-select-class="bg-primary text-white">Primary</we-button>
</we-select>
<!-- Layout option -->
<we-select string="Layout">
<we-button data-select-class="flex-row">Image Right</we-button>
<we-button data-select-class="flex-row-reverse">Image Left</we-button>
</we-select>
</div>
</xpath>
</template>
data-select-class adds/removes CSS classes on the snippet root element when an option is selected. No JavaScript needed for class-based options.
Dynamic Snippets
Dynamic snippets fetch data from Odoo models and render it in a carousel, grid, or list. They use a JSON route to reload data based on options the editor selects:
class LibrarySnippets(http.Controller):
@http.route('/books/snippet/featured',
type='json', auth='public', website=True)
def featured_books(self, limit=4, **kw):
books = request.env['library.book'].sudo().search([
('website_published', '=', True),
('featured', '=', True),
], limit=limit)
return request.env['ir.ui.view']._render_template(
'my_module.s_featured_books_content',
{'books': books},
)
The snippet's JavaScript calls this route when it initializes and when editor options change. Built-in dynamic snippets like s_dynamic_snippet follow this same pattern.
JavaScript Snippet Initialization
For client-side behavior (carousels, counters, animations), add a JavaScript file in static/src/js/ and register a snippet widget:
/** @odoo-module **/
import { SnippetOption } from '@web_editor/js/editor/snippets.options';
import { registry } from "@web/core/registry";
// Public widget — runs on the frontend for visitors
import publicWidget from '@web/legacy/js/public/public_widget';
publicWidget.registry.BookHighlight = publicWidget.Widget.extend({
selector: '.s_book_highlight',
start() {
// Runs when snippet loads on the page
this._initCounter();
return this._super(...arguments);
},
_initCounter() {
const counter = this.el.querySelector('.book-count');
if (counter) {
// Animate the counter
const target = parseInt(counter.dataset.target, 10);
counter.textContent = target.toLocaleString();
}
},
});
- A snippet needs two XML records: the template itself and an entry in
website.snippets - Use
oe_structurewith a uniqueidto create editable zones within your snippet - Snippet options use
we-select/we-buttonwithdata-select-classfor class-based configuration - Dynamic snippets fetch model data via JSON routes and re-render when options change
Interview Questions
- What is a website snippet?
- How do you create a custom snippet?
- How do snippets appear in the editor?
- What are snippet options?
- How do snippets relate to QWeb templates?
Related Topics
FAQ
Add data-oe-protected="true" to the snippet root element. This prevents the website editor from moving, duplicating, or removing it. Useful for structural snippets that are part of a page template rather than content blocks.
Each page is an ir.ui.view record with a unique key. When an editor saves, Odoo creates a "cow" (copy-on-write) view that overrides the original template for the current website. The original template is preserved and the override is stored as a child view with website_id set.
Yes — add data-oe-model, data-oe-id, and data-oe-field attributes to an element. Odoo's editor will allow inline editing and save changes back to the model field. For static text (not linked to a model), just add the oe_editable class to enable inline text editing that saves to the view record.

