Skip to content

Start a new project

firenook init sets up a new project that runs every emulator: a firebase.json, a project id, rules, indexes and a Functions codebase in TypeScript. In a terminal it asks on one screen, then installs the packages, pins Firenook, builds your code once and offers to start the emulators. It needs no Google account. If your project already has a firebase.json, switch it over instead.

In an empty folder, run init through your package manager:

Terminal window
npx firenook init

This runs the latest release, on any Node from 20 up, and init pins that release in the folder’s package.json, so every later command runs the same one. A folder that already has a package.json keeps it: init adds Firenook and an emulators script, and keeps a pin or a script that is already there.

In a terminal, init opens on one screen with every choice already made, so Enter takes them all:

╭──────────────────────────────────────────────────────────────────────────╮
│ │
│ ▄█ │
│ ▄███▀ ▄ firenook 0.2.0 │
│ ▄███▀ ███▄ a new project in ~/my-app │
│ ▀██▀ ███▀ Node 24.20.0 · Claude Code found │
│ █▀▀ │
│ │
├─ New project ────────────────────────────────────────────────────────────┤
│ │
│ Project ID › demo-my-app │
│ demo- keeps it on this machine: no request can reach a │
│ real Firebase project │
│ Emulators ◉ Firestore 8080 ◉ Auth 9099 ◉ Storage 9199 │
│ ◉ Pub/Sub 8085 ◉ Console 4000 │
│ Functions ● TypeScript ○ JavaScript ○ None │
│ Agents ◉ Claude Code ○ Cursor ○ VS Code │
│ Set up ◉ Install packages ◉ Pin Firenook │
│ │
╰──────────────────────────────────────────────────────────────────────────╯
↑↓ field ←→ choice space toggle enter create esc cancel
Row What it chooses
Project ID demo- plus the folder’s name. Type another id to use your real project’s
Emulators Firestore, Auth, Storage, Pub/Sub and the console, each on its official port. A port something already listens on is named, with the program that holds it
Functions A TypeScript codebase, a JavaScript one, or none. Cloud Tasks and Eventarc come with Functions
Agents The coding agents the folder shows (.claude/, .cursor/, .vscode/, or claude on the PATH), which get Firenook’s MCP server
Set up Install the packages, and pin Firenook in package.json with an emulators script

↑ and ↓ move between the rows, ←, → and Space change one, and the line under the row you are on says what it does: for TypeScript, functions/src/index.ts, compiled to lib/ as you save. Esc or Ctrl-C leaves with Cancelled: nothing was written. and exit code 130, and the folder stays as it was.

When the folder sits inside another package, a package.json in a folder above it, Pin Firenook starts off and its line names that file: add Firenook there instead.

Enter writes the files, gives the chosen agents the MCP server, pins Firenook, and runs the installs: npm install in functions/, and your package manager’s install in the project. A TypeScript codebase is then built once. Each step gets a line with its time:

├─ Set-up ──────────────────────────────────────────────────────── 15.3 s ─┤
│ │
│ Files ✓ 4 ms firebase.json, .firebaserc, rules, functions/, … │
│ Agents ✓ 0 ms .mcp.json for Claude Code │
│ Firenook ✓ 517 ms 0.2.0 in package.json │
│ Packages ✓ 14.1 s npm install in functions/, 241 packages │
│ Build ✓ 1.1 s tsc, into functions/lib │
│ │

Then init asks Start the emulators now?. Yes starts them in the same terminal. No ends with what to run later:

├─ Next ───────────────────────────────────────────────────────────────────┤
│ │
│ Start npm run emulators │
│ starts the emulators and compiles your TypeScript as you │
│ save │
│ Connect the console's Connect button has the lines that point │
│ your app at the emulators, for each SDK │
│ │
╰──────────────────────────────────────────────────────────────────────────╯

Ctrl-C during the installs stops them and keeps the files already written. On a Node older than 22, init says how to switch instead of offering to start.

One command starts the emulators from then on:

Terminal window
npm run emulators

The emulators script is firenook emulators:start --keep-data --build:

  • --keep-data keeps your data between runs in .firenook/state, from the first start. Without it every start begins empty, as the official emulator does. See keep data between runs.
  • --build runs the codebase’s own build:watch script (tsc --watch) beside the emulators. Your TypeScript is compiled before it loads and again on every save, with any errors in the log. See TypeScript.

The codebase has one HTTP function. Call it once the emulators are ready:

Terminal window
$ curl http://127.0.0.1:5001/demo-my-app/us-central1/helloWorld
Hello from Firebase!

The console is at http://127.0.0.1:4000/console.

The codebase has a serve script of its own, as the official template does. From functions/, npm run serve starts only the Functions emulator, with --build for TypeScript; Firenook finds firebase.json in the folder above.

File What it holds
firebase.json Firestore with its rules and indexes, Storage with its rules, the Functions codebase in functions/ with a predeploy build, every emulator on its official port, and the console on
.firebaserc The project id, as projects.default
firestore.rules The official starter rules: open to every client for 30 days from today, then closed
firestore.indexes.json No indexes and no field overrides
storage.rules The official starter rules: every client read and write is denied
functions/package.json firebase-functions 7 and firebase-admin 13 on Node 24, TypeScript 7, and the build, build:watch and serve scripts
functions/tsconfig.json The official settings: src/ compiled into lib/
functions/src/index.ts The helloWorld HTTP function
functions/.gitignore node_modules/, lib/ and *.local
.gitignore .firenook/ and *-debug.log, appended when missing
package.json With Pin Firenook: Firenook at an exact version, and the emulators script
.mcp.json, .cursor/mcp.json, .vscode/mcp.json Firenook’s MCP server, for each agent you chose

