Stephan Miller
Firecrawl CLI Setup: Skip the Installer That Rewrites Your Editors

Firecrawl CLI Setup: Skip the Installer That Rewrites Your Editors

In the last post I gave the Firecrawl setup three sentences and moved on, because the post was about the fetch skill I was building and not about installation.

The install itself is one command. Everything around the install is what you have to watch: an installer that wants to configure every editor on your machine, an auth screen that reports two contradictory things in consecutive lines, and a shell issue that cost me an API key I could only copy once. Plus one fork in the road worth deciding before you start, which is whether the tool should run as a command or as a resident MCP server.

So here is the setup told properly, for somebody who is installing Firecrawl right now and would rather not spend the time I spent. Everything below was run on this machine against CLI version 1.23.3.

Install With npm, Not With the Installer

The command you want:

npm install -g firecrawl-cli

That is the whole install. You get a firecrawl binary on your PATH, symlinked into your global node modules like any other npm package, and nothing else on your machine changes.

The docs will also offer you a curl-piped-to-shell one-liner, and the CLI ships a firecrawl init command. Both do considerably more than install a binary. Here is init describing itself:

Usage: firecrawl init [options] [template]

Set up Firecrawl: install CLI, authenticate, add integrations, and scaffold a
template

Arguments:
  ...

Options:
  --all                Explicitly install skills to all detected agents (default
                       unless --agent is used)
  -y, --yes            Run init non-interactively; skills still install globally
                       across all detected agents unless --agent is used
  -g, --global         Install skills globally (user-level, default)
  -a, --agent <agent>  Install skills to a specific agent
  -k, --api-key <key>  Authenticate with this API key (skips interactive login)
  --skip-install       Skip global CLI installation
  --skip-auth          Skip authentication
  --skip-skills        Skip skills installation

Read the -y line again. Skills still install globally across all detected agents. The non-interactive flag does not mean “do less.” It means “do all of it without asking.”

And init is not alone. There are four separate commands in this CLI whose actual job is writing Firecrawl into tools that are not Firecrawl:

Install With npm, Not With the Installer

CommandWhat it does to your machine
initInstalls the CLI, authenticates, and pushes agent skills to every detected agent
setupInstalls skills, workflows, mcp, or defaults individually
make defaultMakes Firecrawl the default provider for supported workflows
launchInstalls the Firecrawl MCP server into an agent, then starts that agent

The one that gave me pause is setup, and specifically its undo flag, which is documented as “Undo setup defaults by re-enabling native web tools where supported.” Read that again. The undo re-enables your native web tools, which means the thing being undone disabled them. A scraping vendor’s installer, in its default path, turns off the web fetching your coding agent already had.

I guess that is the reasonable end state of a competitive market where the install experience is a growth channel, and Firecrawl is far from the only tool doing it. It is also the thing I do not want, because I would like to be the one who decides what is in their context.

Skip all four. One npm install does the job.

You can confirm you got the right outcome:

which firecrawl
# /opt/homebrew/bin/firecrawl

firecrawl --version
# 1.23.3

CLI or MCP: Pick the One That Stays Out of the Way

I run Firecrawl through the CLI rather than its MCP server. This is the same verdict I landed on with Playwright.

An MCP server is resident. Its tool definitions sit in the agent’s context on every single request, whether or not this particular request has anything to do with scraping. That is a fixed tax paid in tokens and in attention, on every turn, for a capability I use maybe twice a week.

A CLI is not resident. The agent runs a command, the output goes to a file, and between calls the tool occupies exactly nothing. When I need Firecrawl, a skill tells the agent the command to run. When I do not, there is no evidence in the context that Firecrawl exists.

The launch command exists specifically to install the MCP server into your agent and then start it, and it knows about claude, code/vscode, codex, codex-app, hermes, openclaw, and opencode. That is a well-built feature and I understand why people want it.

CLI or MCP: Pick the One That Stays Out of the Way

Here is the counterargument, because there is one. An MCP server means the agent discovers the capability on its own. It sees a scraping tool in its tool list and reaches for it without being told. With the CLI approach, something has to tell the agent that Firecrawl is an option, which in my case is a skill I had to write. If you do not want to write that glue, the MCP server is genuinely the faster road.

I already had the glue. So for me the CLI wins on the only part I care about, which is that a background tool beats one that takes over the screen.

The Auth Screen That Contradicts Itself

Get your API key from the dashboard, then check your configuration. This is what firecrawl view-config printed for me:

┌─────────────────────────────────────────┐
│          Firecrawl Configuration        │
└─────────────────────────────────────────┘

Status: ✓ Authenticated

API Key:  Not set
API URL:  https://api.firecrawl.dev
Config:   /Users/<you>/Library/Application Support/firecrawl-cli

Authenticated, with a check mark. API key not set. Two lines apart, both delivered with total confidence.

I spent real time on this, and the answer turns out to be that neither line is lying. They are answering two different questions but forgot to label which is which.

Status: reflects live authentication state, including the FIRECRAWL_API_KEY environment variable. API Key: reflects only a key stored in the CLI’s own config file, which is empty when you authenticate through the environment instead of through firecrawl config. Two auth paths, two readouts, stacked adjacently with no indication that they are reading different sources.

You can prove it in one command. Clear the environment variable and run the same thing:

env -u FIRECRAWL_API_KEY firecrawl view-config
Status: Not authenticated

Run any command to start authentication, or use:
  firecrawl config    Authenticate with browser or API key

The status flipped, which means the status was reading the environment variable all along.

