Letting Users Delete Omnibox Suggestions

Make extension omnibox suggestions removable: the deletable flag, handling onDeleteSuggestion, what deletion should mean (hide, forget usage, or delete data), undo, and keeping deleted items out of future results.

Published October 2, 2026 Updated October 2, 2026 7 min read
Table of Contents

Chrome lets users remove entries from the address bar dropdown — hover a history suggestion and click the ✕, or press Shift+Delete. Extension suggestions can opt into the same affordance. Without it, a stale or embarrassing suggestion keeps appearing every time the user types a matching query, and the only way to get rid of it is to find and delete the underlying data somewhere in the extension’s UI. With deletable: true and an onDeleteSuggestion handler, the user removes it right where it bothers them. The interesting part is deciding what “delete” means. This guide covers the API and that decision. It belongs to omnibox and address bar integration.

How deletable suggestions work

Each suggestion passed to suggest() can include deletable: true. Chrome then shows a remove button on that row (and honours Shift+Delete when it is selected). When the user removes it, Chrome fires chrome.omnibox.onDeleteSuggestion with the suggestion’s description text, then removes the row from the current dropdown. Note what you receive: the description, not the content — so your handler must map a description back to the item. The default suggestion cannot be deleted. Deletion is local to the current dropdown until you act on it; the next keystroke calls onInputChanged again, and if you return the item, it reappears.

Deleting an omnibox suggestionThe worker returns suggestions marked deletable and remembers a description-to-item map; the user presses the remove button; onDeleteSuggestion fires with the description; the worker finds the item, hides it, and future input no longer returns it.Service workerOmniboxUsersuggest([{content, description, deletable:true}])✕ on "Old project notes"onDeleteSuggestion(description)map → item; hide itnext keystroke → item absent
You get the description back — keep a map to the item.

Step-by-step: deletable suggestions

1. Mark suggestions deletable and keep a lookup

 1// sw.js
 2let shown = new Map();   // description → item id, for the current dropdown
 3
 4chrome.omnibox.onInputChanged.addListener(async (text, suggest) => {
 5  const items = await search(text);
 6  shown = new Map();
 7  suggest(items.slice(0, 6).map((it) => {
 8    const description = `${highlight(it.title, text)} <url>${escapeXml(it.url)}</url>`;
 9    shown.set(description, it.id);
10    return { content: it.url, description, deletable: true };
11  }));
12});

Execution context: the service worker. Because onDeleteSuggestion passes the description, store the exact string you sent. Descriptions must be unique within a dropdown for the lookup to be unambiguous; including the URL usually guarantees that. The map is only needed for the current dropdown, so an in-memory variable is fine — but see step 5 for worker restarts.

2. Handle the deletion

1chrome.omnibox.onDeleteSuggestion.addListener(async (description) => {
2  const id = shown.get(description) ?? (await idFromDescription(description));
3  if (!id) return;
4  await hideFromOmnibox(id);
5});

Execution context: the service worker, with the listener at the top level. The fallback idFromDescription extracts the URL from the <url> element when the in-memory map was lost. The handler should be fast and must not open UI — the user is in the middle of typing.

What deleting a suggestion could meanThree meanings of deleting an omnibox suggestion — hide from omnibox, forget usage history, delete the item — compared on what happens to the data, reversibility and when each fits.MeaningData keptReversibleFits whenHide from omniboxYesYes, from settingsMost extensionsForget usageItem yes, history noNoRanking-driven suggestionsDelete the itemNoOnly with undoSuggestions are the only …
Default to hiding: it matches what users expect from the address bar and loses nothing.

3. Choose what deletion means

1async function hideFromOmnibox(id) {
2  const { omniboxHidden = [] } = await chrome.storage.local.get("omniboxHidden");
3  if (!omniboxHidden.includes(id)) omniboxHidden.push(id);
4  await chrome.storage.local.set({ omniboxHidden });
5  hiddenSet.add(id);
6}

Execution context: the service worker. In the browser’s own address bar, removing a suggestion removes that history entry — a narrow, expected effect. For an extension, the closest equivalent is usually “don’t suggest this again”, not “delete my saved page”: a user cleaning up the dropdown would be dismayed to find the item gone from their library. Hiding is reversible and loses nothing. If your suggestions are derived from usage (recently opened items), “forget this usage” is another narrow option. Delete the underlying data only if the omnibox is the item’s only interface — and then offer undo.

4. Filter hidden items from future results

1let hiddenSet = new Set();
2chrome.storage.local.get("omniboxHidden").then(({ omniboxHidden = [] }) => { hiddenSet = new Set(omniboxHidden); });
3
4async function search(text) {
5  return (await rank(text)).filter((it) => !hiddenSet.has(it.id));
6}

