Bookmarklets are bookmarks that run JavaScript. They’re like browser extensions, but without actually needing an extension. In fact, I think they may predate extensions. Speaking of history, bookmarklets have sometimes been called “favelets”. Regardless of what you want to call them, you should learn how to use them. They’re like a secret tool for the sophisticated cybersurfer.
Basics
Bookmarklets are javascript: URIs, generally looking something like the following:
javascript:(function () {
/* Your code here */
})()
Here’s a super basic bookmarklet:
javascript:window.alert("Hello!");
That’ll just make an alert saying “Hello!”. To actually add this to your bookmarks toolbar, you can do one of three things:
- Add a new bookmark manually
- Drag a link of the bookmarklet to your toolbar
- Edit the URL of an existing bookmark
Only the third way seems to work on mobile devices. As of 2024-02-11, I’ve confirmed it works on Firefox for Android and Safari on an iPad (but it seems like Apple might censor this functionality in third-party browsers).
Adding bookmarklets manually
In Firefox, to add a bookmarklet manually, you do the following:
- Open up your “Library” (your list of bookmarks) by pressing Control+Shift+O (or by going to the hamburger menu and going to Bookmarks > Manage bookmarks
- Select your “Bookmarks Toolbar” from the sidebar
- In the menu, select Organize > Add Bookmark
- Give your bookmark a name, and then copy your bookmarklet URL to the URL.
- (Optional) You can give your bookmarklet a keyword and it’ll run if you type that keyword in the address bar.
Dragging bookmarklet links
This is a bit riskier, especially if you’re using other people’s bookmarklets. The reason being it can be hard to read a link and understand what the code does. So do this only for your own bookmarklets, or at your own risk.
The following link is a bookmarklet that shows the current URL of the page: Current window URL
The code for this bookmarklet is:
javascript:window.alert(`The URL of this page is: ${window.location}`)
To add this bookmarklet, do the following:
- Make sure your Bookmarks Toolbar is open (in Firefox by pressing Control+Shift+B)
- Drag the link to the toolbar
- In Firefox (at least in Firefox 120, tested on 2024-02-09) you’ll be given a dialog to edit this bookmark
Creating bookmarklets from existing bookmarks
On mobile browsers (like Firefox for Android or Safari on iOS), it doesn’t seem like you can straight-up add a bookmark by entering a name and a URL. A workaround is to bookmark a random page, and then edit the name and URL to contain the bookmarklet’s URL. For example, in Firefox on Android, I’ll bookmark this page, then click Edit Bookmark, edit the name first (pasting in a bookmarklet name), copy bookmarklet code, click Edit Bookmark again and then edit the URL. Editing the name first instead of the URL allows you edit the same bookmark more easily later.
Using bookmarklets
To use a bookmarklet, click the bookmark in your toolbar. Or, if you’ve given it a keyword, type the keyword.
In mobile Firefox, go to your address bar as if you were entering an address, then change the search engine to be Bookmarks. Then start typing the name of the bookmarklet, and click the bookmarklet when it shows up. Previously I thought that going to “Bookmarks” didn’t work well, but maybe it does now as of 2025-10-05.
Examples
Here are some example bookmarklets:
Kill Sticky V2 javascript:(() => { function jmmKillSticky() { const posTypes = new Set(['fixed', 'sticky']); const fixedElements = Array.from(document.querySelectorAll("body *")).filter(el => posTypes.has(getComputedStyle(el).position)); fixedElements.forEach(el => { el.remove(); }); window.alert(`jmm.io killsticky removed ${fixedElements.length} elements`); }; setTimeout(jmmKillSticky); /* jmm.io killsticky v2: Last modified: 2024-02-11 */ })()This bookmarklet removes any element that has a
fixedorstickyposition. Great for removing annoying banners that take up too much vertical space.-
Text→AAAA javascript:(() => { function jmmTextToScream() { function replaceNonwhitespace(char) { const textnodes = Array.from(document.querySelectorAll("*"), el => Array.from(el.childNodes).filter(x => x.nodeType === Node.TEXT_NODE)).flat(); textnodes.forEach(x => { x.data = x.data.replace(/\S+/ig, match => char.repeat(match.length)); }); } replaceNonwhitespace("A"); } setTimeout(jmmTextToScream); /* jmm.io text→scream */ })()This non-useful bookmarklet replaces all text nodes with an equivalent length scream, consisting of a capital letter A (repeated, of course). Doesn’t work for pseudo-elements (e.g. “
::before” or “::after”) where text uses a CSScontent:rule. See /getalltextnodes for how this works. Redact regex javascript:(()=>{ function jmmRedactRegex() { const pattern = window.prompt(`Enter a regexp or word to redact (e.g. "hello" or "[0-9]+")`); const regex = new RegExp(pattern, "ig"); const textnodes = Array.from(document.querySelectorAll("*"), el => Array.from(el.childNodes).filter(x => x.nodeType === Node.TEXT_NODE)).flat(); let counter = 0; textnodes.forEach(x => { x.data = x.data.replace(regex, match => (counter++, "█".repeat(match.length))); }); window.alert(`Redacted ${counter} ${(new Intl.PluralRules("en-US")).select(counter) === "one" ? "match" : "matches"}`); window.counter = counter; } setTimeout(jmmRedactRegex); /* jmm.io redact regex 2024-02-11 */ })()This bookmarklet prompts for a (case-insensitive) regex and replaces all matches with an equivalent length black bar. If the length of the content redacted is sensitive, you should use something else.
-
DOM serializer javascript:(() => { function jmmSerializeDOM() { const serializer = new XMLSerializer(); const str = serializer.serializeToString(document.documentElement); if(str) jmmSaveTextFile(str, window.prompt(`Save serialized DOM as (e.g. "something.txt"):`)); function jmmSaveTextFile (text, filename) { const f = new File([text], filename, { type: "text/plain" }); const url = URL.createObjectURL(f); const ael = document.createElement("a"); ael.href = url; ael.download = filename; ael.textContent = "Download"; document.body.append(ael); ael.click(); ael.remove(); URL.revokeObjectURL(url); } } setTimeout(jmmSerializeDOM); /* jmm.io DOM serializer 2024-02-11 */ })()Serialize the document and save it.
-
Reveal Links javascript:(()=>{ function jmmRevealLinks() { const links = Array.from(document.querySelectorAll(":link")); links.forEach(el => { el.replaceChildren(el.href) }); }; setTimeout(jmmRevealLinks); })()Replace links with their hrefs. Makes it easier to see where you’re going.
-
Links Only javascript:(()=>{ function jmmOnlyLinksGrouped() { const links = Array.from(document.querySelectorAll(":link")); function rep(tag, ...rest) { const el = document.createElement(tag); el.replaceChildren(...rest); return el; } const grouped = Map.groupBy(links.map(jmmMakeLink), x => x.origin); document.body.replaceChildren(...Array.from(grouped, jmmMakeGroup)); [...document.styleSheets].forEach(c => { c.disabled = true; }); function jmmMakeLink(el) { const ael = rep("a", el.textContent || "(Empty)"); ael.href = el.href; return { origin: (new URL(el.href))?.origin, newel: rep("div", rep("dt", ael), rep("dd", el.href)) } } function jmmMakeGroup([k, v]) { return rep("dl", rep("dt", k), rep("dd", ...v.map(x => x.newel))); } }; setTimeout(jmmOnlyLinksGrouped); /* jmm.io only show links grouped 2024-02-11 */ })()Shows page with all links, grouped by origin. Possibly handy for cybersurfing.
And here’s another version that groups identical URLs:
Link Groups 2 javascript:(()=>{ function jmmLinkGroups2() { const links = Array.from(document.querySelectorAll(":link")); function rep(tag, ...rest) { const el = document.createElement(tag); el.replaceChildren(...rest); return el; } function groupMapFlat(arr, keyfn, mapfn) { return Array.from(Map.groupBy(arr, keyfn), mapfn).flat(); } document.body.replaceChildren(...groupMapFlat(links.map(jmmMakeLink), x => x.origin, jmmMakeGroup)); [...document.styleSheets].forEach(c => { c.disabled = true; }); function jmmMakeLink(el) { const ael = rep("a", el.textContent || "(Empty)"); ael.href = el.href; return { origin: (new URL(el.href))?.origin, href: el.href, a: ael}; } function jmmMakeGroup([k, v]) { return rep("dl", rep("dt", k), rep("dd", rep("dl", ...groupMapFlat(v, x => x.href, jmmMakeGroup2)))); } function jmmMakeGroup2([url, links]) { return [...links.map(x => rep("dt", x.a)), rep("dd", url)]; } }; setTimeout(jmmLinkGroups2); /* jmm.io Link Groups 2 2024-02-11 */ })() -
No Media javascript:(() => { function jmmNoMedia() { const mediaTypes = ['audio', 'video', 'img', 'picture']; let counter = 0; mediaTypes.flatMap(x => [...document.querySelectorAll(x)]) .forEach(x => { counter++; x.remove(); }); window.alert(`Removed ${counter} element${(new Intl.PluralRules("en-US")).select(counter) === "one" ? "" : "s"}`); }; setTimeout(jmmNoMedia); /* jmm.io No Media 2024-02-11 */ })()Removes media elements like audio, video, and images. Doesn’t work for background images.
-
No StyleSheets javascript:(() => { function jmmNoStyleSheets() { for (const ss of document.styleSheets) ss.disabled = true; }; setTimeout(jmmNoStyleSheets); /* jmm.io No StyleSheets 2025-09-10 */ })()Just turns off all stylesheets. Probably should add some functionality to look through shadow DOM and turn those off too.
-
Design Mode javascript:(() => { function jmmDesignMode() { document.designMode = document.designMode === "on" ? "off" : "on"; window.alert(`document.designMode is now ${document.designMode}`); }; setTimeout(jmmDesignMode); /* jmm.io Design Mode 2025-10-05 */ })()Toggles
document.designMode, letting you edit stuff. Strangely, in Firefox for Android (Nightly v145.0a1, tested on 2025-10-05), you get a caret and it seems like you can cut/paste stuff, but I don’t get an on-screen keyboard popping up. So this bookmarklet is less useful than the following bookmarklet. -
root contenteditable javascript:(() => { function jmmContentEditable() { const docel = document.documentElement; if (docel.hasAttribute("contenteditable")) { docel.removeAttribute("contenteditable"); window.alert(`contenteditable disabled`); } else { docel.setAttribute("contenteditable", "true"); window.alert(`contenteditable enabled`); } }; setTimeout(jmmContentEditable); /* jmm.io contenteditable 2025-10-05 */ })()Sets the root document node to have
contenteditable="true". This bookmarklet is a workaround for an issue wheredesignModedoesn’t seem to trigger an on-screen keyboard on mobile. On Firefox Nightly (v145.0a1, 2025-10-05) on Android, this correctly triggers an on-screen keyboard. -
BlobNavTest javascript:(() => { function jmmBlobNavTest() { const doc = document.implementation.createDocument( "http://www.w3.org/1999/xhtml", "html", null, ); const xhtml = doc.createElement("html"); console.log(doc.body); const t = doc.createElement("template"); t.innerHTML = `Hello, <b>world</b>!`; doc.documentElement.append(t.content.cloneNode(true)); const s = new XMLSerializer().serializeToString(doc); document.body.append(s); const b = new Blob([s], { type: "application/xhtml+xml" }); const a = document.createElement("a"); const u1 = URL.createObjectURL(b); a.setAttribute("href", u1); /* a.setAttribute("target", "_blank"); */ a.append("Hello"); document.body.append(a); a.click(); }; setTimeout(jmmBlobNavTest); /* jmm.io blob navigation test 2025-10-05 */ })()Testing to see if I can navigate to a blob.
Issues
Firefox Android bookmarklet support
I haven’t found a way to get bookmarklets to work in Firefox for Android.
Okay, I found out that bookmarklets do work in Firefox for Android.
They’re a little bit wonky though.
Bookmarklet timing issue
A simple bookmarklet that just triggers an alert doesn’t seem to work:
javascript:window.alert("Hello!");
Well actually, I noticed that it would sometimes work, and sometimes wouldn’t. I think the issue comes down to timing. That is, there’s not a problem with alerting but with the fact that the script runs before the last tab comes back into focus.
A workaround is to use a setTimeout so the bookmarklet runs on the next event cycle, like so:
javascript:(()=>{setTimeout(()=>{window.alert("Hello!");});})()
However, the following doesn’t work, and I’m not sure why:
javascript:setTimeout(()=>{window.alert("Hello");},0);See also Bugzilla 1811582 - Bookmarklets doesn't work from bookmarks.
Content-Security-Policy (CSP) issues
Another issue is that bookmarklets don’t seem to work with pages that have a Content-Security-Policy (CSP) that prevent external scripts.
See this GitHub blog post on CSP and bugzilla bug 866522 (Bookmarklets affected by CSP).
Support in general
I get the vague sense that bookmarklets are becoming less supported over time due to security concerns.
I think I may have needed to switch some about:config flag to get javascript: URIs to work.