Back
Chapter 11

Working with Custom Pages

View Source Code

So far we can create new notes to any web page in our web extension and we can show it to the user whether a page already has some notes or not

But to make the functionality complete I want to have a new page which lists all the notes that the user has created

Luckily it’s super easy to add new pages to our extension so head over to wxt.dev and see the documentation

Creating an unlisted page

You can see that there are a lot of entry points so you can configure the bookmarks page the dev tools when there’s a new tab the popup which you already worked in

But I want to add an unlisted page which is a custom page for our extension and this will be the one where the user can manage the notes

So click on unlisted pages and it gives me some explanation how to do it

Basically naming convention is important

I can create a new folder and in that I add index.html

So let’s follow that path

My route should be called notes so I head over to our code base and create a new folder in the entrypoints called notes

And in that I will just add a new index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>My Notes</title>
</head>
<body>
  <div id="app">
    <h1>All Your Notes</h1>
    <div id="notes-grid"></div>
  </div>
  <script type="module" src="./main.ts"></script>
</body>
</html>

Perfect

Now I can add JavaScript and CSS just like in the popup

// entrypoints/notes/main.ts
import './style.css';
import { storage } from 'wxt/storage';

const loadNotes = async () => {
  const notesStorage = await storage.getItem('sync:notesStorage');
  const notesGrid = document.getElementById('notes-grid');
  
  if (!notesStorage || Object.keys(notesStorage).length === 0) {
    notesGrid.innerHTML = '<p>No notes found</p>';
    return;
  }
  
  // Render each note
  for (const [pageKey, noteData] of Object.entries(notesStorage)) {
    const noteCard = createNoteCard(pageKey, noteData);
    notesGrid.appendChild(noteCard);
  }
};

loadNotes();

Accessing the custom page

Your custom page gets a URL in the format

chrome-extension://{extension-id}/notes.html

To open it programmatically without knowing the extension ID

const url = browser.runtime.getURL('/notes.html');
await browser.tabs.create({ url });

You can also add query parameters

const url = browser.runtime.getURL('/notes.html?welcome=true');

Linking from popup

Provide easy access to your custom page from the popup

// In popup
const notesButton = document.querySelector('#view-notes');
notesButton.addEventListener('click', async () => {
  await browser.tabs.create({
    url: browser.runtime.getURL('/notes.html')
  });
});

Listening to storage changes

Keep the page in sync with storage updates

browser.storage.onChanged.addListener((changes, namespace) => {
  if (namespace === 'sync' && changes.notesStorage) {
    // Reload and re-render notes
    loadNotes();
  }
});

Perfect

Now users have a central place to manage all their notes across every website they visit

What’s next

We’ve created a custom page for managing notes

In the next chapter we’ll learn about the extension lifecycle and how to handle installation updates and uninstallation