Mist field guide

How to build a Chrome new-tab extension with Manifest V3

A new-tab extension can be three small files: a Manifest V3 declaration, an HTML page and a local script. Start permission-free and add capabilities only when a feature requires them.

Minimal new-tab interface illustrating a simple Chrome extension outcome

Minimal project structure

Create a new directory that contains only the files below. The example is original and intentionally separate from Mist’s implementation.

Project tree
quiet-tab/
├── manifest.json
├── newtab.html
├── newtab.css
└── newtab.js

Declare the new-tab override

The `chrome_url_overrides` key points `newtab` to a packaged HTML file. This minimal version needs no permissions. A descriptive title helps users distinguish the page from Chrome’s default.

manifest.json
{
  "manifest_version": 3,
  "name": "Quiet Tab",
  "version": "1.0.0",
  "description": "A small local new-tab page.",
  "chrome_url_overrides": {
    "newtab": "newtab.html"
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  }
}

Write semantic HTML with an external script

Manifest V3 extension pages do not allow inline JavaScript under the default security policy. Keep behavior in a packaged file and label the form for keyboard and assistive-technology users.

newtab.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Quiet Tab</title>
    <link rel="stylesheet" href="newtab.css">
    <script src="newtab.js" defer></script>
  </head>
  <body>
    <main>
      <p id="date"></p>
      <h1>Your next step</h1>
      <form id="note-form">
        <label for="note">One intention for this session</label>
        <input id="note" name="note" maxlength="120" autocomplete="off">
        <button type="submit">Save locally</button>
      </form>
      <p id="status" role="status"></p>
    </main>
  </body>
</html>

Add a small, responsive stylesheet

newtab.css
:root {
  color-scheme: light dark;
  font-family: system-ui, sans-serif;
}

body {
  min-height: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
  background: #101217;
  color: #f5f7fb;
}

main {
  width: min(36rem, calc(100% - 2rem));
}

label,
input,
button {
  display: block;
  width: 100%;
}

input,
button {
  min-height: 2.75rem;
  margin-top: 0.5rem;
  box-sizing: border-box;
}

:focus-visible {
  outline: 3px solid #9ab7ff;
  outline-offset: 3px;
}

Store the preference without extra permissions

`localStorage` is sufficient for this one-page, device-local example and requires no manifest permission. For larger extensions, Chrome’s storage API offers local, sync, session and managed areas; `chrome.storage` requires the `storage` permission.

newtab.js
const note = document.querySelector('#note');
const form = document.querySelector('#note-form');
const status = document.querySelector('#status');
const date = document.querySelector('#date');

date.textContent = new Intl.DateTimeFormat(undefined, {
  dateStyle: 'full'
}).format(new Date());

note.value = localStorage.getItem('quiet-tab-note') ?? '';

form.addEventListener('submit', (event) => {
  event.preventDefault();
  localStorage.setItem('quiet-tab-note', note.value.trim());
  status.textContent = 'Saved in this browser profile.';
});

Keep permissions and CSP minimal

Do not add `tabs`, history, bookmarks or broad host access preemptively. Each permission should enable a visible feature and be documented for reviewers and users.

Chrome enforces a minimum content security policy for extension pages. Package executable code with the extension, avoid `eval`, inline handlers and remotely hosted scripts, and restrict remote connections to the specific services a feature needs.

Test in developer mode

  • Open `chrome://extensions` and enable Developer mode.
  • Choose Load unpacked and select the project directory.
  • Open a normal new tab and confirm the page title, keyboard order and local save.
  • Inspect the page for console errors and unexpected network requests.
  • Use Reload on the extension card after source changes.
  • Test removal and conflicts with any other new-tab override.

Common mistakes

  • Putting JavaScript inline and hitting the extension CSP.
  • Pointing `newtab` to a missing or remote HTML file.
  • Requesting powerful permissions for features that do not need them.
  • Depending on a network response before showing the core page.
  • Omitting a `<title>` or visible keyboard focus.
  • Storing secrets in extension source; every packaged file reaches the user.

Prepare for publication

Add appropriately sized icons, a concise single-purpose description, accurate screenshots, a privacy policy when data handling requires it and clear permission justifications. Zip the contents so `manifest.json` sits at the archive root.

Before submitting, validate that the production archive contains no development credentials, source maps with secrets, unused permissions or remote executable code.

Primary references

Sources checked for this guide

Product and policy facts were last checked on 2026-07-25.