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.
Top comments (0)