How to Enable All Existing Snippet Options in Odoo 19

Enable all existing snippet options in Odoo 19 by matching the Website Builder markup conventions inside a custom snippet template.

Every Odoo 19 theme build reaches the same awkward moment. The custom snippet drops onto the page, the layout looks right, then the client clicks it and asks why the options panel is nearly empty. No background picker, no column controls, no padding handles. The standard blocks have all of that.

The answer is that the Odoo 19 Website Builder hands out options based on what it recognises in your markup, not on how cleanly you wrote the template. Options bind to CSS selectors, structural classes and registered plugins, so if your snippet does not match, the option never renders. A good share of the Odoo performance optimization services work I do on themes begins here, and it is a smaller fix than people brace for.

What Counts as an Existing Snippet Option in Odoo 19

Odoo 19 rebuilt the editor on the html_builder module. An option is now an OWL component extending BaseOptionComponent, registered through a Plugin via the builder_options resource and added to the website-plugins registry. Background and colour, sizing, spacing, animation, visibility, grid mode and parallax all already exist inside the html_builder and website plugin folders.

That word existing matters. You are not writing these options, you are making your snippet eligible for them. If you are migrating from Odoo 16, 17 or 18, the old website.snippet_options template with its we-selectwe-button and data-selector attributes is gone. Those bindings moved into JavaScript static properties.

Why a Custom Snippet Starts With Fewer Options Than a Core One

In practice I see three causes, almost always in this order.

  1. The wrapper element is wrong, so section level options never bind.
  2. The internal structure skips the Bootstrap classes the builder looks for, so column and sizing options stay hidden.
  3. The snippet was never registered in website.snippets, so the builder treats it as loose markup.

Fix those three and most of the panel returns on its own, before any plugin code.

Step 1: Fix the Wrapper Before You Touch Any JavaScript

The standard root of any structure snippet is a <section>. That tag makes the block movable, duplicable and removable, and it anchors most of the section level controls in the snippet options panel.

<template id="s_ay_feature_block" name="Feature Block">
    <section class="s_ay_feature_block pt80 pb80"
             data-name="Feature Block"
             data-snippet="s_ay_feature_block">
        <div class="container s_allow_columns">
            <div class="row">
                <div class="col-lg-6">
                    <h2>Built for your workflow</h2>
                    <p>Short supporting copy.</p>
                </div>
                <div class="col-lg-6">
                    <img src="/website_ay/static/src/img/content/placeholder.jpg"
                         class="img-fluid" alt="Feature illustration"/>
                </div>
            </div>
        </div>
    </section>
</template>

The Three Attributes Every Snippet Root Needs

class gives the snippet a unique hook, data-name is the label shown in the right hand panel, and data-snippet identifies the block. The builder adds the last two on drag and drop, but if you hard code a snippet into a theme page you must write them yourself, or the block goes unrecognised and misbehaves at upgrade time.

One rule worth tattooing somewhere: never nest a <section> inside another <section>. It triggers the Website Builder options twice and the panel starts fighting itself. Use an inner content snippet instead.

The pt80 pb80 classes give you the draggable padding handles. Odoo increments those values in multiples of eight up to 256.

Step 2: Unlock Column, Sizing and Spacing Options With Structural Classes

Sizing options are triggered by structure, not configuration. Any Bootstrap column descending directly from a .row gets resize handles, so .row > .col-lg-* is the shape you want. The s_allow_columns class on the container switches on the columns selector.

Turning Individual Options Back Off

Enabling everything is rarely the end goal on a client site. Once the options are live you will want to lock parts of the layout. Odoo 19 gives you opt out classes for that:

  • s_nb_column_fixed on the row disables the column count option.
  • s_col_no_resize on the row disables resizing for all child columns, or on one column to lock just that one.
  • s_col_no_bgcolor disables the background colour option for a row or a single column.
  • o_not_editable makes an element non editable and oe_unremovable stops it being deleted.

Step 3: Enable Background, Colour and Parallax Options

Colour comes from the palette classes. Add o_cc with a numbered combination such as o_cc2 to a section or column and the background colour option appears, wired to the theme palette rather than hard coded hex values.

For image backgrounds, oe_img_bg and o_bg_img_center give you the background image controls. A parallax section needs a different shape:

<section class="s_ay_hero parallax s_parallax_is_fixed"
         data-scroll-background-ratio="1">
    <span class="s_parallax_bg oe_img_bg o_bg_img_center"
          style="background-image: url('/website_ay/static/src/img/content/hero.jpg');"/>
    <div class="o_we_bg_filter bg-black-50"/>
    <div class="container">
        <!-- Content -->
    </div>
</section>

The o_we_bg_filter div is the colour filter layer, and it is what exposes the filter and opacity control. For grid layout options, put o_grid_mode and data-row-count on the row and o_grid_item on the children.

For a walk through of what eac style=”color:black”h control actually does once it appears, rather than how to make it appear, see my piece on how snippet options work in the Odoo 19 Website Builder.

Step 4: Register the Snippet So the Builder Treats It as First Class

Options and drag and drop behaviour both improve once the snippet is declared through website.snippets inheritance. Add it to the snippet_structure list with a group:

