Add Problems to LeetCode List
Uses the LeetCode GraphQL API (addQuestionToFavorite mutation) via a browser session to bulk-add problems to a custom LeetCode problem list.
How LeetCode Lists Work
- Each custom list has a hash (e.g.
dyf6r5b7) visible in its URL:https://leetcode.com/problem-list/<hash>/ - Problems are identified by their numeric ID (the number in the problem URL, e.g.
1for Two Sum) - The API uses the session cookies (especially
csrftoken) from the logged-in browser — no separate auth needed
Prerequisites
- A browser session attached via playwright-cli CDP (usually port 9222)
- User must be logged into LeetCode in that browser
- The target problem list hash (from the URL)
Steps
1. Attach to the browser
playwright-cli attach --cdp=http://localhost:9222
2. Navigate to the target list page (establishes cookies)
playwright-cli goto https://leetcode.com/problem-list/<LIST_HASH>/
3. Add problems via GraphQL
Use playwright-cli run-code --filename=<script> with a script that calls page.evaluate:
async page => {
const results = await page.evaluate(async () => {
const problems = [
{id: '1', name: 'Two Sum'},
{id: '217', name: 'Contains Duplicate'},
// ... more problems
];
const csrftoken = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
const favoriteIdHash = 'dyf6r5b7'; // replace with actual list hash
const results = [];
const query = `mutation addQuestionToFavorite($favoriteIdHash: String!, $questionId: String!) {
addQuestionToFavorite(favoriteIdHash: $favoriteIdHash, questionId: $questionId) {
ok
error
questionId
favoriteIdHash
}
}`;
for (const p of problems) {
const resp = await fetch('/graphql/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-csrftoken': csrftoken,
'Referer': `https://leetcode.com/problem-list/${favoriteIdHash}/`,
'x-operation-name': 'addQuestionToFavorite',
},
body: JSON.stringify({
operationName: 'addQuestionToFavorite',
variables: { favoriteIdHash, questionId: p.id },
query
})
});
const data = await resp.json();
results.push({
name: p.name,
id: p.id,
ok: data?.data?.addQuestionToFavorite?.ok,
error: data?.data?.addQuestionToFavorite?.error
});
await new Promise(r => setTimeout(r, 150)); // polite delay
}
return results;
});
return JSON.stringify(results, null, 2);
}
4. Verify
Reload the page and take a screenshot to confirm the question count.
AlgoMonster 50 Problem IDs
For the AlgoMonster Monster 50 list (read from https://algo.monster/practice/monster50):
| # | Problem | LeetCode ID | Difficulty |
|---|---|---|---|
| 1 | Two Sum | 1 | Easy |
| 2 | Contains Duplicate | 217 | Easy |
| 3 | Best Time to Buy and Sell Stock | 121 | Easy |
| 4 | Valid Palindrome | 125 | Easy |
| 5 | Valid Parentheses | 20 | Easy |
| 6 | Binary Search | 704 | Easy |
| 7 | Reverse Linked List | 206 | Easy |
| 8 | Merge Two Sorted Lists | 21 | Easy |
| 9 | Linked List Cycle | 141 | Easy |
| 10 | Same Tree | 100 | Easy |
| 11 | Maximum Depth of Binary Tree | 104 | Easy |
| 12 | Invert Binary Tree | 226 | Easy |
| 13 | Climbing Stairs | 70 | Easy |
| 14 | Group Anagrams | 49 | Medium |
| 15 | Top K Frequent Elements | 347 | Medium |
| 16 | Longest Consecutive Sequence | 128 | Medium |
| 17 | Container With Most Water | 11 | Medium |
| 18 | 3Sum | 15 | Medium |
| 19 | Longest Substring Without Repeating Characters | 3 | Medium |
| 20 | Longest Repeating Character Replacement | 424 | Medium |
| 21 | Min Stack | 155 | Medium |
| 22 | Daily Temperatures | 739 | Medium |
| 23 | Find Minimum in Rotated Sorted Array | 153 | Medium |
| 24 | Search in Rotated Sorted Array | 33 | Medium |
| 25 | Remove Nth Node From End of List | 19 | Medium |
| 26 | Merge Intervals | 56 | Medium |
| 27 | Insert Interval | 57 | Medium |
| 28 | Non-overlapping Intervals | 435 | Medium |
| 29 | Binary Tree Level Order Traversal | 102 | Medium |
| 30 | Validate Binary Search Tree | 98 | Medium |
| 31 | Kth Smallest Element in a BST | 230 | Medium |
| 32 | Lowest Common Ancestor of a Binary Tree | 236 | Medium |
| 33 | Number of Islands | 200 | Medium |
| 34 | Clone Graph | 133 | Medium |
| 35 | Graph Valid Tree | 261 | Medium (Premium) |
| 36 | Course Schedule | 207 | Medium |
| 37 | Pacific Atlantic Water Flow | 417 | Medium |
| 38 | K Closest Points to Origin | 973 | Medium |
| 39 | Combination Sum | 39 | Medium |
| 40 | Word Search | 79 | Medium |
| 41 | House Robber | 198 | Medium |
| 42 | Coin Change | 322 | Medium |
| 43 | Word Break | 139 | Medium |
| 44 | Longest Increasing Subsequence | 300 | Medium |
| 45 | Trapping Rain Water | 42 | Hard |
| 46 | Minimum Window Substring | 76 | Hard |
| 47 | Word Ladder | 127 | Hard |
| 48 | Merge K Sorted Lists | 23 | Hard |
| 49 | Find Median from Data Stream | 295 | Hard |
| 50 | N-Queens | 51 | Hard |
Notes
- The mutation is idempotent — adding a problem that's already in the list returns
ok: truewithout duplicating it questionIdis a string, not an integer — pass the problem number as a string- Graph Valid Tree (#261) is LeetCode Premium and may not appear in the public list count even though the API returns
ok: true - The browser automatically sends session cookies (auth) — no manual cookie handling needed
- The
csrftokencookie value must also be set as thex-csrftokenheader — this is Django's double-submit CSRF protection (cookie alone isn't enough, header must match cookie) - All fetch calls must be made via
page.evaluate()so they run inside the browser context (same-origin, so cookies are sent automatically)