Execution context: the service worker. Filtering at query time keeps the underlying data intact and makes un-hiding trivial. Load the set at startup so the first keystroke after a worker restart already respects it.

Hidden items stay out of resultsSearch results are ranked from the full data set, then filtered against the hidden set loaded from storage at startup; a settings page lists hidden items and lets the user restore them.Full datalibraryrank(text)scored matches− hiddenSetfrom storageresultsuggest()visible resultsSettings: Hiddenlist + RestoreRestoreremove from hiddenSet
Filter at query time; restore from settings.

5. Survive worker restarts

The service worker can stop between the moment suggestions are shown and the moment the user deletes one. The new instance will not have the shown map. Make descriptions parseable — for example, always end with <url>…</url> — so the handler can recover the URL and find the item by URL:

1async function idFromDescription(desc) {
2  const m = desc.match(/<url>(.*?)<\/url>/);
3  if (!m) return null;
4  const url = unescapeXml(m[1]);
5  return (await findByUrl(url))?.id ?? null;
6}

Execution context: the service worker. Unescape before looking up, because the description contains the escaped form. See escaping XML in omnibox descriptions.

6. Let users restore hidden items

Add a “Hidden from address bar” list in the options page with a Restore button for each entry. Without it, a misclick is permanent and invisible. Keep the list short by showing titles and URLs, and offer “Restore all”.

7. Offer undo for real deletions

If deletion removes data, keep a short-lived tombstone and show a notification: “Deleted ‘Old project notes’ — Undo”. On Undo, restore from the tombstone. Clear tombstones after a few minutes with an alarm. See handling notification clicks and buttons.

8. Build the restore list in options

 1// options.js
 2async function renderHidden() {
 3  const { omniboxHidden = [] } = await chrome.storage.local.get("omniboxHidden");
 4  const items = await chrome.storage.local.get(omniboxHidden.map((id) => `item:${id}`));
 5  const list = document.querySelector("#hidden-list");
 6  list.replaceChildren(...omniboxHidden.map((id) => {
 7    const it = items[`item:${id}`];
 8    const li = document.createElement("li");
 9    li.textContent = it ? it.title : chrome.i18n.getMessage("deletedItem");
10    const btn = Object.assign(document.createElement("button"), { textContent: chrome.i18n.getMessage("restore") });
11    btn.addEventListener("click", async () => {
12      await chrome.storage.local.set({ omniboxHidden: omniboxHidden.filter((x) => x !== id) });
13      renderHidden();
14    });
15    li.append(btn);
16    return li;
17  }));
18}

Execution context: the options page. Reading the same omniboxHidden key the worker writes keeps both in sync. The worker should listen to storage.onChanged for that key and rebuild hiddenSet, so a restore takes effect on the next keystroke. Items whose underlying data has since been deleted are shown with a placeholder title so the user can still clean them up.

Common mistakes

  • Expecting content in the handler. You receive the description.
  • Deleting library data on omnibox removal. Users expect a narrow effect.
  • Not filtering on the next keystroke. The item reappears immediately.
  • In-memory-only lookup. Fails after a worker restart.
  • No way to restore. Misclicks become permanent.

Cross-browser variation

  • Chrome / Edge: deletable and onDeleteSuggestion supported in current versions.
  • Firefox: browser.omnibox supports deletable and onDeleteSuggestion in recent versions; descriptions are plain text, so parse the URL from your own plain-text format.
  • Safari: no omnibox API.

Verification

  1. Type a query, remove a suggestion with ✕, then type again and confirm it does not return.
  2. Restart the browser and confirm it is still hidden.
  3. Stop the worker between showing and deleting, and confirm deletion still works.
  4. Restore the item from settings and confirm it reappears.

FAQ

Can the default suggestion be deletable?

No. Only suggestions passed to suggest().

Does Shift+Delete work?

Yes, on a selected deletable suggestion, the same as the remove button.

Should every suggestion be deletable?

Make data-derived suggestions deletable. Built-in commands (help, settings) need not be.

Is a hidden list synced across devices?

Only if you store it in storage.sync. Hiding is usually a local preference, so storage.local is a reasonable default; sync it if users ask for consistent results everywhere.

What if two suggestions have the same description?

The lookup becomes ambiguous. Make descriptions unique by including the URL or another distinguishing detail; if duplicates are genuinely the same item, merge them before suggesting.

Should hiding also remove usage history?

It can. Clearing the item’s usage counts means that, if it is restored later, it starts from a neutral ranking rather than jumping straight back to the top.

Other UI/UX Patterns & Interactive Components Resources