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.
To use a bookmarklet, click the bookmark in your toolbar. Or, if you’ve given it a keyword, type the keyword.
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
fixed
orsticky
position. 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.
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.