If you've ever exported cookies with curl -c, yt-dlp, a browser extension or an antidetect browser, you've met cookies.txt — the Netscape cookie format. And if you've tried to import that file into something that expects JSON (Cookie-Editor, EditThisCookie, Puppeteer, Playwright, most antidetect browsers), you've probably had to convert it.
Here's how the format works, the one bug almost every quick converter has, and a tiny converter that gets it right.
The Netscape format in 30 seconds
One cookie per line, 7 fields separated by tabs:
.example.com TRUE / TRUE 1893456000 sid abc123 | # | Field | Example | Meaning |
|---|---|---|---|
| 1 | domain | .example.com | Cookie domain |
| 2 | include subdomains | TRUE | TRUE = sent to subdomains too |
| 3 | path | / | Cookie path |
| 4 | secure | TRUE | HTTPS only |
| 5 | expiration | 1893456000 | Unix time in seconds, 0 = session cookie |
| 6 | name | sid | |
| 7 | value | abc123 |
Lines starting with # are comments. Almost.
The #HttpOnly_ trap
curl, yt-dlp and many browser exporters write HttpOnly cookies like this:
#HttpOnly_.example.com TRUE / TRUE 1893456000 auth_token secret It looks like a comment, so the classic one-liner converter skips it:
if (line.startsWith('#')) continue; // 💥 auth_token is gone The problem: HttpOnly cookies are usually exactly the ones you need — session and auth tokens. You import the "converted" file, and you're mysteriously logged out.
The fix is to check for the prefix before treating the line as a comment:
let httpOnly = false; if (line.startsWith('#HttpOnly_')) { httpOnly = true; line = line.slice('#HttpOnly_'.length); } else if (line.startsWith('#')) { continue; } The JSON side
The JSON format that Cookie-Editor, EditThisCookie and most antidetect browsers import looks like this:
{ "domain": ".example.com", "hostOnly": false, "path": "/", "secure": true, "httpOnly": true, "session": false, "name": "auth_token", "value": "secret", "expirationDate": 1893456000 } Two mappings people get wrong:
-
hostOnlyis the inverse of "include subdomains".TRUEin the Netscape file meanshostOnly: false. - Expiration
0means a session cookie. Setsession: trueand leave outexpirationDateinstead of writingexpirationDate: 0(some importers treat that as "expired in 1970" and drop the cookie).
One more edge case: a cookie value can contain a tab. Split the line into fields and join everything after the 6th field back together instead of taking parts[6].
A converter that handles all of it
I put the whole thing into a single-file, zero-dependency converter: stockrush-org/netscape-to-json.
CLI:
npx github:stockrush-org/netscape-to-json cookies.txt > cookies.json # and back npx github:stockrush-org/netscape-to-json --reverse cookies.json > cookies.txt Node.js:
const { netscapeToJson, jsonToNetscape } = require('netscape-to-json'); const cookies = netscapeToJson(fs.readFileSync('cookies.txt', 'utf8')); Using it with Puppeteer:
const cookies = netscapeToJson(fs.readFileSync('cookies.txt', 'utf8')) .map(({ expirationDate, hostOnly, session, ...c }) => ({ ...c, expires: expirationDate ?? -1, })); await page.setCookie(...cookies); If you don't want to install anything, there's a free online version — it runs in the browser, nothing gets uploaded.
TL;DR
- Netscape
cookies.txt= 7 tab-separated fields per line. -
#HttpOnly_lines are cookies, not comments — don't skip them. -
hostOnly= NOT "include subdomains"; expiration0= session cookie. - Join the value back if it contains tabs.
Found an edge case it doesn't handle? Issues and PRs are welcome.