Skip to main content
iVentureTeam

html_composer_message

The rich text area of Odoo's full email composer is its own field widget. html_composer_message tunes the HTML editor for writing messages: mentions, collapsed quotes, batch-only placeholders, and attachment syncing.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 24, 2026Updated August 24, 20267 min read
Technical namehtml_composer_message
Field typeshtml
Viewsform (composer and scheduled message dialogs)
Modulemail, shipped with every Odoo database
Used in core3 occurrences in the mail module: the full composer and scheduled message dialogs
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0
No-code setupNot applicable: it targets the composer models, not user forms
Alternativeshtml_mail, html, text

What the composer body widget does

When you hit the expand arrow in Odoo's chatter and land in the full email composer, the big writing area is not a generic HTML field. It is html_composer_message, a purpose-built subclass of the html_mail widget, which is itself the email-safe variant of the standard html editor. The mail module applies it to the body field of the composer wizard and of scheduled messages.

Its job is to make the editor behave like a message writer rather than a document writer. Reading getConfig() in the source: the video plugin is filtered out, banner commands are disabled in the powerbox, and a mention plugin is added so typing @ suggests people exactly like in Discuss, wired to the thread the composer was opened from. The signature block is tracked separately, so Odoo can tell an empty message with a signature from a message someone actually wrote.

The widget also carries composer-specific plumbing on a shared event bus: it reports whether the body is genuinely empty on an accidental discard, hands the HTML back without the signature when the dialog saves, and deletes inline attachment previews from the body when their chip is removed in the attachment list below the editor.

What this means for your team

Nobody configures this widget, but knowing how it behaves saves real support time, because several of its behaviors look like bugs to end users and are in fact rules.

The one that generates the most questions: dynamic placeholders only exist in batch mode. If a user opens the composer for a single customer and cannot find the placeholder command they saw in a template tutorial, that is the widget stripping the placeholder plugins because composition_batch is false. Placeholders like a contact's name only make sense when one email fans out to many recipients; for a single message you would just type the name. Train users on that rule once and the ticket disappears.

Second, the quoted thread in reply-all mode is collapsed behind an expander rather than deleted. Users who think their reply lost the history just need the toggle. Third, images and files pasted into the body become real attachments of the message, not just embedded blobs, so they appear in the attachment list and count for the recipient, which is usually what a business sender wants.

Working examples

How mail applies it (from the scheduled message form)

<field name="body" widget="html_composer_message" class="h-100"/>

Reusing it on a custom wizard

The widget assumes composer-shaped data. If you borrow it for your own sending wizard, mirror the fields it reads:

body = fields.Html()
model = fields.Char()  # target model of the thread
res_ids = fields.Char()  # JSON list of target ids
composition_batch = fields.Boolean()
composition_comment_option = fields.Selection([...])

With model and res_ids present, mentions and suggestions resolve against the right thread; without them the editor still works but the thread-aware features quietly do nothing.

The composer bus: attachments, discards and signatures

The most instructive part of the source is the attachment lifecycle, because three components cooperate through env.fullComposerBus.

Paste in, link up. When a file lands in the editor, onAttachmentChange links it into the record's attachment_ids, but only when the record is mail.compose.message and the view loaded attachment_ids. On mail.scheduled.message, which uses the same widget, the hook deliberately does nothing.

Chip out, body clean. When the user removes an attachment chip below the editor, the attachment list widget fires ATTACHMENT_REMOVED, and this widget deletes every element in the body carrying that data-attachment-id, then records a history step so undo works.

Save without the signature. On SAVE_CONTENT, the widget strips .o-signature-container blocks and returns the remaining HTML, plus a flag saying whether a signature block was present. Odoo 18 returned innerText flattened by a whitespace regex here, and the 18 source carries a TODO admitting the regex merges consecutive newlines; 19 replaced that with proper HTML markup. If discarded drafts from the full composer looked mangled to your users on 18, that was why, and upgrading genuinely fixes it.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Same registration; mention plugin replaced and placeholder sets reorganized on the development branch. See below.
Odoo 19.0VerifiedVerified against the shipped source: video plugin removed, banner commands disabled, HTML-based save, reply_all quote collapsing.
Odoo 18.0VerifiedFirst release, on the new html_editor stack. Saves discarded content as flattened plain text and requires the composer bus context.
Odoo 17.0Not availableDoes not exist; the composer used the previous editor stack.
Odoo 16.0Not availableDoes not exist.

Upgrade note for 18 to 19. XML needs no changes, but two behaviors flip: saved-back content is HTML instead of regex-flattened text, and the widget no longer assumes the composer bus exists, so it can render outside the full composer dialog without crashing. Custom code patching the 18 class should be retested against the plugin-based 19 editor.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The notes below read the public development branch, which can still change before release; we re-verify against the shipped version.

