Skip to main content
iVentureTeam

mail_attachments

The attachment list on Odoo's invoice send wizard. It knows the difference between a file you added, a file the system generated, and a file that must not be deleted, and it cleans up after itself when you close the wizard.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated August 27, 20266 min read
Technical namemail_attachments
Field typesjson
Viewsform
Moduleaccount, present wherever Invoicing or Accounting is installed
Used in core1 occurrence, the document send wizard in account
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0
No-code setupNo. The field holds a structured value the server builds
Alternativesmany2many_binary, mail_composer_attachment_list, binary, account_batch_sending_summary

What the mail attachments field does

Sending an invoice from Odoo attaches more than one thing. There is the invoice PDF, which does not exist yet when the wizard opens, because it is generated at send time. There may be an electronic invoicing file. There may be terms and conditions the company always attaches. And the user may want to add something of their own.

Those are different kinds of entry with different rules, and a plain attachment relation cannot express them. So the wizard uses a structured field holding a list of entries, each flagged as a placeholder for a document to be generated, as protected from deletion, or as a manual upload. This widget renders that list and manages the flags.

Visually it is a list of files with an upload button and a remove icon on each. What is behind each remove icon depends on the entry: some are truly removed, some are only marked to be skipped, and some queue a real deletion for later.

What this means for your team

The behavior worth knowing about is the cleanup. Uploading a file to a wizard creates a real attachment record immediately, so abandoning the wizard would otherwise leave orphan files in the database. This widget deletes those on close, but only when the wizard was never saved, which is the correct rule: a saved wizard means the user meant it.

The three-way distinction also prevents a recurring support question. Removing the invoice PDF from the list does not stop the invoice from having a PDF; it stops that document being attached to this send. Marking the entry to be skipped rather than deleting it is what makes that possible, and it is why the entry sometimes reappears the next time the wizard opens.

The tooltip mechanism is the small quality touch. When a document cannot be included, for example because the format is not supported for that customer's electronic invoicing route, the reason appears on the file itself rather than in an error after sending.

Supported options in Odoo 19

There is nothing to configure. The registration provides a component and nothing else: no options, no supported types, no extractor. Everything comes from the structure the server puts in the field and from one companion field. Read from mail_attachments.js, Odoo 19.0.

OptionTypeWhat it does
placeholderflag on each entryMarks a document that will be generated at send time. Removing it keeps the entry and sets a skip marker instead of deleting anything.(default: false)
protect_from_deletionflag on each entryMarks a file that must not be deleted. Removing it behaves the same as a placeholder: kept and skipped.(default: false)
manualflag on each entryMarks a file the user uploaded in this wizard. If the wizard is closed without being saved, every entry with this flag is deleted.(default: false)
attachments_not_supportedcompanion fieldRead by literal name and not declared as a dependency. Maps attachment identifiers to the reason a document cannot be sent, shown as a tooltip.

One companion field is read by literal name. The tooltips shown on unsupported documents come from a field holding a mapping from attachment identifier to explanation. The widget declares no dependency on it, so a custom wizard has to load it; when it is absent, the files render with no tooltips and nothing else breaks.

Working examples

The core usage

<field name="mail_attachments_widget"
       widget="mail_attachments" nolabel="1"/>

No options. The structure in the field decides which entries appear and how each one behaves when removed.

The entry shape

{
  "id": 4211,
  "name": "INV_2026_0007.pdf",
  "placeholder": true,
  "protect_from_deletion": false,
  "manual": false
}

The three flags are what the remove handler branches on. A placeholder is a document that will be generated at send time.

The tooltip companion

# on the wizard model, read by literal name
attachments_not_supported = fields.Json(...)
# { attachment_id: "reason shown as a tooltip" }

Optional in practice: without it the list simply shows no explanations.

Three flags, one deletion queue, and a cleanup on close

The remove handler is where the widget's rules live. It walks the current list and, for the entry being removed, branches on two flags. If the entry is a placeholder or is protected from deletion, a copy of it is kept in the list with a skip marker set, so the server knows not to include it in this send. Otherwise the entry is dropped from the list and its attachment identifier is added to a set of things to delete. The rebuilt list is then written back to the field.

Nothing is deleted at that moment. The deletion set is drained when the component unmounts, which is what makes the cleanup safe: closing the wizard, navigating away or the dialog being destroyed all trigger it, and a user who changes their mind before then has simply changed a local list.

