This workflow follows the Agent → Chat Trigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Firecrawl Web Crawler Agent",
"settings": {
"executionOrder": "v1"
},
"nodes": [
{
"parameters": {
"content": "## Firecrawl Web Crawler Agent\n**What it does:** A conversational web research agent with full Firecrawl tool coverage (scrape, search, map, crawl, batch, extract, autopilot, browser sandbox) plus a Postgres credit ledger and 24h scrape cache.\n\n**Architecture:** Chat Trigger \u2192 LangChain Tools Agent (Claude Sonnet 4.6 via OpenRouter) \u2192 10 tools wired into 9 sub-workflows + a `think` scratchpad.\n\n### Required credentials\n1. **Firecrawl API** \u2014 HTTP Header Auth with `Authorization: Bearer fc-...`. Attach to every sub-workflow's HTTP Request node.\n2. **OpenRouter API** \u2014 attach to the `OpenRouter Chat Model` node (or swap for any LangChain-supported LLM).\n3. **Postgres** (writable) \u2014 attach to `Postgres Chat Memory` and every Postgres node inside the sub-workflows. Use the `firecrawl_agent_rw` role created by `sql/01-setup.sql`.\n\n### Sub-workflows required (import first, then re-link IDs)\n- `[FC Sub] Scrape URL with 24h Cache` \u2192 `scrape_url`\n- `[FC Sub] Search the Web` \u2192 `search_web`\n- `[FC Sub] Map a Website` \u2192 `map_site`\n- `[FC Sub] Crawl Site (Bounded)` \u2192 `crawl_site`\n- `[FC Sub] Batch Scrape URLs` \u2192 `batch_scrape`\n- `[FC Sub] Extract Structured Data` \u2192 `extract_data`\n- `[FC Sub] Firecrawl Autopilot Agent` \u2192 `firecrawl_autopilot`\n- `[FC Sub] Browser Sandbox Session` \u2192 `browser_session`\n- `[FC Sub] Check Credit Spend` \u2192 `check_credits`\n\n### Before activating\n1. Run `sql/01-setup.sql` in your Postgres.\n2. Create the 3 credentials above.\n3. Import sub-workflows 03-11, then open this workflow and re-select each tool's sub-workflow from the dropdown.\n4. Optional: INSERT into `firecrawl_allowed_domains` to enforce a strict domain allowlist (leave empty for unrestricted research).\n\n### Demo prompts\n- *Research what products firecrawl.dev offers and compare with crawl4ai.com \u2014 give me a pricing + features table.*\n- *Map docs.n8n.io, then batch-scrape the 10 pages most relevant to AI agents and summarize.*\n- *Extract the job title and salary range from jobs.lever.co/firecrawl using a schema.*\n- *How many credits have we spent this session?*\n- *Scrape stripe.com/pricing and give me their Standard plan details with citations.*",
"height": 720,
"width": 800,
"color": 6
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-700,
-780
],
"id": "sticky-fc-main-header",
"name": "README"
},
{
"parameters": {
"content": "### 1. Chat Trigger\nUser-facing entry point. `mode: hostedChat` exposes a public chat URL you can share or embed. `sessionId` (auto-generated per conversation) is what isolates memory + credit tracking per user.\n\nFor private use: set `public: false` and embed via the `@n8n/chat` widget with a JWT in `metadata`.\n\nFor programmatic use, see the companion `02-webhook-firecrawl-agent` workflow.",
"height": 280,
"width": 320,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-700,
-20
],
"id": "sticky-fc-trigger",
"name": "Trigger note"
},
{
"parameters": {
"content": "### 2. The Agent\nLangChain **Tools Agent** (maxIterations=12). The system message encodes the winning patterns:\n- **Map-first** for site research\n- **Cache-aware** (trust 24h cache_hit)\n- **Credit-discipline** (check_credits every 5 calls)\n- **Citation-first output** (inline `[n]` + Sources table)\n- **Tool decision table** (exact tool per task type)",
"height": 280,
"width": 320,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-360,
-40
],
"id": "sticky-fc-agent",
"name": "Agent note"
},
{
"parameters": {
"content": "### 3. Brain (LLM + Memory)\n**OpenRouter \u2192 Claude Sonnet 4.6** at temperature 0.2. Swap the `model` field for any OpenRouter-supported model.\n\n**Postgres Chat Memory:** conversation history in `public.n8n_chat_histories`, keyed by the chat sessionId. Last 20 messages persist per turn.",
"height": 240,
"width": 320,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-520,
520
],
"id": "sticky-fc-brain",
"name": "Brain note"
},
{
"parameters": {
"content": "### 4. Discovery tools (read-lite)\n**search_web** \u2014 Firecrawl web search, 2 credits per 10 results.\n**map_site** \u2014 URL inventory of a site, 1 credit flat.\n**check_credits** \u2014 pure Postgres read, 0 credits.\n\nUse these BEFORE expensive operations. The winning pattern: `map_site` \u2192 filter via `think` \u2192 `batch_scrape`.",
"height": 280,
"width": 400,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-180,
520
],
"id": "sticky-fc-discovery",
"name": "Discovery tools"
},
{
"parameters": {
"content": "### 5. Scrape tools (page content)\n**scrape_url** \u2014 single URL, 1 credit, 24h cached.\n**batch_scrape** \u2014 up to 25 URLs in parallel, 1 credit each.\n**crawl_site** \u2014 bounded recursive crawl (max 50 pages, depth 3).\n**extract_data** \u2014 LLM structured extraction, 5 credits per URL.",
"height": 280,
"width": 400,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
260,
520
],
"id": "sticky-fc-scrape",
"name": "Scrape tools"
},
{
"parameters": {
"content": "### 6. Autopilot + Sandbox tools\n**firecrawl_autopilot** \u2014 hands off to Firecrawl's own server-side agent for multi-page tasks with unknown structure.\n**browser_session** \u2014 persistent browser (create/execute/delete). Required for login-gated or heavy-JS sites.\n**think** \u2014 scratchpad. Use before any expensive call and after any tool error.",
"height": 280,
"width": 400,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
700,
520
],
"id": "sticky-fc-autopilot",
"name": "Autopilot tools"
},
{
"parameters": {
"public": true,
"mode": "hostedChat",
"initialMessages": "Hi! I'm your Firecrawl web research agent.\nI can search, scrape, map, crawl, and extract structured data from the open web \u2014 then give you citation-backed answers.\n\nTry: *Research what Firecrawl offers and compare with Apify \u2014 give me a features table with citations.*",
"options": {
"title": "Firecrawl Web Research Agent",
"subtitle": "Ask. I research. You get citations.",
"responseMode": "lastNode"
}
},
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.1,
"position": [
-680,
0
],
"id": "fc-chat-trigger",
"name": "When chat message received"
},
{
"parameters": {
"promptType": "auto",
"options": {
"maxIterations": 12,
"systemMessage": "You are a senior web research analyst powered by Firecrawl. Your job is to turn user questions into well-sourced, citation-dense research briefs \u2014 not just raw scraped text.\n\n## Your tools and when to call them\n\n**Discovery (cheap, call liberally):**\n- `search_web(query, limit?, scrape_results?)` \u2014 Firecrawl web search. 2 credits per 10 results. Use when you don't know which URLs matter.\n- `map_site(url, search?, limit?)` \u2014 returns every URL on a site. 1 credit flat. Use this BEFORE crawling or batch-scraping any site.\n- `check_credits(session_id?, credit_cap?)` \u2014 pure Postgres read, 0 credits. Call this every 5 tool calls and BEFORE any crawl/batch/extract call.\n\n**Scrape (page content \u2014 costs credits):**\n- `scrape_url(url, formats?, only_main_content?, include_screenshot?, force_refresh?)` \u2014 single URL, 1 credit, 24h cached. `cache_hit: true` means 0 credits charged. Trust the cache unless the user asks for fresh data.\n- `batch_scrape(urls[], formats?)` \u2014 up to 25 URLs in parallel, 1 credit each. Prefer this over looping scrape_url.\n- `crawl_site(url, max_pages?, max_depth?, include_patterns?, exclude_patterns?)` \u2014 bounded recursive crawl. Hard caps: 50 pages, depth 3. Last resort when you MUST traverse the whole site.\n- `extract_data(urls[], prompt?, schema?)` \u2014 LLM-powered structured extraction. 5 credits per URL. Use only when you need schema-conforming JSON, not free-form text.\n\n**Advanced:**\n- `firecrawl_autopilot(prompt, url?, max_wait?)` \u2014 hand the goal to Firecrawl's own server-side agent. Best for multi-page tasks where you don't know the site structure. Variable cost.\n- `browser_session(action, session_id?, code?, language?, url?)` \u2014 persistent browser for login-gated or heavy-JS sites. Actions: `create`, `execute`, `delete`, `list`. ALWAYS call `delete` when done \u2014 stale sessions hold resources.\n- `think(thought)` \u2014 your scratchpad. Call before complex plans, to decompose multi-step tasks, and ALWAYS after a tool error before retrying.\n\n## THE WINNING PATTERNS \u2014 follow these in order\n\n### Pattern 1: Map-first research on a site\nIf the user asks about a *site* (vs a specific URL), DO NOT crawl blind. Do this:\n1. `map_site(url)` \u2192 get every URL (1 credit)\n2. `think` \u2192 filter the URL list to the 5-15 most relevant ones\n3. `batch_scrape(filtered_urls)` \u2192 scrape only what matters\n\nThis pattern routinely saves 30-80% of credits vs `crawl_site`. Use it whenever you can.\n\n### Pattern 2: Use the cache\n- If you scraped a URL earlier in this conversation, trust it. Don't re-scrape.\n- When `scrape_url` or `batch_scrape` returns `cache_hit: true`, that means 0 credits were charged. Note this in your observations.\n- Pass `force_refresh: true` ONLY when the user explicitly asks for fresh data or you know the page changed.\n\n### Pattern 3: Credit discipline\n- Call `check_credits` every 5 tool calls and BEFORE any `crawl_site`, `batch_scrape`, `extract_data`, or `firecrawl_autopilot`.\n- If `over_budget: true` or `near_cap: true`, STOP calling new tools. Summarize with what you have and tell the user your budget is exhausted.\n- Include a credit-usage line in your final answer's observations (e.g., `Used 12 Firecrawl credits this turn.`).\n\n### Pattern 4: Citation-first output\nEvery factual claim must carry an inline `[n]` citation. Sources map to the numbered list in the Sources section. No citation = no claim. This is non-negotiable.\n\n### Pattern 5: Tool selection decision table\n| User intent | Right tool |\n|---|---|\n| \"What does X say?\" (single URL) | `scrape_url` |\n| \"Find pages about X\" | `search_web` |\n| \"What's on site Y?\" | `map_site` \u2192 `batch_scrape` on selected URLs |\n| \"Scrape these 10 URLs\" | `batch_scrape` |\n| \"Exhaustive crawl of a section\" | `crawl_site` with tight `include_patterns` |\n| \"Extract prices / job titles / spec fields\" | `extract_data` with a schema |\n| \"Navigate a heavy-JS or logged-in site\" | `browser_session` |\n| \"Autonomous deep research, you figure it out\" | `firecrawl_autopilot` |\n| \"How much have I spent?\" | `check_credits` |\n\n## Hard rules\n- **No writes, no destructive ops.** You only read the web and read/write the credit ledger + cache.\n- **Respect allowlist.** If `firecrawl_allowed_domains` is populated, the sub-workflows block disallowed URLs. Don't try to work around it \u2014 ask the user to add the domain.\n- **Respect robots.txt signals** in Firecrawl responses. If Firecrawl returns an error indicating a page is blocked, report the restriction to the user; don't try alternative extractions without explicit confirmation.\n- **Never invent URLs.** Only cite URLs you actually scraped or that came back from `search_web` / `map_site`.\n- **Never invent facts.** Everything in your answer must trace back to a scraped page. If you can't source it, say so.\n- **Cap enforcement is in the sub-workflows.** You don't have to police `max_pages` or `limit` \u2014 the sub-workflows clamp inputs. But prefer tighter limits than the caps to save credits.\n- **On tool error:** call `think` \u2192 diagnose (wrong URL, timeout, schema mismatch) \u2192 retry ONCE with a fix. After 2 failures, ask the user.\n- **maxIterations is 12.** Budget your tool calls. If you're at 10 calls and haven't got enough, stop and answer with what you have.\n\n## Output format \u2014 STRICT\nEvery response MUST have these 5 sections in order:\n\n### 1. Headline\nONE bold sentence with the key finding + the most important numbers. Example:\n`**Firecrawl charges $0 for 500 credits/mo on Hobby and $19/mo for 10K credits on Standard [1], while Apify starts at $49/mo for a $49 credit pool [2].**`\n\n### 2. Summary\n2-4 short paragraphs answering the user's question. Every factual claim carries an inline `[n]`. Numbers, product names, quotes, and dates all need citations. Use the format:\n- Claim \u2192 `[1]`\n- Multi-source claim \u2192 `[1][3]`\n- Quoted text \u2192 `\"exact quote\" [2]`\n\n### 3. Evidence table\nA markdown pipe table with three columns: `Finding`, `Source`, `Confidence` (high/medium/low based on whether the source states it directly vs implies it).\n\n```\n| Finding | Source | Confidence |\n| --------------------------------- | ------ | ---------- |\n| Firecrawl Hobby tier is free | [1] | high |\n| Apify starts at $49/mo | [2] | high |\n| Firecrawl added sandbox in 2026 | [3] | medium |\n```\n\n### 4. Sources\nNumbered list, one line per source:\n```\n[1] Firecrawl Pricing \u2014 https://firecrawl.dev/pricing (scraped 2026-04-23)\n[2] Apify Pricing \u2014 https://apify.com/pricing (scraped 2026-04-23)\n```\n- Use the actual scraped URL and the actual `scraped_at` timestamp from the tool response.\n- The numbering in the text, table, and this list must line up exactly.\n\n### 5. Observations\n2-4 bullets of NON-OBVIOUS insights:\n- Trade-offs not explicit on either page\n- Gaps in the source material (what wasn't stated)\n- Credit usage note: `Used N Firecrawl credits this turn (M cache hits).`\n- A concrete follow-up the user could ask\n\nSeparate each section with a blank line. No greetings, no \"Here is your analysis\", no closing pleasantries. Keep the whole response tight."
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 1.9,
"position": [
-340,
0
],
"id": "fc-agent-main",
"name": "Firecrawl Web Agent"
},
{
"parameters": {
"model": "anthropic/claude-sonnet-4.6",
"options": {
"temperature": 0.2
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
-500,
260
],
"id": "fc-llm",
"name": "OpenRouter Chat Model"
},
{
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ $('When chat message received').item.json.sessionId }}",
"contextWindowLength": 20,
"tableName": "n8n_chat_histories"
},
"type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
"typeVersion": 1.3,
"position": [
-340,
260
],
"id": "fc-memory",
"name": "Postgres Chat Memory"
},
{
"parameters": {
"name": "search_web",
"description": "Searches the open web via Firecrawl. Returns up to `limit` ranked results with title, URL, description, and optionally scraped markdown. Costs 2 credits per 10 results (plus 1 per result if scrape_results=true). Inputs: query (required), limit (default 5, max 20), scrape_results (default false).",
"workflowId": {
"__rl": true,
"value": "Fc04SubSearch01x",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"query": "={{ $fromAI('query', 'The search query string.', 'string') }}",
"limit": "={{ $fromAI('limit', 'How many results to return (1-20).', 'number') }}",
"scrape_results": "={{ $fromAI('scrape_results', 'If true, scrape each result and include markdown (adds 1 credit per result).', 'boolean') }}",
"session_id": "={{ $('When chat message received').item.json.sessionId }}"
},
"matchingColumns": [],
"schema": [
{
"id": "query",
"displayName": "query",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "limit",
"displayName": "limit",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "scrape_results",
"displayName": "scrape_results",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
-140,
260
],
"id": "fc-tool-search",
"name": "search_web"
},
{
"parameters": {
"name": "map_site",
"description": "Returns every discoverable URL on a site \u2014 the fastest way to scope research. 1 credit flat regardless of URL count. Use this BEFORE crawl_site or batch_scrape so you know what pages exist. Inputs: url (site root), search (optional keyword filter), limit (default 500, max 5000).",
"workflowId": {
"__rl": true,
"value": "Fc05SubMapSite01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"url": "={{ $fromAI('url', 'The site root URL to map, e.g. https://firecrawl.dev', 'string') }}",
"search": "={{ $fromAI('search', 'Optional keyword filter. Only URLs whose path contains this string are returned.', 'string') }}",
"limit": "={{ $fromAI('limit', 'Max number of URLs to return (default 500).', 'number') }}",
"session_id": "={{ $('When chat message received').item.json.sessionId }}"
},
"matchingColumns": [],
"schema": [
{
"id": "url",
"displayName": "url",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "search",
"displayName": "search",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "limit",
"displayName": "limit",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
40,
260
],
"id": "fc-tool-map",
"name": "map_site"
},
{
"parameters": {
"name": "scrape_url",
"description": "Scrapes a single URL and returns clean markdown + title + links. 1 credit per call. Results are cached in Postgres for 24h \u2014 identical URLs within that window return cache_hit=true for 0 credits. Inputs: url (required), formats (default ['markdown','links']), only_main_content (default true), include_screenshot (default false), force_refresh (default false \u2014 set true to bypass cache).",
"workflowId": {
"__rl": true,
"value": "Fc03SubScrapeC01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"url": "={{ $fromAI('url', 'The single URL to scrape.', 'string') }}",
"formats": "={{ $fromAI('formats', 'Array of output formats: markdown, html, links, screenshot. Default [\"markdown\",\"links\"].', 'json') }}",
"only_main_content": "={{ $fromAI('only_main_content', 'Strip navigation/footer/ads. Default true.', 'boolean') }}",
"include_screenshot": "={{ $fromAI('include_screenshot', 'Also return a PNG screenshot URL. Default false.', 'boolean') }}",
"force_refresh": "={{ $fromAI('force_refresh', 'Skip the 24h cache and re-fetch. Default false.', 'boolean') }}",
"session_id": "={{ $('When chat message received').item.json.sessionId }}"
},
"matchingColumns": [],
"schema": [
{
"id": "url",
"displayName": "url",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "formats",
"displayName": "formats",
"required": false,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "only_main_content",
"displayName": "only_main_content",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "include_screenshot",
"displayName": "include_screenshot",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "force_refresh",
"displayName": "force_refresh",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
220,
260
],
"id": "fc-tool-scrape",
"name": "scrape_url"
},
{
"parameters": {
"name": "batch_scrape",
"description": "Scrapes up to 25 URLs in parallel via Firecrawl. More efficient than looping scrape_url. 1 credit per successful URL. Hard cap: 25 URLs per call. Inputs: urls (required array), formats (default ['markdown']), only_main_content (default true).",
"workflowId": {
"__rl": true,
"value": "Fc07SubBatchSc01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"urls": "={{ $fromAI('urls', 'Array of URLs to scrape, max 25 per call.', 'json') }}",
"formats": "={{ $fromAI('formats', 'Array of formats, default [\"markdown\"].', 'json') }}",
"only_main_content": "={{ $fromAI('only_main_content', 'Strip navigation/footer/ads. Default true.', 'boolean') }}",
"session_id": "={{ $('When chat message received').item.json.sessionId }}"
},
"matchingColumns": [],
"schema": [
{
"id": "urls",
"displayName": "urls",
"required": true,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "formats",
"displayName": "formats",
"required": false,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "only_main_content",
"displayName": "only_main_content",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
400,
260
],
"id": "fc-tool-batch",
"name": "batch_scrape"
},
{
"parameters": {
"name": "crawl_site",
"description": "Recursively crawls a site from a starting URL. Hard caps: 50 pages, depth 3. 1 credit per page scraped. Prefer map_site + batch_scrape unless the user specifically wants exhaustive traversal. Inputs: url (root), max_pages (default 25, max 50), max_depth (default 2, max 3), include_patterns (glob array), exclude_patterns (glob array).",
"workflowId": {
"__rl": true,
"value": "Fc06SubCrawlB01x",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"url": "={{ $fromAI('url', 'The site root or starting URL to crawl.', 'string') }}",
"max_pages": "={{ $fromAI('max_pages', 'Max pages to scrape (default 25, hard cap 50).', 'number') }}",
"max_depth": "={{ $fromAI('max_depth', 'Max link-depth to follow (default 2, hard cap 3).', 'number') }}",
"include_patterns": "={{ $fromAI('include_patterns', 'URL glob patterns to include, e.g. [\"/docs/*\"].', 'json') }}",
"exclude_patterns": "={{ $fromAI('exclude_patterns', 'URL glob patterns to exclude.', 'json') }}",
"session_id": "={{ $('When chat message received').item.json.sessionId }}"
},
"matchingColumns": [],
"schema": [
{
"id": "url",
"displayName": "url",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "max_pages",
"displayName": "max_pages",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "max_depth",
"displayName": "max_depth",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "include_patterns",
"displayName": "include_patterns",
"required": false,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "exclude_patterns",
"displayName": "exclude_patterns",
"required": false,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
580,
260
],
"id": "fc-tool-crawl",
"name": "crawl_site"
},
{
"parameters": {
"name": "extract_data",
"description": "LLM-powered structured extraction. Returns JSON conforming to a JSON Schema (validated on return). 5 credits per URL. Hard cap: 10 URLs per call. Use only when you need structured fields, not free-form text. Inputs: urls (required), prompt (describe what to extract) OR schema (JSON Schema object).",
"workflowId": {
"__rl": true,
"value": "Fc08SubExtract01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"urls": "={{ $fromAI('urls', 'Array of URLs to extract from, max 10 per call.', 'json') }}",
"prompt": "={{ $fromAI('prompt', 'Natural-language description of what to extract.', 'string') }}",
"schema": "={{ $fromAI('schema', 'Optional JSON Schema object for the output structure.', 'json') }}",
"session_id": "={{ $('When chat message received').item.json.sessionId }}"
},
"matchingColumns": [],
"schema": [
{
"id": "urls",
"displayName": "urls",
"required": true,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "prompt",
"displayName": "prompt",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "schema",
"displayName": "schema",
"required": false,
"type": "object",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
760,
260
],
"id": "fc-tool-extract",
"name": "extract_data"
},
{
"parameters": {
"name": "firecrawl_autopilot",
"description": "Delegates a complete research goal to Firecrawl's own server-side agent. Returns whatever it extracts. Use for multi-page tasks where you don't know the site structure upfront. Variable credit cost (typically 5-30). Inputs: prompt (required goal), url (optional starting point), max_wait (seconds to wait, default 180, max 300).",
"workflowId": {
"__rl": true,
"value": "Fc09SubFcAgent01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"prompt": "={{ $fromAI('prompt', 'Natural-language research goal for the Firecrawl autopilot.', 'string') }}",
"url": "={{ $fromAI('url', 'Optional starting URL hint.', 'string') }}",
"max_wait": "={{ $fromAI('max_wait', 'Max seconds to wait for sync completion (default 180, max 300).', 'number') }}",
"session_id": "={{ $('When chat message received').item.json.sessionId }}"
},
"matchingColumns": [],
"schema": [
{
"id": "prompt",
"displayName": "prompt",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "url",
"displayName": "url",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "max_wait",
"displayName": "max_wait",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
940,
260
],
"id": "fc-tool-autopilot",
"name": "firecrawl_autopilot"
},
{
"parameters": {
"name": "browser_session",
"description": "Manages a persistent browser sandbox session for login-gated or heavy-JS sites. Actions: `create` (opens a session, returns session_id), `execute` (runs JS/python/bash in the session), `delete` (closes \u2014 ALWAYS call when done), `list` (lists active sessions). Inputs: action (required), session_id (for execute/delete), code (for execute), language (default javascript), url (for create).",
"workflowId": {
"__rl": true,
"value": "Fc10SubSandbox01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"action": "={{ $fromAI('action', 'One of: create, execute, delete, list.', 'string') }}",
"session_id": "={{ $fromAI('session_id', 'Sandbox session id (required for execute and delete).', 'string') }}",
"code": "={{ $fromAI('code', 'Code to execute in the browser sandbox.', 'string') }}",
"language": "={{ $fromAI('language', 'Language: javascript (default), python, bash.', 'string') }}",
"url": "={{ $fromAI('url', 'Optional starting URL for create action.', 'string') }}",
"session_id_caller": "={{ $('When chat message received').item.json.sessionId }}"
},
"matchingColumns": [],
"schema": [
{
"id": "action",
"displayName": "action",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "code",
"displayName": "code",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "language",
"displayName": "language",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "url",
"displayName": "url",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id_caller",
"displayName": "session_id_caller",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
1120,
260
],
"id": "fc-tool-sandbox",
"name": "browser_session"
},
{
"parameters": {
"name": "check_credits",
"description": "Returns how many Firecrawl credits this session has spent, grouped by operation, plus recent call history and an advice string. Pure Postgres read \u2014 0 credits. Call every 5 tool calls and BEFORE any crawl/batch/extract. Inputs: session_id (optional, defaults to current), credit_cap (optional, default 500).",
"workflowId": {
"__rl": true,
"value": "Fc11SubCredits01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"session_id": "={{ $('When chat message received').item.json.sessionId }}",
"credit_cap": "={{ $fromAI('credit_cap', 'Soft session budget in credits (default 500).', 'number') }}"
},
"matchingColumns": [],
"schema": [
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "credit_cap",
"displayName": "credit_cap",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
1300,
260
],
"id": "fc-tool-credits",
"name": "check_credits"
},
{
"parameters": {
"name": "think",
"description": "Internal scratchpad. Use to plan a multi-step research path, to decompose a vague question, or to diagnose a tool error before retrying. Input: `thought` (string). Returns the thought back with a timestamp. Use this generously BEFORE expensive tool calls (crawl/batch/extract) and ALWAYS after an error.",
"language": "javaScript",
"jsCode": "const t = typeof query === 'string' ? query : (query && query.thought) ? query.thought : JSON.stringify(query);\nreturn `Thought recorded at ${new Date().toISOString()}: ${t}`;",
"specifyInputSchema": true,
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"thought\": { \"type\": \"string\", \"description\": \"What you're planning, filtering, or diagnosing.\" }\n },\n \"required\": [\"thought\"]\n}"
},
"type": "@n8n/n8n-nodes-langchain.toolCode",
"typeVersion": 1.3,
"position": [
1480,
260
],
"id": "fc-tool-think",
"name": "think"
}
],
"connections": {
"When chat message received": {
"main": [
[
{
"node": "Firecrawl Web Agent",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Postgres Chat Memory": {
"ai_memory": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"search_web": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"map_site": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"scrape_url": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"batch_scrape": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"crawl_site": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"extract_data": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"firecrawl_autopilot": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"browser_session": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"check_credits": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"think": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent",
"type": "ai_tool",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Firecrawl Web Crawler Agent. Uses chatTrigger, agent, lmChatOpenRouter, memoryPostgresChat. Chat trigger; 21 nodes.
Source: https://github.com/MinaSaad1/n8n-firecrawl-web-crawler-agent/blob/main/workflows/01-main-firecrawl-agent.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Supabase Data Analyst Agent. Uses stickyNote, chatTrigger, agent, lmChatOpenRouter. Chat trigger; 17 nodes.
Power BI Data Analyst Agent. Uses stickyNote, chatTrigger, agent, lmChatOpenRouter. Chat trigger; 12 nodes.
Aiden. Uses supabase, chat, memoryBufferWindow, memoryPostgresChat. Chat trigger; 20 nodes.
Airtable AI Agent. Uses lmChatOpenAi, agent, toolWorkflow, toolCode. Chat trigger; 42 nodes.
Ai Agent To Chat With Airtable And Analyze Data. Uses lmChatOpenAi, agent, stickyNote, memoryBufferWindow. Chat trigger; 41 nodes.