The command you actually want is firecrawl --status, which does not have this problem because it names its source:

  🔥 firecrawl cli v1.23.3

  ● Authenticated via FIRECRAWL_API_KEY
  Concurrency: 0/2 jobs (parallel scrape limit)
  Credits: 1,324 / 1,000 (132% left this cycle)
  .firecrawl: not found - no local cache
  .gitignore: missing - add .firecrawl/ to ignore cache

“Authenticated via FIRECRAWL_API_KEY.” That is the whole fix. One flag, and the ambiguity disappears. Use --status and forget view-config exists.

(Yes, it says I have 132% of my credits left after running some crawls this month. I am choosing to accept this gift and not ask questions.)

The Almost-Right Command

Now the part that actually cost me something.

The Almost-Right Command That Cost Me an API Key

The key goes in an environment variable called FIRECRAWL_API_KEY. On bash or zsh, that means one line in your shell’s startup file:

# ~/.bashrc on bash, ~/.zshrc on zsh
export FIRECRAWL_API_KEY=fc-your-key-here

Then open a new terminal, or run source ~/.bashrc in the one you already have. The export does not reach backwards into shells that were already running.

Here is the trap, and it is not specific to any one shell. Typing export FIRECRAWL_API_KEY=fc-... straight at your prompt instead of putting it in the startup file works perfectly for the rest of that session and then evaporates when you close the window. Everything you test in the next five minutes succeeds. That is the worst possible failure mode, because success is exactly what convinces you to stop paying attention and move on.

If you are on fish

This is where it bit me, and fish makes the hole a little easier to fall into, because it has four variable scopes instead of one and the two that matter here differ by a single letter:

# Wrong. Dies when you close the terminal.
set -x FIRECRAWL_API_KEY fc-your-key-here

# Right. Persists across sessions.
set -Ux FIRECRAWL_API_KEY fc-your-key-here

-x exports it to child processes. -U makes it universal, which in fish means it survives the terminal closing. You need both.

The afternoon the almost-right command cost me

I asked an AI assistant for the fish command to set an environment variable permanently, and got back set -x. I closed the terminal, came back, was not authenticated, asked again, phrased it more emphatically, and got back set -x again. Somewhere in there I closed the tab with the API key on it.

I had not saved the key anywhere else, because I had just watched it get set successfully and had no reason to think I would need it again. So I rotated it and started over, which took the whole thing from a five-minute setup to something dumber. Copy the key into a password manager before you touch your shell config, and none of this can happen to you.

The generalizable lesson has nothing to do with fish. AI-assisted setup fails in a specific and nasty shape: it hands you the almost-right command. A wrong command errors out immediately and you fix it in ten seconds. An almost-right command succeeds, validates cleanly, and fails an hour later, after you have thrown away the thing you would need to recover. set -x is not a wrong answer to “set an environment variable in fish.” It is a wrong answer to “set it permanently,” and the word doing the work is the one the model dropped. Bash has the identical hole with a bare export at the prompt.

Whatever shell you are on, open a fresh terminal and confirm the thing you just set is still there before you trust it. That is the entire takeaway and it applies well beyond this tool.

Prove It Works Before You Trust It

Do not skip this. The whole point of the previous section is that a broken setup can look fine.

Prove It Works Before You Trust It

firecrawl scrape https://example.com --only-main-content -o smoke.md --timing

The -o flag is not optional in spirit. Without it the scraped page goes to stdout, and if you happen to smoke test something larger than example.com you will dump an entire article into your terminal. The --timing flag gives you a status line worth seeing.

A working setup looks like this:

Timing: {
  "url": "https://example.com",
  "requestTime": "2026-09-08T23:59:53.881Z",
  "duration": "390ms",
  "status": "success"
}
Scrape ID: 01a08376-c56f-73bf-bc28-411202792b72

And smoke.md contains actual markdown:

# Example Domain

This domain is for use in documentation examples without needing permission. Avoid use in operations.

[Learn more](https://iana.org/domains/example)

A broken key looks like this, and thankfully it is loud rather than silent:

Error: Unauthorized: Invalid token

One more thing while you are here. Scrapes with multiple URLs get cached into a .firecrawl/ directory in your working directory, and the --status output nags you about this every time, whether or not you are anywhere near a git repo. When you are in one, listen to it:

echo ".firecrawl/" >> .gitignore

Committing a cache directory full of scraped pages is a bad afternoon waiting to happen, and it is the kind of thing you only notice three weeks later in a diff.

The Setup Summary

Stripped of everything above, the working setup is four lines:

npm install -g firecrawl-cli
echo 'export FIRECRAWL_API_KEY=fc-your-key-here' >> ~/.bashrc && source ~/.bashrc
firecrawl --status
firecrawl scrape https://example.com --only-main-content -o smoke.md

On zsh, swap ~/.bashrc for ~/.zshrc. On fish, line two is set -Ux FIRECRAWL_API_KEY fc-your-key-here.

Install, key, verify, prove. Nothing writes into your editors, nothing sits in your agent’s context, and you know it works because you watched it work.

The mess I spent an afternoon in was not really Firecrawl’s mess. It was two things that are now true of most developer tools: the installer has become a growth channel, so its default path is maximal rather than minimal, and the auth surface has grown enough paths that the status readout can no longer say one clear thing. Both of those will be true of the next tool you install too. The defense is the same either way, which is to install the binary, wire it up yourself, and run one command that proves it before you build anything on top of it.

Next in this series I start actually using the thing, which means the first real recipe. This post exists so that one does not have to open with a paragraph about environment variables.

Stephan Miller

Written by

Kansas City Software Engineer and Author

Twitter | Github | LinkedIn

Updated

* This website contains affiliate links. This means that if you click on a link and purchase a product or service, I may receive a small commission at no extra cost to you. Please note that I only recommend products and services that I believe in and that will add value to my readers. Not all links on this website are affiliate links. Learn more.