External Components Registration Guide
This guide is for Firefox feature teams who want to embed custom web components into the about:newtab and about:home pages.
Overview
The External Components system allows you to register custom web components that will be displayed on the New Tab page without modifying the New Tab codebase. You provide a registrant module that returns component configurations, and the New Tab system handles loading and rendering your components.
Getting Started
The first step is always to engage with the New Tab team in order to arrange for your component to be integrated, as there’s some work that must occur within the New Tab code to allow this.
The New Tab team will help guide you as you develop your component so that it integrates properly and sustainably. The intent of this mechanism is so that external teams can focus on providing the features that they’re the experts in, while New Tab can ensure that the integration of those features maps properly to New Tab’s needs.
Component Configuration
Each component configuration must include:
{
type: "UNIQUE_TYPE", // Required: Unique identifier for this component type.
componentURL: "chrome://...", // Required: URL to the module that defines the custom element
tagName: "custom-element", // Required: Tag name of the custom element
l10nURLs: [], // Optional: Array of localization file URLs
attributes: {}, // Optional: HTML attributes to set on the element
cssVariables: {} // Optional: CSS custom properties to set on the element
}
Required Fields
type: A unique string identifier for your component (e.g.,
"SEARCH","MY_FEATURE"). This must be unique across all registered components. The New Tab team will assign this to you once you’ve started talking to them about your external component.componentURL: A chrome:// or resource:// URL pointing to the ES module that defines your custom element.
tagName: The HTML tag name for your custom element (must contain a hyphen per web component standards).
Optional Fields
l10nURLs: Array of Fluent localization file paths (e.g.,
["browser/myfeature.ftl"])attributes: Object mapping attribute names to values to set on your custom element
cssVariables: Object mapping CSS custom property names to values for styling
Step-by-Step Registration
1. Create a Registrant Module
Create a module that extends BaseAboutNewTabComponentRegistrant:
// MyComponentRegistrant.sys.mjs
import {
AboutNewTabComponentRegistry,
BaseAboutNewTabComponentRegistrant,
} from "moz-src:///browser/components/newtab/AboutNewTabComponents.sys.mjs";
export class MyComponentRegistrant extends BaseAboutNewTabComponentRegistrant {
getComponents() {
return [
{
type: AboutNewTabComponentRegistry.TYPES.MY_FEATURE,
componentURL: "chrome://browser/content/myfeature/component.mjs",
tagName: "my-feature-component",
l10nURLs: ["browser/myfeature.ftl"],
attributes: {
"data-feature-id": "my-feature",
"role": "region"
},
cssVariables: {
"--feature-primary-color": "var(--in-content-primary-button-background)",
"--feature-spacing": "16px"
}
}
];
}
}
2. Define Your Custom Element
Create the custom element referenced in your componentURL. This can be a
vanilla web component, or a MozLitElement.
// component.mjs
class MyFeatureComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
:host {
display: block;
padding: var(--feature-spacing, 12px);
color: var(--feature-primary-color, blue);
}
</style>
<div data-l10n-id="my-feature-title"></div>
<div class="content"></div>
`;
this.render();
}
disconnectedCallback() {
// Clean up any event listeners or resources
}
render() {
const content = this.shadowRoot.querySelector(".content");
content.textContent = "My feature content";
}
}
customElements.define("my-feature-component", MyFeatureComponent);
3. Register with the Category Manager
Register your registrant with the category manager when your feature initializes.
Typically, this is done declaratively inside of a chrome manifest file like
BrowserComponents.manifest, like so:
category browser-newtab-external-component moz-src:///browser/components/my-team/MyComponentRegistrant.sys.mjs MyComponentRegistrant
Declarative is preferable, but if you need to do this dynamically, it can be done by adding a category at runtime like so:
Services.catMan.addCategoryEntry(
"browser-newtab-external-component",
"moz-src:///browser/components/my-team/MyComponentRegistrant.sys.mjs",
"MyComponentRegistrant",
false,
true
);
Best Practices
Use Shadow DOM
Always use Shadow DOM to encapsulate your component’s styles and avoid conflicts:
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
// Your component markup goes here
}
Never reach into the New Tab page DOM
External components are forbidden from reaching outside of themselves into the surrounding page DOM either for reading or writing state. If there’s some state value that your component needs to read, talk to the New Tab team so that we can expose that value to you.
Localization
Use Fluent for all user-facing strings:
Add your localization file to
l10nURLsin your configurationUse
data-l10n-idattributes in your markupCreate corresponding entries in your .ftl file
Example .ftl file:
my-feature-title = My Feature
my-feature-description = This is my feature description
Styling
Use CSS custom properties for theming to integrate with Firefox’s design system:
:host {
color: var(--in-content-text-color);
background: var(--in-content-box-background);
font: message-box;
}
Cleanup
Always clean up resources in disconnectedCallback:
disconnectedCallback() {
// Remove event listeners
// Cancel pending operations
// Clear timers
}
Testing
Writing Tests
Test your registrant in xpcshell tests:
add_task(async function test_my_registrant() {
const registrant = new MyComponentRegistrant();
const components = registrant.getComponents();
Assert.equal(components.length, 1);
Assert.equal(components[0].type, "MY_FEATURE");
Assert.equal(components[0].tagName, "my-feature-component");
});
Integration Testing
Test that your component registers correctly with the category manager:
add_task(async function test_component_registration() {
let registry = new AboutNewTabComponentRegistry();
Services.catMan.addCategoryEntry(
"browser-newtab-external-component",
"resource://gre/modules/MyComponentRegistrant.sys.mjs",
"MyComponentRegistrant",
false,
true
);
await TestUtils.waitForTick();
let components = Array.from(registry.values);
Assert.ok(components.some(c => c.type === "MY_FEATURE"));
Services.catMan.deleteCategoryEntry(
"browser-newtab-external-component",
"resource://gre/modules/MyComponentRegistrant.sys.mjs",
false
);
registry.destroy();
});
Debugging
Enable Logging
Set this pref to enable component registration logging:
browser.newtabpage.activity-stream.externalComponents.log=true
Common Issues
Component not appearing on New Tab:
Verify your registrant is added to the category manager
Check that your component type is unique
Ensure your custom element is properly defined
Check the browser console for errors
Styling issues:
Verify you’re using Shadow DOM
Check that CSS custom properties are defined
Ensure localization files are loaded
Type conflicts:
Each component type must be unique. If two registrants provide the same type, only the first one will be registered.
Support
For questions or issues with the External Components system, contact the Firefox New Tab team or file a bug in the Firefox :: New Tab Page component.