Hashtag Extractor – Pull Every Tag Out of Any Text
Paste a caption, a draft post, a spreadsheet column or a post you copied from somewhere else, and this hashtag extractor gives you back every tag in it — de-duplicated, counted, ordered and ready to copy out in whatever shape you need. The whole job happens in your browser. Nothing is uploaded, nothing is stored, and no network request of any kind is made. The tool reports what is in your text and how long it is; it does not suggest tags or comment on them.
Why a hand-written regular expression gets this wrong
The obvious pattern, #[A-Za-z0-9_]+, fails in two directions at once. It silently drops every non-Latin tag — #日本, #مرحبا, #новости, #हिंदी — because the character class has no letters outside ASCII in it. And it happily picks up things that are not tags at all: the fragment of https://example.com/docs#installation, the local part of hi#dev@example.com, the sharp in C# and the number in item#3.
This tool is built the other way round. The grammar uses Unicode property escapes, so letters in every script are letters and combining marks stay attached to their base letter. Before a single tag is read, the text is scanned for stretches that are off limits — web addresses, email addresses and, if you switch it on, code — and any match inside one is discarded. The text itself is never rewritten, so the original string survives intact for the preview.
The preview shows the decisions, not just the answers
Below the input your text is redrawn exactly as typed, with every judgement marked: extracted tags highlighted, protected stretches muted under a dotted underline reading inside a URL — not extracted, and skipped markers struck through with the reason attached. It answers “why didn’t it pick up #42?” without you having to ask. Each piece is an ordinary text node, so nothing pasted in can become markup or a live link.
The rules, spelled out
A marker opens a tag only when the character before it is not part of a tag body — that is what keeps C#, F# and a#b out. A run of markers collapses and the body is read after the last one, so ##Friday is #Friday. The body runs until the first character that is not a letter, combining mark, digit or underscore, which is why #foo-bar gives #foo and #end. gives #end. A body with no letters in it is not a tag, so #2024 and #___ are rejected — but #Top10 is fine, and every rejection appears in a short skipped list rather than vanishing. The fullwidth # common in Japanese, Chinese and Korean posts is always a marker; ♯ and ⌗ sit behind a toggle that is off by default.
Intl.Segmenter on a locale pinned to English so the same text can never count differently on two machines. String.length would call #👨👩👧 eight characters; a reader calls it two.Grouping, ordering and getting the tags back out
#Coffee, #coffee and #COFFEE are one row counted three times. The matching key is normalised to NFC and lower-cased with a pinned locale, so an accent typed as a combining mark groups with the precomposed form and a Turkish keyboard cannot change the result. The spelling shown is always the first one you typed, with the alternates listed underneath. Three sort orders are offered — first appearance, most frequent and alphabetical — and each is a complete order, with first appearance breaking every tie, so the same input always produces the same list.
The output box rebuilds the tags with your choice of separator, with or without the marker, as typed or lower-cased, and exports to .txt, .csv and .json. The CSV is quoted per RFC 4180 and any cell opening with =, +, - or @ is neutralised, so a tag like #=cmd arrives in a spreadsheet as text rather than a formula. Beside it sits the caption on its own, tags stripped and whitespace collapsed, plus a “will this fit?” panel whose character limit is an editable number carrying the date it was written down — platform limits change without notice.