<template id="snippets" inherit_id="website.snippets" name="AY Snippets">
    <xpath expr="//snippets[@id='snippet_groups']/*[1]" position="before">
        <t snippet-group="ay"
           t-snippet="website.s_snippet_group"
           string="Custom Blocks"
           t-thumbnail="/website_ay/static/src/img/wbuilder/s_ay_group.svg"/>
    </xpath>
    <xpath expr="//snippets[@id='snippet_structure']/*[1]" position="before">
        <t t-snippet="website_ay.s_ay_feature_block"
           string="Feature Block" group="ay">
            <keywords>feature columns image</keywords>
        </t>
    </xpath>
</template>

Step 5: Layer Your Own Options on Top With a Builder Plugin

Once the built in set is back, anything extra goes through a plugin. The component declares where it binds, the plugin registers it via builder_options, and an XML template renders the controls.

import { BaseOptionComponent } from "@html_builder/core/utils";
import { Plugin } from "@html_editor/plugin";
import { registry } from "@web/core/registry";

export class AyFeatureBlockOption extends BaseOptionComponent {
    static template = "website_ay.AyFeatureBlockOption";
    static selector = ".s_ay_feature_block";
    static applyTo = ":scope > .container > .row";
}

export class AyFeatureBlockOptionPlugin extends Plugin {
    static id = "ayFeatureBlockOption";
    resources = {
        builder_options: [AyFeatureBlockOption],
    };
}

registry.category("website-plugins").add(
    AyFeatureBlockOptionPlugin.id,
    AyFeatureBlockOptionPlugin
);
<templates xml:space="preserve">
    <t t-name="website_ay.AyFeatureBlockOption">
        <BuilderRow label.translate="Media Position">
            <BuilderSelect>
                <BuilderSelectItem classAction="''">Right</BuilderSelectItem>
                <BuilderSelectItem classAction="'flex-row-reverse'">Left</BuilderSelectItem>
            </BuilderSelect>
        </BuilderRow>
    </t>
</templates>

Register both files in the website.website_builder_assets bundle. Forget that and the editor never loads them, and you lose an hour debugging a selector that was correct all along.

Binding With selector, applyTo and exclude

Three static properties do the binding. selector decides which elements make the option appear. applyTo points it at a child of the matched element, so you can target the inner row while the user clicks the whole section. exclude removes specific matches, useful when a broad selector such as ul would catch navigation menus.

Making a Snippet Droppable as Inner Content

If the block should drop inside another building block rather than only at section level, add its selector to the so_content_addition_selector resource and use dropzone_selector with dropIn or dropNear to control where it lands. Add it to snippet_content so it appears in the Inner content list too.

A Checklist I Run on Every Odoo 19 Theme

  1. Root is a <section> with a unique class, data-name and data-snippet.
  2. No section nested inside another section.
  3. Padding classes such as pt80 pb80 on the section.
  4. Structure follows .container.row.col-lg-*, with s_allow_columns on the container.
  5. Colour classes o_cc o_cc* on elements that should be recolourable.
  6. Snippet registered in website.snippets with a group and thumbnail.
  7. Opt out classes are applied only where the client should be locked out.
  8. Custom option JavaScript and XML both in website.website_builder_assets.

When a panel is still short after all eight, it is nearly always item two or item eight. A nested section doubles the options, and a missing asset bundle entry fails silently.

Getting this right decides whether the client edits the theme themselves or sends you a ticket for every small change. If you are staring at a half empty options panel on a live site, Book a Consultation and we can walk your snippet markup together, find the missing bindings, and decide which controls to leave switched off on purpose.

Conclusion

Enabling all existing snippet options in Odoo 19 is less about writing code than about writing markup the Website Builder recognises. Use a <section> root, follow the Bootstrap structure, add the palette and padding classes, register the snippet properly, then use the opt out classes to lock down what should not change. Only then does a builder plugin make sense, adding genuinely custom controls rather than rebuilding what Odoo already ships.

Frequently Asked Questions

Do I still use website.snippet_options and we-select in Odoo 19?

No. Odoo 19 moved snippet options onto html_builder. Options are OWL components extending BaseOptionComponent, registered through a plugin in the website-plugins registry, with templates built from BuilderRow and BuilderSelect. Migrating from Odoo 18 or earlier means rewriting that XML, not porting it.

Why do my column resize handles not appear?

Almost always structure. The columns must descend directly from a .row, and nothing in that chain can carry s_col_no_resize. Adding s_allow_columns to the container also switches on the columns selector.

Can I enable every option and then hide just a few?

Yes, and that is what I recommend. Turn the full set on through structure and classes, confirm the panel is complete, then apply s_nb_column_fixeds_col_no_resizes_col_no_bgcoloro_not_editable or oe_unremovable to the elements the client should not change.

My custom option does not show up at all. What should I check first?

Check the website_builder_assets bundle before the selector. Both the JavaScript and the XML template must be registered there. Then confirm selector matches the element the user actually clicks, and remember applyTo is what lets the option act on a child.

Does adding a section inside a section really cause problems?

It does. Nested sections trigger the Website Builder options twice, producing duplicate or conflicting controls. Use an inner content snippet for nested blocks and register it through so_content_addition_selector and snippet_content.

Reach Out for Support

Facing a problem? Contact us and receive expert help and fast solutions.