The unmount handler adds one more rule. If the wizard record has no database identifier, meaning it was never saved, every entry flagged as a manual upload is added to the deletion set as well. That is what stops abandoned uploads accumulating. A saved wizard keeps its uploads, because the user committed to them.

The tooltips are assembled by deep-copying the list and, for each entry whose identifier appears in the companion mapping, attaching the explanation. Deep-copying rather than mutating matters here: the rendered list is a throwaway view of the data, and the field itself keeps only the real entries.

Odoo 19 simplified the file: several presentation helpers, for building download URLs and reading file extensions, were moved out, and the two data accessors became getters. The behavior described above is unchanged from Odoo 17.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Byte-identical to Odoo 19 on the development branch.
Odoo 19.0VerifiedVerified against the shipped source. Presentation helpers removed and accessors turned into getters.
Odoo 18.0VerifiedSame removal rules and cleanup, with the presentation helpers still in the component.
Odoo 17.0VerifiedFirst version.
Odoo 16.0Not availableWidget does not exist.

Upgrade note. No view change is needed between Odoo 17 and Odoo 19. Odoo 19 removed presentation helpers from the component and turned two methods into getters, which matters only to custom code that called them. The entry structure and the removal rules are the same throughout.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The development branch is unstable and this can still change; we re-verify the page after release.

Byte-identical. The file on the development branch matches Odoo 19 exactly, including the three flags, the deletion queue and the unsaved-wizard cleanup. Any change to how attachments are presented will come from the template.

Common problems and fixes

SymptomCause and fix
A removed document came back next timeIt was a placeholder or a protected file, so removing it only marked it to be skipped for that send. Expected. Remove it again on the next wizard, or change the setting that attaches it.
Uploaded files disappearedThe wizard was closed without being saved, and every manual upload is deleted in that case. Complete the wizard, or re-upload after saving.
No tooltips on unsupported documentsThe wizard does not load the companion field the widget reads by literal name. Add that field to the wizard model and the view.
Orphan attachments in the databaseSomething closed the form in a way that did not unmount the component. The cleanup runs on unmount; check for a customization that keeps the component alive.
Options are ignoredThe registration has no extractor, so nothing in the options dictionary is read. Nothing to configure here.
Missing widget errorThe accounting module is not installed in that database. Install Invoicing or Accounting.

Mail attachments field vs the alternatives

WidgetBest forKey difference
mail_attachmentsWizards mixing uploaded files with documents that do not exist yetA structured file list with placeholder and protected entries, plus a deletion queue drained on close
many2many_binaryAn ordinary list of attached filesBacked by a real relation, so it cannot represent a document still to be generated
mail_composer_attachment_listAttachments inside the mail composerRemoves the upload button and unlinks composer attachments on removal
binaryA single file on a recordOne field, one file, no list
account_batch_sending_summarySummarizing what a batch send will doA read-only panel with no file management

For an ordinary list of files on a record, the binary attachments widget is simpler and works on a real relation. This one exists because the send wizard needs to represent documents that do not exist yet, which a relation cannot do. If you are building a similar wizard, the flag structure is the part worth copying.

Frequently asked questions

Why does removing the invoice PDF not remove it permanently?+
Because it is a placeholder for a document generated at send time. Removing it keeps the entry in the list with a skip marker, so it is left out of this send rather than deleted.
What happens to files I uploaded if I close the wizard?+
If the wizard was never saved, every manually uploaded file is deleted when the widget unmounts. That is what stops abandoned uploads accumulating in the database.
When are the deletions actually performed?+
On unmount. Removing an entry only queues its attachment; closing the wizard or navigating away drains the queue in one call.
Where do the tooltips come from?+
A companion field on the wizard, read by literal name, that maps attachment identifiers to an explanation. The widget declares no dependency on it, so it has to be loaded by the view.
Does it support any options?+
None. The registration provides a component only, with no options, no extractor and no supported types.

Invoice delivery that does not need a follow-up call

Attachments, terms and conditions, electronic invoicing files and the customer settings behind them decide whether an invoice arrives usable. We configure and test document sending in Odoo on versions 16 through 19.

Book a free consultation

How this page was produced

The three entry flags, the branching in the remove handler, the deletion queue, the unmount cleanup and the tooltip companion field were read from mail_attachments.js on the Odoo 19.0 branch, with the usage taken from the document send wizard view in the same module. Version differences come from comparing the file on the 17.0 and 18.0 branches, and the Odoo 20 statement from a byte comparison against the public development branch. Spotted an error? Tell us and we will correct the page.