Run scripts on every page Stay organized with collections Save and categorize content based on your preferences.
Create your first extension that inserts a new element on the page.
Overview
This tutorial builds an extension that adds the expected reading time to any Chrome extension andChrome Web Store documentation page.

In this guide, we're going to explain the following concepts:
- The extension manifest.
- What icon sizes an extension uses.
- How to inject code into pages usingcontent scripts.
- How to use match patterns.
- Extension permissions.
Before you start
This guide assumes that you have basic web development experience. We recommend checking out theHello world tutorial for an introduction to the extension development workflow.
Build the extension
To start, create a new directory calledreading-time
to hold the extension's files. If youprefer, you can download the complete source code fromGitHub.
Step 1: Add information about the extension
The manifest JSON file is the only required file. It holds important information about theextension. Create amanifest.json
file in theroot of the project and add the following code:
{"manifest_version":3,"name":"Reading time","version":"1.0","description":"Add the reading time to Chrome Extension documentation articles"}
These keys contain basic metadata for the extension. They control how the extension appears on the extensions page and, when published, on the Chrome Web Store. To dive deeper, check out the"name"
,"version"
and"description"
keys on theManifest overview page.
💡Other facts about the extension manifest
- It must be located at theroot of the project.
- The only required keys are
"manifest_version"
,"name"
, and"version"
. - It supports comments (
//
) during development, but these must be removed before uploading your code to the Chrome Web Store.
Step 2: Provide the icons
So, why do you need icons? Althoughicons are optional during development, they arerequired if you plan to distribute your extension on the Chrome Web Store. They also appear in otherplaces like the Extensions Management page.
Create animages
folder and place the icons inside. You can download the icons onGitHub. Next, add the highlighted code to your manifest to declare icons:
{"icons":{"16":"images/icon-16.png","32":"images/icon-32.png","48":"images/icon-48.png","128":"images/icon-128.png"}}
We recommend using PNG files, but other file formats are allowed, except for SVG files.
💡Where are these differently-sized icons displayed?
Icon size | Icon use |
---|---|
16x16 | Favicon on the extension's pages and context menu. |
32x32 | Windows computers often require this size. |
48x48 | Displays on the Extensions page. |
128x128 | Displays on installation and in the Chrome Web Store. |
Step 3: Declare the content script
Extensions can run scripts that read and modify the content of a page. These are calledcontentscripts. They live in anisolated world, meaning they can make changes to their JavaScript environment without conflicting with their host page or other extensions' content scripts.
Add the following code to themanifest.json
to register a content script calledcontent.js
.
{"content_scripts":[{"js":["scripts/content.js"],"matches":["https://developer.chrome.com/docs/extensions/*","https://developer.chrome.com/docs/webstore/*"]}]}
The"matches"
field can have one or morematch patterns. These allow the browser toidentify which sites to inject the content scripts into. Match patterns consist of three parts:<scheme>://<host><path>
. They can contain '*
' characters.
💡Does this extension display a permission warning?
When a user installs an extension, the browser informs them what the extension can do. Content scripts request permission to run on sites that meet the match pattern criteria.
In this example, the user would see the following permission warning:
To dive deeper on extension permissions, seeDeclaring permissions and warn users.
Step 4: Calculate and insert the reading time
Content scripts can use the standardDocument Object Model (DOM) to read and change thecontent of a page. The extension will first check if the page contains the<article>
element.Then, it will count all the words within this element and create a paragraph that displays the totalreading time.
Create a file calledcontent.js
inside a folder calledscripts
and add the following code:
functionrenderReadingTime(article){// If we weren't provided an article, we don't need to render anything.if(!article){return;}consttext=article.textContent;constwordMatchRegExp=/[^\s]+/g;// Regular expressionconstwords=text.matchAll(wordMatchRegExp);// matchAll returns an iterator, convert to array to get word countconstwordCount=[...words].length;constreadingTime=Math.round(wordCount/200);constbadge=document.createElement("p");// Use the same styling as the publish information in an article's headerbadge.classList.add("color-secondary-text","type--caption");badge.textContent=`⏱️${readingTime} min read`;// Support for API reference docsconstheading=article.querySelector("h1");// Support for article docs with dateconstdate=article.querySelector("time")?.parentNode;(date??heading).insertAdjacentElement("afterend",badge);}renderReadingTime(document.querySelector("article"));
💡Interesting JavaScript used in this code
- Regularexpressions used to count only the words inside the
<article>
element. insertAdjacentElement()
used to insert the reading time node after the element.- TheclassList property used to add CSS class names to the element class attribute.
- Optional chaining used to access an object property that may be undefined or null.
- Nullish coalescing returns the
<heading>
if the<date>
is null or undefined.
Step 5: Listen for changes
With the current code, if you switch articles using the left navigation, thereading time is not added to the new article. This is because our site isimplemented as a Single Page Application (SPA) that performs soft navigationsusing theHistory API.
To fix that, we can use aMutationObserver
to listen for changes and add the reading time to new articles.
To do this, add the following to the bottom ofcontent.js
:
MutationObserver
can have a performance cost, so use themsparingly and only observe the most relevant changes.constobserver=newMutationObserver((mutations)=>{for(constmutationofmutations){// If a new article was added.for(constnodeofmutation.addedNodes){if(nodeinstanceofElement &&node.tagName==='ARTICLE'){// Render the reading time for this particular article.renderReadingTime(node);}}}});// https://developer.chrome.com/ is a SPA (Single Page Application) so can// update the address bar and render new content without reloading. Our content// script won't be reinjected when this happens, so we need to watch for// changes to the content.observer.observe(document.querySelector('devsite-content'),{childList:true});
Test that it works
Verify that the file structure of your project looks like the following:
Load your extension locally
To load an unpacked extension in developer mode, follow the steps inDevelopmentBasics.
Open an extension or Chrome Web Store documentation
Here are a few pages you can open to see how long each article will take to read.
It should look like this:

🎯 Potential enhancements
Based on what you've learned today, try to implement any of the following:
- Add anothermatch pattern in the manifest.json to support otherchrome developerpages, like for example, theChrome DevTools orWorkbox.
- Add a new content script that calculates the reading time to any of your favorite blogs ordocumentation sites.
Keep building
Congratulations on finishing this tutorial 🎉. Continue building your skills by completing othertutorials on this series:
Extension | What you will learn |
---|---|
Focus Mode | To run code on the current page after clicking the extension action. |
Tabs Manager | To create a popup that manages browser tabs. |
Continue exploring
We hope you enjoyed building this Chrome extension and are excited to continue your Chromedevelopment learning journey. We recommend the following learning path:
- Thedeveloper's guide has dozens of additional links to pieces of documentationrelevant to advanced extension creation.
- Extensions have access to powerful APIs beyond what's available on the open web.TheChrome APIs documentation walks through each API.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2022-10-04 UTC.