These are the official firebase init templates. With JavaScript, functions/index.js holds the function instead, with no tsconfig.json, no build scripts and no predeploy, and the scripts start without --build.

The scripts, as init writes them for TypeScript:

package.json
{
"name": "my-app",
"private": true,
"scripts": {
"emulators": "firenook emulators:start --keep-data --build"
},
"devDependencies": {
"firenook": "0.2.0"
}
}
functions/package.json (part)
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"serve": "firenook emulators:start --only functions --build"
},

The Admin SDK and server code are not bound by the rules. Your app’s client SDKs are, so write real rules before the Firestore starter rules close. The emulators and their ports, from the firebase.json that init writes:

firebase.json
{
"emulators": {
"auth": { "port": 9099 },
"functions": { "port": 5001 },
"firestore": { "port": 8080 },
"pubsub": { "port": 8085 },
"storage": { "port": 9199 },
"eventarc": { "port": 9299 },
"tasks": { "port": 9499 },
"hub": { "port": 4400 },
"logging": { "port": 4500 },
"ui": { "enabled": true, "port": 4000 },
"singleProjectMode": true
}
}

Change a port here and Firenook listens on the new one; your app’s connect calls must use the same port. The ports reference lists them all.

In a script, in CI, or with its input redirected, init asks nothing. It writes the files, installs nothing, and prints the commands that finish the set-up:

Terminal window
$ npx firenook init
Firenook init: project demo-my-app in ~/my-app
wrote ~/my-app/firebase.json
wrote ~/my-app/.firebaserc
wrote ~/my-app/firestore.rules
wrote ~/my-app/firestore.indexes.json
wrote ~/my-app/storage.rules
wrote ~/my-app/functions/package.json
wrote ~/my-app/functions/tsconfig.json
wrote ~/my-app/functions/src/index.ts
wrote ~/my-app/functions/.gitignore
wrote ~/my-app/.gitignore
Next steps:
npm install --prefix functions
npx firenook emulators:start --keep-data --build
npx firenook mcp add (gives Claude Code the Firenook MCP server)

The last line appears when the folder shows a coding agent. --yes takes the screen’s choices without asking and does the set-up too, a line a step:

Terminal window
$ npx firenook init --yes
Firenook init: project demo-my-app in ~/my-app
wrote ~/my-app/firebase.json
wrote ~/my-app/.firebaserc
wrote ~/my-app/firestore.rules
wrote ~/my-app/firestore.indexes.json
wrote ~/my-app/storage.rules
wrote ~/my-app/functions/package.json
wrote ~/my-app/functions/tsconfig.json
wrote ~/my-app/functions/src/index.ts
wrote ~/my-app/functions/.gitignore
wrote ~/my-app/.gitignore
done Agents: .mcp.json for Claude Code (0.0 s)
done Firenook: 0.2.0 in package.json (0.4 s)
done Packages: npm install in functions/, 241 packages (2.3 s)
done Build: tsc, into functions/lib (0.8 s)
Next steps:
npm run emulators

These flags change the choices, in a terminal or not:

Flag Does
--yes Takes the choices without asking, and installs, pins and builds
--no-install Installs nothing; the next steps list the installs
--no-pin Leaves package.json as it is
--language typescript The Functions codebase: typescript (the default), javascript or none. none writes no codebase, and so no Functions, Cloud Tasks or Eventarc emulator (--no-functions does the same)
--only firestore,auth,storage,functions,pubsub The emulators to set up. The console and the hub always come; Cloud Tasks and Eventarc come with functions
--agent claude,cursor,vscode The coding agents to give the MCP server, instead of the ones the folder shows
--project ID Uses this project id instead of demo-<folder>
--dry-run Prints each file as write or keep, and writes nothing
--force Writes firebase.json over an existing one, and keeps every other file that exists
--json Prints the plan and the next steps as one line of JSON, and asks nothing

init names the project after its folder: demo- plus the folder’s name in lowercase, with other characters turned into hyphens. A folder called My Shop gets demo-my-shop, and a folder already called demo-shop keeps that name.

A project id that starts with demo- is local only, as it is for the official emulator. Firenook contacts no cloud service for it, and you need no Google account or login. Your app must use the same id when it connects; see connect your app.

Your Functions are still your code, and a function that calls an outside API reaches it. The plain output that pipes and CI get says so as it starts:

Demo project demo-my-app: no cloud service is contacted, but user Functions can still reach external providers; this CLI is not a network sandbox.

If .firebaserc already names a project, init keeps it. --project picks another: any demo- id, or a real project id (6 to 30 lowercase letters, digits and hyphens, starting with a letter).

In a terminal, init in a folder with a firebase.json shows what init --adopt would add, and adds it on a yes; see switch from the Firebase emulators. Without a terminal it stops and says what to do:

Terminal window
$ npx firenook init
Firenook: ~/demo-shop/firebase.json exists; use firenook init --adopt to adapt it, or --force to write a new one (the other files there are kept)

--force writes firebase.json again and keeps the rules, the indexes, the Functions code and the rest of .firebaserc as they are. Delete a file first to get its template. TypeScript is the default there too, so pass --language javascript for a JavaScript codebase.

Connect your app to the emulators, then run your tests against them.