---
title: "X-Pat — country flags on X profiles, from X's own data"
description: "Case study: X-Pat (x-profile-location), a Manifest V3 browser extension that shows an X account's country inline — two priority queues and a paced request budget that keep X's 50-per-15-minute limit from running dry."
canonical: "https://asmyshlyaev177.dev/projects/x-profile-location"
image: "https://asmyshlyaev177.dev/og_image.png"
---

[All projects](https://asmyshlyaev177.dev/projects.md)

Published extension · Smart requests queue · community cache

# X-Pat

X-Pat, a browser extension that puts an X account's country in the hover card and the feed, using X's own location API through your existing session. The feed fills in as you scroll without draining the endpoint's rate limit, and accounts can be collapsed, hidden or highlighted by country, organisation, age or bio keyword.

 ![X-Pat screenshot](https://asmyshlyaev177.dev/_vercel/image?url=_astro%2Fx-profile-location.CQm7dHdN.png&w=1024&q=100)

## The challenge

X already knows which country an account posts from and shows it two taps deep, which is two taps more than anyone spends mid-scroll. Moving it into the feed means fetching it before the reader looks — and the endpoint allows 50 requests per 15 minutes, counted against the X session rather than the tab. Spend that the obvious way and a single scroll empties it. Then nothing answers: not the extension, and not the profile you open yourself to check by hand, until the window resets. That is the state the other extensions in this category leave you in, and it is why the budget, not the rendering, is the actual product.

## My role

Sole author. Built the MV3 extension in TypeScript around the scheduler the rest of it hangs on: two priority queues, the feed draining fully before an opened thread's replies get a turn, paced at what is left of the window divided by what is left of the budget — about 21 seconds a lookup, self-correcting as manual hovers stretch the gap. It stops at 85% of the window, so checking a profile by hand always has requests left to spend, and one ledger in the service worker covers every open tab because the limit is per session. Also wrote the filtering rules (country, organisation, account age, bio keyword) and the shared cache server that fronts the same API for every user: one set of handlers running on either Cloudflare Workers + D1 or Node + SQLite through a small database interface, with one latest vote per client per key so a single client cannot poison a value.

## Result

Flags land as you scroll and a profile you open by hand still answers — which is what the reserve is for. The community cache serves most accounts before a request is spent on them, and 5% of each window goes back to re-checking accounts already cached, least-corroborated first, so a location that went stale gets corrected rather than believed for a month. On the Chrome Web Store, running on Chrome, Brave and Edge: two runtime dependencies, no account, no tracking, 1007 unit tests and a Playwright suite against the real X markup.

## Built with

-   TypeScript
-   Browser extension
-   MV3
-   Playwright
-   Cloudflare Workers
-   SQLite

[Website](https://x-pat.pages.dev) · [Chrome Web Store](https://chromewebstore.google.com/detail/x-profile-location/mooomapkphlmpilnlcnpoilondlppbhi) · [Source](https://github.com/asmyshlyaev177/x-profile-location)

More on this

[See where any X account posts from, without leaving your feed](https://asmyshlyaev177.dev/blog/x-profile-location-extension.md)

Why I built X Profile Location: a browser extension that flags an X account's country inline, using X's own API through your existing session. Most of the design fell out of a 50-requests-per-15-minutes rate limit.

I'm available for full-time remote work in EU timezones. Happy to talk through anything here in more detail.

[Get in touch](https://asmyshlyaev177.dev/#contact)

---

Site summary for LLMs: <https://asmyshlyaev177.dev/llms.txt> · Blog: <https://asmyshlyaev177.dev/blog>
