
Firecrawl Scraper
- 575 installs
- 9 repo stars
- Updated April 21, 2026
- benedictking/firecrawl-scraper
firecrawl-scraper is a Claude Code skill with a Node.js CLI that scrapes, crawls, maps, and batch-fetches web pages into agent-ready content via the Firecrawl API without wiring REST calls by hand.
About
firecrawl-scraper is a Claude skill from benedictking/firecrawl-scraper that bundles a Node.js CLI wrapper around Firecrawl API endpoints for agent workflows. Developers configure a FIRECRAWL_API_KEY in .env and invoke commands for scrape, crawl, map, batch-scrape, and crawl-status with JSON payloads from stdin or files. The helper script supports --file payload loading and --wait flags for long crawls. Engineers reach for firecrawl-scraper when building agents, RAG pipelines, or research automations that need clean markdown from live URLs without writing custom HTTPS client code for each Firecrawl endpoint.
- CLI wrapper around Firecrawl scrape, crawl, map, batch-scrape, and crawl-status endpoints
- Accepts JSON via argv, stdin, or --file payloads for repeatable agent workflows
- Optional --wait on long-running crawl jobs with status polling by crawl id
- Loads FIRECRAWL_API_KEY from environment or a local .env next to the helper script
Firecrawl Scraper by the numbers
- 575 all-time installs (skills.sh)
- Ranked #390 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/benedictking/firecrawl-scraper --skill firecrawl-scraperAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 575 |
|---|---|
| repo stars | ★ 9 |
| Security audit | 1 / 3 scanners passed |
| Last updated | April 21, 2026 |
| Repository | benedictking/firecrawl-scraper ↗ |
How do you scrape web pages into agent-ready content with Firecrawl?
Scrape, crawl, map, or batch-fetch web pages into agent-ready content via Firecrawl without wiring REST calls by hand.
Who is it for?
Backend and agent developers who need Firecrawl scrape and crawl endpoints callable from shell scripts and Claude agent workflows.
Skip if: Static sites you already own where sitemap exports suffice, or projects blocked from using paid third-party scraping APIs.
When should I use this skill?
An agent workflow needs to fetch, crawl, or batch-scrape live web URLs into clean content and the developer wants a prebuilt Firecrawl CLI instead of raw REST calls.
What you get
Agent-ready markdown or structured page content from scrape, crawl, map, and batch-scrape Firecrawl API responses.
- Scraped markdown content
- Crawl job results
- Site map data
Files
Firecrawl Scraper Skill
Trigger Conditions & Endpoint Selection
Choose Firecrawl endpoint based on user intent:
- scrape: Need to extract content from a single web page (markdown, html, json, screenshot, pdf)
- crawl: Need to crawl entire website with depth control and path filtering
- map: Need to quickly get a list of all URLs on a website
- batch-scrape: Need to scrape multiple URLs in parallel
- crawl-status: Given crawl job ID, check crawl progress/results (optional
--wait)
Recommended Architecture (Main Skill + Sub-skill)
This skill uses a two-phase architecture:
1. Main skill (current context): Understand user question → Choose endpoint → Assemble JSON payload 2. Sub-skill (fork context): Only responsible for HTTP call execution, avoiding conversation history token waste
Execution Method
Use Task tool to invoke firecrawl-fetcher sub-skill, passing command and JSON (stdin):
Task parameters:
- subagent_type: Bash
- description: "Call Firecrawl API"
- prompt: cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs <scrape|crawl|map|batch-scrape|crawl-status> [--wait]
{ ...payload... }
JSONPayload Examples
1) Scrape Single Page
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs scrape
{
"url": "https://example.com",
"formats": ["markdown", "links"],
"onlyMainContent": true,
"includeTags": [],
"excludeTags": ["nav", "footer"],
"waitFor": 0,
"timeout": 30000
}
JSONAvailable formats:
"markdown","html","rawHtml","links","images","summary"{"type": "json", "prompt": "Extract product info", "schema": {...}}{"type": "screenshot", "fullPage": true, "quality": 85}
2) Scrape with Actions (Page Interaction)
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs scrape
{
"url": "https://example.com",
"formats": ["markdown"],
"actions": [
{"type": "wait", "milliseconds": 2000},
{"type": "click", "selector": "#load-more"},
{"type": "wait", "milliseconds": 1000},
{"type": "scroll", "direction": "down", "amount": 500}
]
}
JSONAvailable actions:
wait,click,write,press,scroll,screenshot,scrape,executeJavascript
3) Parse PDF
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs scrape
{
"url": "https://example.com/document.pdf",
"formats": ["markdown"],
"parsers": ["pdf"]
}
JSON4) Extract Structured JSON
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs scrape
{
"url": "https://example.com/product",
"formats": [
{
"type": "json",
"prompt": "Extract product information",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"description": {"type": "string"}
},
"required": ["name", "price"]
}
}
]
}
JSON5) Crawl Entire Website
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs crawl
{
"url": "https://docs.example.com",
"formats": ["markdown"],
"includePaths": ["^/docs/.*"],
"excludePaths": ["^/blog/.*"],
"maxDiscoveryDepth": 3,
"limit": 100,
"allowExternalLinks": false,
"allowSubdomains": false
}
JSON5.1) Crawl + Wait for Completion
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs crawl --wait
{
"url": "https://docs.example.com",
"formats": ["markdown"],
"limit": 100
}
JSON6) Map Website URLs
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs map
{
"url": "https://example.com",
"search": "documentation",
"limit": 5000
}
JSON7) Batch Scrape Multiple URLs
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs batch-scrape
{
"urls": [
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3"
],
"formats": ["markdown"]
}
JSON8) Check Crawl Status
node .claude/skills/firecrawl-scraper/firecrawl-api.cjs crawl-status <crawl-id>Wait for completion:
node .claude/skills/firecrawl-scraper/firecrawl-api.cjs crawl-status <crawl-id> --waitKey Features
Formats
- markdown: Clean markdown content
- html: Parsed HTML
- rawHtml: Original HTML
- links: All links on page
- images: All images on page
- summary: AI-generated summary
- json: Structured data extraction with schema
- screenshot: Page screenshot (PNG)
Content Control
onlyMainContent: Extract only main content (default: true)includeTags: CSS selectors to includeexcludeTags: CSS selectors to excludewaitFor: Wait time before scraping (ms)maxAge: Cache duration (default: 48 hours)
Actions (Browser Automation)
wait: Wait for specified timeclick: Click element by selectorwrite: Input text into fieldpress: Press keyboard keyscroll: Scroll pageexecuteJavascript: Run custom JS
Crawl Options
includePaths: Regex patterns to includeexcludePaths: Regex patterns to excludemaxDiscoveryDepth: Maximum crawl depthlimit: Maximum pages to crawlallowExternalLinks: Follow external linksallowSubdomains: Follow subdomains
Environment Variables & API Key
Two ways to configure API Key (priority: environment variable > .env):
1. Environment variable: FIRECRAWL_API_KEY 2. .env file: Place in .claude/skills/firecrawl-scraper/.env, can copy from .env.example
Response Format
All endpoints return JSON with:
success: Boolean indicating successdata: Extracted content (format depends on endpoint)- For crawl: Returns job ID, use
crawl-status(or GET /v2/crawl/{id}) to check status
# Firecrawl API Key Configuration
# Get your API key from: https://www.firecrawl.dev/app/api-keys
FIRECRAWL_API_KEY=your_api_key_here
.env
node_modules/
#!/usr/bin/env node
/**
* Firecrawl API Helper Script
* Provides a CLI wrapper around Firecrawl endpoints for skill integration.
*
* Usage:
* node firecrawl-api.js <scrape|crawl|map|batch-scrape|crawl-status> [<json-string>]
* cat payload.json | node firecrawl-api.js scrape
* node firecrawl-api.js scrape --file ./payload.json
* node firecrawl-api.js crawl --wait < payload.json
* node firecrawl-api.js crawl-status <crawl-id> [--wait]
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const API_BASE = 'https://api.firecrawl.dev';
function loadApiKey() {
if (process.env.FIRECRAWL_API_KEY) {
return process.env.FIRECRAWL_API_KEY;
}
const envPath = path.join(__dirname, '.env');
if (!fs.existsSync(envPath)) {
return null;
}
const envContent = fs.readFileSync(envPath, 'utf8');
const match = envContent.match(/FIRECRAWL_API_KEY\s*=\s*(.+)/);
if (!match) {
return null;
}
return match[1].trim().replace(/^[\"']|[\"']$/g, '');
}
function usage() {
const cmd = path.basename(process.argv[1] || 'firecrawl-api.js');
console.error(
[
'Usage:',
` node ${cmd} <scrape|crawl|map|batch-scrape|crawl-status> [<json-string>]`,
` cat payload.json | node ${cmd} scrape`,
` node ${cmd} scrape --file ./payload.json`,
` node ${cmd} crawl --wait < payload.json`,
` node ${cmd} crawl-status <crawl-id> [--wait]`,
'',
'Options:',
' --wait Wait for crawl job completion (crawl / crawl-status only)',
' --id Crawl job id (crawl-status only)',
'',
'Env:',
' FIRECRAWL_API_KEY (env var) or .env file next to this script',
].join('\n'),
);
}
function readStdin() {
return new Promise((resolve, reject) => {
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
data += chunk;
});
process.stdin.on('end', () => resolve(data));
process.stdin.on('error', reject);
});
}
async function readPayload(args) {
const fileFlagIndex = args.findIndex((arg) => arg === '--file');
if (fileFlagIndex !== -1) {
const filePath = args[fileFlagIndex + 1];
if (!filePath) {
throw new Error('Missing value for --file');
}
const content = fs.readFileSync(filePath, 'utf8');
return JSON.parse(content);
}
const dataFlagIndex = args.findIndex((arg) => arg === '--data');
if (dataFlagIndex !== -1) {
const json = args[dataFlagIndex + 1];
if (!json) {
throw new Error('Missing value for --data');
}
return JSON.parse(json);
}
if (args[0] && !args[0].startsWith('-')) {
return JSON.parse(args[0]);
}
if (process.stdin.isTTY) {
throw new Error('No payload provided (pass JSON arg, --data, --file, or pipe via stdin)');
}
const stdin = await readStdin();
if (!stdin.trim()) {
throw new Error('Empty stdin payload');
}
return JSON.parse(stdin);
}
function requestJson(method, endpointPath, apiKey, payload) {
return new Promise((resolve, reject) => {
const body = payload === undefined ? null : JSON.stringify(payload);
const url = new URL(endpointPath, API_BASE);
const req = https.request(
url,
{
method,
headers: {
'Authorization': `Bearer ${apiKey}`,
...(body === null
? {}
: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
}),
'User-Agent': 'Firecrawl-Skill/1.0',
},
timeout: 60_000,
},
(res) => {
let data = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
const ok = res.statusCode && res.statusCode >= 200 && res.statusCode < 300;
if (!ok) {
reject(new Error(`API Error ${res.statusCode}: ${data}`));
return;
}
try {
resolve(JSON.parse(data));
} catch {
resolve(data);
}
});
},
);
req.on('error', reject);
req.on('timeout', () => {
req.destroy(new Error('Request timed out'));
});
if (body !== null) {
req.write(body);
}
req.end();
});
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function takeFlag(args, flag) {
const index = args.indexOf(flag);
if (index === -1) {
return false;
}
args.splice(index, 1);
return true;
}
function takeFlagValue(args, flag) {
const index = args.indexOf(flag);
if (index === -1) {
return null;
}
const value = args[index + 1];
if (!value) {
throw new Error(`Missing value for ${flag}`);
}
args.splice(index, 2);
return value;
}
function extractCrawlJobId(result) {
const candidates = [
result?.id,
result?.jobId,
result?.data?.id,
result?.data?.jobId,
result?.crawlId,
result?.data?.crawlId,
];
return candidates.find((value) => typeof value === 'string' && value.trim().length > 0) || null;
}
function extractCrawlStatus(result) {
const status = result?.status ?? result?.data?.status;
if (typeof status !== 'string') {
return null;
}
return status.trim().toLowerCase();
}
function isTerminalSuccessStatus(status) {
return new Set(['completed', 'complete', 'done', 'success', 'succeeded', 'finished']).has(status);
}
function isTerminalFailureStatus(status) {
return new Set(['failed', 'error', 'cancelled', 'canceled']).has(status);
}
async function getCrawlStatus(apiKey, crawlId) {
const safeId = encodeURIComponent(crawlId);
return requestJson('GET', `/v2/crawl/${safeId}`, apiKey);
}
async function waitForCrawlCompletion(apiKey, crawlId) {
const pollIntervalMs = 3_000;
// Poll forever by default; caller can abort with Ctrl+C.
for (;;) {
const statusResult = await getCrawlStatus(apiKey, crawlId);
const status = extractCrawlStatus(statusResult);
if (!status || isTerminalSuccessStatus(status)) {
return statusResult;
}
if (isTerminalFailureStatus(status)) {
throw new Error(`Crawl job ${crawlId} ended with status "${status}"`);
}
await sleep(pollIntervalMs);
}
}
const ENDPOINT_BY_COMMAND = {
scrape: '/v2/scrape',
crawl: '/v2/crawl',
map: '/v2/map',
'batch-scrape': '/v2/batch-scrape',
};
(async () => {
const command = process.argv[2];
if (!command || command === '--help' || command === '-h') {
usage();
process.exit(command ? 0 : 1);
}
const args = process.argv.slice(3);
if (args.includes('--help') || args.includes('-h')) {
usage();
process.exit(0);
}
const apiKey = loadApiKey();
if (!apiKey) {
console.error('Missing Firecrawl API key: set FIRECRAWL_API_KEY or create .env next to firecrawl-api.js');
process.exit(1);
}
try {
const wait = takeFlag(args, '--wait');
if (command === 'crawl-status') {
const explicitId = takeFlagValue(args, '--id');
let crawlId = explicitId;
if (!crawlId) {
if (args[0] && !args[0].startsWith('-') && !args[0].trim().startsWith('{')) {
crawlId = args[0];
}
const hasJsonPayload =
args.includes('--file') ||
args.includes('--data') ||
(args[0] && args[0].trim().startsWith('{')) ||
!process.stdin.isTTY;
if (!crawlId && hasJsonPayload) {
const payload = await readPayload(args);
crawlId = extractCrawlJobId(payload);
}
}
if (!crawlId) {
throw new Error('Missing crawl id (pass <crawl-id>, --id <crawl-id>, or a JSON payload containing id/jobId)');
}
const result = wait
? await waitForCrawlCompletion(apiKey, crawlId)
: await getCrawlStatus(apiKey, crawlId);
console.log(JSON.stringify(result, null, 2));
return;
}
const endpoint = ENDPOINT_BY_COMMAND[command];
if (!endpoint) {
usage();
process.exit(1);
}
if (wait && command !== 'crawl') {
throw new Error('--wait is only supported for crawl or crawl-status');
}
const payload = await readPayload(args);
const result = await requestJson('POST', endpoint, apiKey, payload);
if (command === 'crawl' && wait) {
const crawlId = extractCrawlJobId(result);
if (!crawlId) {
throw new Error('Missing crawl job id in response (expected id/jobId)');
}
const finalResult = await waitForCrawlCompletion(apiKey, crawlId);
console.log(JSON.stringify(finalResult, null, 2));
return;
}
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
})();
Firecrawl Fetcher Sub-skill
Note: This is an internal sub-skill, invoked by the firecrawl-scraper main skill through the Task tool.Purpose
Execute Firecrawl API calls in an independent context with context: fork, avoiding carrying main conversation context, reducing token consumption.
Received Parameters
Receives complete command through Task's prompt, using stdin for JSON:
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs <scrape|crawl|map|batch-scrape|crawl-status> [--wait]
{ ...payload... }
JSONOutput
Returns Firecrawl API's JSON response as-is (pretty printed).
Related skills
How it compares
Choose firecrawl-scraper over generic curl examples when you need a maintained CLI with crawl-status polling and batch-scrape support for agent pipelines.
FAQ
What Firecrawl endpoints does firecrawl-scraper support?
firecrawl-scraper exposes CLI commands for scrape, crawl, map, batch-scrape, and crawl-status via a Node.js helper script. Developers pass JSON payloads through stdin, files, or inline arguments to call each Firecrawl API operation.
How do you authenticate firecrawl-scraper?
firecrawl-scraper reads a FIRECRAWL_API_KEY from a .env file in the project directory. Developers obtain the key from the Firecrawl dashboard before running scrape or crawl commands.
When should developers use firecrawl-scraper?
Developers should use firecrawl-scraper when agent or backend workflows need live web content without hand-writing HTTPS calls to Firecrawl. The CLI fits RAG ingestion, research agents, and batch URL processing pipelines.
Is Firecrawl Scraper safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.