Using WebExtension APIs

Extending the simple example extension to make use of WebExtension APIs.

In the second part of the Hello World Extension Tutorial, we will add a "Details" button to the message header toolbar. A click on it will show some information about the currently displayed message, which we retrieve using Thunderbird's WebExtension APIs.

Using a message_display_action

Similar to adding the browser_action in the first part of the Hello World Extension Tutorial, we have to extend the manifest.json to add the message_display_action.:

"message_display_action": {
    "default_popup": "messagePopup/popup.html",
    "default_title": "Details",
    "default_icon": "images/internet-32px.png"


The HTML file for our popup needs some place-holders, which we can later fill using JavaScript and Thunderbird's WebExtension APIs. We create a messagePopup folder inside the hello-world project folder and place the following popup.html file in the newly created folder:

<!DOCTYPE html>

    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" media="screen" href="popup.css">

    <div class="grid-container">
        <div class="header">Subject:</div>
        <div id="subject" class="content"></div>
        <div class="header">From:</div>
        <div id="from" class="content"></div>
        <div class="header">Received-Header:</div>
        <div id="received" class="content"></div>
    <script type="module" src="popup.js"></script>



We place the following popup.css file in the same folder as the popup.html file.

.grid-container {
    display: grid;
    grid-template-columns: 1fr 6fr;

.header {
    font-weight: bold

.grid-container div {
    margin: 1ex;

Instead of tables, we use modern CSS styling to format our HTML into a tabular view. The display: grid container defines how our 6 DIV elements inside the container DIV are aligned. Check the grid documentation or this grid guide for more details.


All WebExtension API functions return a Promise instead of an actual value. This aims to simplify the handling of asynchronous functions.

The author of this example prefers the async/await syntax over the .then() approach for handling Promises. This allows us to avoid the so called callback-hell of asynchronous functions and instead keep writing sequential code by simply awaiting all the returned Promises.

// The user clicked our button, get the active tab in the current window using
// the tabs API.
let tabs = await messenger.tabs.query({ active: true, currentWindow: true });

// Get the message currently displayed in the active tab, using the
// messageDisplay API. Note: This needs the messagesRead permission.
// The returned message is a MessageHeader object with the most relevant
// information.
let message = await messenger.messageDisplay.getDisplayedMessage(tabs[0].id);

// Update the HTML fields with the message subject and sender.
document.getElementById("subject").textContent = message.subject;
document.getElementById("from").textContent =;

// Request the full message to access its full set of headers.
let full = await messenger.messages.getFull(;
document.getElementById("received").textContent = full.headers.received[0];

The popup.js file was loaded as a top level ES6 module by specifying type="module" in its script tag. This allows us to use the await keyword directly in file scope code. Otherwise we would need to use an asynchronous wrapper function: async function load() { let tabs = await messenger.tabs.query({ active: true, currentWindow: true, }); ... } load();

In Thunderbird, all WebExtension API can be accessed through the browser.* namespace, as with Firefox, but also through the messenger.* namespace, which is a better fit for Thunderbird.


The tabs API provides access to Thunderbird's tabs. We need to get hold of the current active tab to learn which message is displayed there. We use the query method to find it in line 3.

Using messenger.tabs.getCurrent() will not work, as that always returns the tab in which it is being called from. In our case, the call is executed from inside the popup of the message_display_action and not from inside the tab we are looking for.


The getDisplayedMessage method of the messageDisplay API provides access to the currently viewed message in a given tab. It returns a Promise for a MessageHeader object from the messages API with basic information about the message in line 9.

At this stage we are interested in the subject (line 12) and the author (line 13).

The getDisplayMessage method requires the messagesRead permission, which needs to be added to the permissions key of our manifest.json file.

"permissions": [


We also want to get the received header from the message. That information is not part of the general MessageHeader object, so we have to request the full message.

The getFull method in line 16 returns a Promise for a MessagePart object, which relates to messages containing multiple MIME parts. The headers member of the part returned by getFull includes the headers of the message (excluding headers which are part of nested MIME parts available through the parts member).

Testing the Extension

Let's double-check that we made the correct changes and have all the files in the right places:

  ├── manifest.json
  ├── images/
      ├── internet.png
      ├── internet-16px.png
      └── internet-32px.png
  ├── mainPopup/
      ├── popup.css
      ├── popup.html
      └── popup.js
  └── messagePopup/
      ├── popup.css
      ├── popup.html
      └── popup.js

This is how our manifest.json should now look like:

    "manifest_version": 2,
    "name": "Hello World Example",
    "description": "A basic Hello World example extension!",
    "version": "2.0",
    "author": "Thunderbird Team",
    "browser_specific_settings": {
        "gecko": {
            "id": "",
            "strict_min_version": "78.0"
    "browser_action": {
        "default_popup": "mainPopup/popup.html",
        "default_title": "Hello World",
        "default_icon": "images/internet-32px.png"
    "message_display_action": {
        "default_popup": "messagePopup/popup.html",
        "default_title": "Details",
        "default_icon": "images/internet-32px.png"
    "permissions": [
    "icons": {
        "64": "images/internet.png",
        "32": "images/internet-32px.png",
        "16": "images/internet-16px.png"


As described in the first part of the Hello World Extension Tutorial, go to the Add-ons Manager to open the Debug Add-on Page and temporarily install the extension.

Trying it Out

Open any message and you will find a "Details" button in the message header toolbar. A click on it will show you the subject, the author and the first found received header of the currently viewed message.

Last updated