You are the admin of a group with 400 members and you need those numbers in a spreadsheet — for an event list, a CRM import, a reminder to people who asked to be reminded. WhatsApp gives you no export button. Copying numbers by hand takes an afternoon and still produces typos.
This post shows how to export WhatsApp group contacts to CSV using a Tampermonkey userscript that reads the member list already sitting in your browser, tags each number with a country, and writes a file Excel or Google Sheets can open. The full script is below, and so are the parts of it that break.
Why WhatsApp Web has no export button
WhatsApp ships no contact export, and that is a product decision rather than a technical limit. What it does do is render the group member list into the page as ordinary HTML, and anything rendered into a page can be read by JavaScript running inside that same page.
Open a group, click the group name, scroll down to the member list, and every row is a DOM node — the DOM being the live tree of elements your browser builds out of a page. For members who are not saved in your phone's address book, the number sits in the title attribute of a span. That single fact is the whole trick.
The userscript that exports WhatsApp group contacts to CSV
A userscript is a piece of JavaScript that a browser extension injects into one specific site every time you open it. Tampermonkey is the extension that manages them, it runs on Chrome, Edge, Firefox and Brave, and it is one of the tools I keep in daily rotation.
Install Tampermonkey from the official Tampermonkey documentation and download page, open its dashboard, create a new script, paste the file in, save with Ctrl+S, then reload web.whatsapp.com. A green panel appears in the bottom-right corner.
Here is the whole script:
What each piece is doing
Function | Job |
|---|---|
| Walks every |
| Longest-prefix match against the country map: tries 4 digits, then 3, 2, 1 |
| Builds the text, wraps it in a Blob, triggers a download with no server involved |
| Stores your selected country codes with |
The digit range is not decoration. E.164, the ITU recommendation that governs international phone numbering, caps a number at 15 digits, so anything longer is a false positive by definition.
The download itself uses a Blob, which is a chunk of data the browser holds in memory and can hand to a download link. Nothing leaves your machine — the MDN reference on the Blob interface covers the mechanics if you want them.
Filtering contacts by country code
The panel builds a clickable chip for every country code it finds in the scan, and clicking one narrows both the table and the export. Your selection persists between sessions, so a filter set to +92 and +44 is still set when you open WhatsApp tomorrow.
Matching runs longest prefix first:
js
function detectCountryCode(number) {
for (const len of [4, 3, 2, 1]) {
const stripped = number.replace(/\s/g, '');
const prefix = stripped.substring(0, len + 1); // +1 for the '+'
if (COUNTRY_CODES[prefix]) return prefix;
}
const m = number.match(/^(\+\d{1,4})/);
return m ? m[1] : '+?';
}
The built-in map has no ambiguous entries today, so the order looks redundant. It stops mattering the moment you extend it: add +1242 for the Bahamas next to the existing +1, and a shortest-first loop would label every Bahamian number as US/CA. Codes that miss the map fall through to the regex and show up as Unknown, which is your cue to add them.
What lands in the CSV
Exporting WhatsApp group contacts to CSV gives you three columns, one row per unique number, every field wrapped in double quotes so the leading + survives the trip into a spreadsheet.
Phone Number | Country Code | Country |
|---|---|---|
+92 300 1234567 | +92 | PK |
+44 7700 900123 | +44 | GB |
+1 415 555 0142 | +1 | US/CA |
The file arrives as whatsapp_contacts_1753900000000.csv — that timestamp keeps a second export from silently overwriting the first. The quoting follows RFC 4180, the specification for comma-separated values, which is why a field containing a comma does not blow up the row.
After that it is a spreadsheet problem: dedupe against the list you already have, normalise the formatting, push it into Sheets or Postgres on a schedule. That cleaning and storage step is usually where I end up writing a second script, and it is covered under the data pipeline and scheduling work I take on.
Where this breaks, and what to do about it
The two failures you will actually hit are an empty scan and a contact count lower than the group size. Both come from how WhatsApp renders lists, not from anything wrong in the code. The same pattern shows up across my other browser scraping write-ups, so it is worth understanding once.
The member list is virtualised. WhatsApp keeps only the rows near your viewport in the page and discards the rest as you scroll, so a scan captures what is on screen and nothing more. Scroll the member list slowly to the bottom first, then scan. If you scan more than once, note that each press replaces the previous result wholesale — merge instead:
js
const key = n => n.replace(/\s/g, '');
const merged = new Map(allContacts.map(c => [key(c.number), c]));
extractContacts().forEach(c => merged.set(key(c.number), c));
allContacts = [...merged.values()];
Keying on the space-stripped number also patches the deduplication hole in the original: +92 300 1234567 and +923001234567 are different strings, so the raw seen Set happily stores both.
Saved contacts show a name, not a number. If a member is already in your address book, WhatsApp puts their name in the title attribute, the startsWith('+') check skips them, and your export quietly comes up short. Reading names as well means walking each member row and pulling two child nodes rather than collecting every span[title].
Selectors rot. span[title] is deliberately generic because WhatsApp ships obfuscated class names that change between releases; a generic selector outlives a specific one, but it is not permanent. When the panel says "No contacts detected yet" on a group you can plainly see, assume the markup moved before you assume the group is empty, and inspect one row in DevTools.
Missing @grant lines throw. Delete the // @grant GM_setValue header line and the console returns Uncaught ReferenceError: GM_setValue is not defined the first time you click a filter chip. Tampermonkey only exposes those helpers to scripts that declare them up front.
Excel mangles the output. Double-click the CSV and Excel may strip the leading + or render a long number in scientific notation. Open it through Data → From Text/CSV and set the phone column type to Text. Google Sheets respects the quoting better but still needs the column formatted as plain text before you sort.
Consent, terms and personal data
A phone number attached to a person is personal data in most jurisdictions, and being technically able to read something is not the same as being cleared to store it. I can build the extraction; the decision to run it against a live account is yours to make.
Two practical notes before you export WhatsApp group contacts to CSV. WhatsApp's terms of service govern how you may interact with the client, and they are worth reading before you install anything. And if the numbers are heading into a marketing list, the rules that bite are the ones covering unsolicited messages in your recipients' countries, not the ones covering scraping.
Frequently Asked Questions
Can I export a WhatsApp group without an extension?
Not natively. WhatsApp's own "Export chat" feature sends you the message history as a text file, and numbers appear in it only for members who have actually posted something. No menu anywhere in the app produces a member list, which is why every working way to export WhatsApp group contacts to CSV reads the rendered page instead.
Does this work on the WhatsApp phone app?
No. A userscript needs a desktop browser to inject JavaScript into, so it runs on WhatsApp Web only. The Android and iOS apps draw their member lists natively, well out of reach of Tampermonkey, and the standalone Windows and macOS clients are closed to userscripts for the same reason.
Why does the scan find fewer contacts than the group has?
Usually two reasons stacked together. WhatsApp only renders rows near your viewport, so members you have not scrolled past do not exist in the page yet. On top of that, anyone saved in your address book shows a name rather than a number, and the script's + check skips them.
Can I export names alongside the numbers?
Yes, though it needs a different approach. The name and the number live in separate nodes within the same member row, so instead of collecting every span[title] you iterate the rows and read both children. That is a rewrite of extractContacts() rather than a setting you toggle.
Will WhatsApp ban my account for running this?
I cannot promise either way, and anyone who does is guessing. The script sends no requests to WhatsApp's servers and reads only text your browser has already drawn on screen. Whether that sits inside the terms you agreed to is a judgement you make before you install it, not after.
If your list spans several groups, or the numbers need to land in Google Sheets on a schedule rather than your downloads folder, that is a build rather than a script. Tell me what you need on the page where I quote scraping jobs.