The mention machinery is replaced. The composer-specific MentionPlugin file is deleted on master and a new MailFullComposerSuggestionPlugin takes its place, built on the same MentionList and suggestion system Discuss uses, delimiter handling included. Patches against the 19 mention plugin will not survive.

Placeholder plugins are reorganized: the batch-mode filter switches from DYNAMIC_PLACEHOLDER_PLUGINS to a new DYNAMIC_FIELD_PLUGINS set, part of a wider dynamic-field rework in the editor.

Attachment tracking gets an observer. The widget grows a MutationObserver-based attachment set with cleanup on unmount, and the signature class moves to a renamed user_signature_plugin module. All internal, but they are exactly the seams where composer customizations sit, so plan a retest during your Odoo 20 migration.

Common problems and fixes

SymptomCause and fix
No dynamic placeholder command in the composerThe composer is in single-message mode; placeholder plugins are stripped when composition_batch is false. Compose from a list selection of multiple records, or use an email template instead.
The quoted previous messages seem to have disappeared in a replyWith reply_all active, the quoted thread is collapsed by the content-expandable plugin. Click the expander toggle in the body; the content is still there.
Pasted image does not appear as an attachment on a scheduled messageThe auto-link hook only runs for mail.compose.message records. Attach the file through the paperclip instead of pasting when scheduling.
Mentions suggest nobodyThe thread cannot be resolved because model or res_ids is missing on the record. Ensure both fields are set, which core composer flows do automatically.
No video command in the powerboxThe widget removes the video plugin deliberately; embedded video does not belong in email. This is designed behavior, not a regression.
Signature edits do not persist into the saved bodySignature blocks are stripped on SAVE_CONTENT; the signature is re-applied at send time from user settings. Change the signature under user preferences instead of in the composer.

Composer body widget vs the alternatives

WidgetBest forKey difference
html_composer_messageThe body of emails being composed or scheduledThread-aware mentions, batch-gated placeholders, attachment and signature plumbing
html_mailEmail-styled HTML stored on records, such as template bodiesCSS inlining for email clients, no composer bus or mention plugin
htmlGeneral rich text on business documentsFull editor with video and banners, no email constraints
textPlain multi-line notesNo formatting at all, and therefore nothing to sanitize

Rule of thumb from the mail module itself: html_composer_message for message bodies being written, html_mail for email-styled content stored on records such as templates, plain html for everything that is not an email.

Frequently asked questions

Where is html_composer_message actually used?+
On the body field of the full email composer (mail.compose.message) and of scheduled messages (mail.scheduled.message). It is applied by the mail module's own views; you would not normally put it on business forms.
Why do dynamic placeholders only appear sometimes?+
The widget removes the dynamic placeholder plugins unless the composer's composition_batch field is true. Placeholders resolve per recipient, so they are only offered when one message will be rendered for many records, such as composing from a multi-record selection.
Does it support the same options as the html widget?+
It declares no options itself and spreads the html_mail descriptor, so the inherited editor knobs from the html family apply. Its distinctive behaviors, mentions, quote collapsing, attachment sync, are driven by record fields and environment, not by XML options.
Can I type @ to mention colleagues in the full composer?+
Yes. The widget registers a mention plugin wired to the same suggestion system as Discuss, scoped to the thread the composer was opened from via the record's model and res_ids values. Mentioned partners are added as followers or recipients according to the composer flow.
What happens to attachments I paste into the body?+
On the standard composer they are uploaded and linked into attachment_ids, so they show as chips under the editor. Removing a chip also removes the pasted preview from the body, the two widgets synchronize over the composer bus. On scheduled messages the auto-link is intentionally skipped.
Is html_composer_message changing in Odoo 20?+
The development branch replaces the mention plugin with a shared full-composer suggestion plugin, renames the placeholder plugin set, and adds MutationObserver-based attachment tracking. These are internals, the widget name and usage stay the same, and nothing is final until the September 2026 release.

Composer not behaving the way your sales team expects?

We customize Odoo's mailing pipeline end to end: composer wizards with your own fields, dynamic placeholder strategies for batch sends, and editor plugins that survive version upgrades. If your team lives in the full composer all day, small fixes here pay back daily.

Ask us about composer customization

How this page was produced

Verified by reading html_composer_message_field.js and its plugin files on the Odoo 19.0 branch, diffing against 18.0 for the version notes, including the plain-text save path and its TODO comment, and against the public development branch for the Odoo 20 section, where the mention plugin replacement was found. The descriptor chain to htmlMailField was traced for the options statement. Core usage was confirmed in the mail composer and scheduled message view archs. Corrections welcome via our contact page.