Skip to content

Profiles and productionExact

Profile Behavior
strict Matches production Firebase behavior and constraints as closely as possible within the supported scope. The default, and recommended, including when migrating from the official emulator.
emulator Targets the behavior of a pinned version of the official emulator (firebase-tools 15.28.2). Use it only to check alignment with the official emulator, for example as an extra test target.

The emulator profile accepts behavior that production refuses, so a test can pass there and fail against production. Use strict for your tests.

schemaVersion = 1
profile = "strict"

Where Fireemu departs from production on purpose

Section titled “Where Fireemu departs from production on purpose”

Matching production is Fireemu’s goal, but not every production behavior is worth reproducing by default. Some only add setup to a test: production needs signed tokens and signing keys, a feature enabled through an Admin API call, or an hour of waiting. Copying them exactly would make Fireemu harder to use in CI and on a developer’s machine without catching more bugs.

For these behaviors, the default in both profiles is a development default that works without setup. Each one has its own setting, and each is listed here, so a difference from production is always a documented choice rather than a gap.

To get production’s behavior for all of them at once, set productionExact to true (strict profile only). An explicit setting still decides over it:

schemaVersion = 1
profile = "strict"
productionExact = true
[auth]
allowUnsignedCustomTokens = true
Development default Setting With productionExact
Unsigned emulator custom tokens are accepted auth.allowUnsignedCustomTokens Refused
Firestore, Storage and Realtime Database Security Rules accept @firebase/rules-unit-testing’s unsigned mock ID token auth.allowUnsignedIdTokens Refused, as production refuses an unsigned token
A Realtime Database instance without a rules file is open to every request, as the official emulator serves it database.defaultAccess ("open") "locked": only the emulator’s owner credential reads and writes, as production admits only administrators to a new database in locked mode
Identity-provider sign-in accepts fixture assertions and the sign-in widget auth.idpAssertions ("fixture") "signed": OIDC ID tokens must verify with an auth.idpSigners key, SAML responses with the provider’s certificates
The Eventarc listener admits any credential or none eventarc.authentication ("permissive") "production": a missing, malformed or JWT-shaped bearer is refused with production’s 401
A tenant ID can be any readable name, such as tenant-a auth.tenantIdFormat ("readable") "generated": production’s generated form, such as acme-x7k2q
Multi-tenancy is on: the Admin API creates tenants and auth.tenants loads without an updateConfig call auth.multiTenant.allowTenants (true) Unset until enabled, as in a new production project: tenant routes are refused and auth.tenants needs allowTenants: true
Multi-factor authentication is on, SMS and TOTP, without a console toggle, an updateConfig call or an auth.mfa block auth.mfa (state: "ENABLED") Off until enabled, as in a new production project: enrollment is refused with OPERATION_NOT_ALLOWED : SMS based MFA not enabled. or TOTP based MFA not enabled.
An end user’s Firestore Listen stream stays open however long it is held firestore.listenStreamLifetime ("unlimited") "oneHour": ended with INTERNAL an hour after it opened
App Check works without registering an app: any well-formed debug token (a UUIDv4) exchanges for any app, so an enforceAppCheck callable can be called with no appCheck block; a missing or malformed token is still refused appCheck.debugTokens ("any") "registered": only apps from appCheck.apps with a registered debug-token digest exchange, and App Check is not served without an appCheck block. An appCheck block restores it too
Dead-letter forwarding needs neither a project number nor IAM grants pubsub.deadLetterAuthorization ("implicit") "iam": a message is forwarded only after the Pub/Sub service agent is granted roles/pubsub.subscriber on the subscription and roles/pubsub.publisher on the dead-letter topic. Naming pubsub.projectNumbers selects it too

The startup banner and fireemu capabilities report whether productionExact is set, and the effective value and source of each setting in the table.

Most projects do not need productionExact; it is an advanced switch for a stricter comparison with production. A query whose composite index is not defined is not among these defaults: strict refuses it as production does, and firestore.missingIndexes: "report" is an explicit opt-out that runs it and reports the missing index on standard error. productionExact does not change that key.

These differ from production and are not switched by productionExact:

Development behavior How to get production’s behavior
Pub/Sub accepts requests without any credential, so clients that use PUBSUB_EMULATOR_HOST work Set pubsub.authentication.localBearerToken; requests without a credential are then refused as production refuses them. See Pub/Sub and Eventarc.
localhost is in the default authorized domains for action-link continue URLs; a new production project may not include it Declare auth.authorizedDomains, which replaces the defaults
Storage: a signed URL or a POST policy is accepted with any signature (its expiry and conditions are checked), because verifying one needs the signing service account’s key Not available
Storage: the JSON API serves a request that has no credential, or any credential other than a Firebase ID token, as the owner, because a local run cannot judge an access token Not available
Storage: the first write to a bucket nothing declared is served and creates the bucket, with one note: line on standard error; production answers 404 List the bucket in storage.buckets; the 404 itself is not available
Storage: a bucket-level public grant (IAM allUsers) cannot be made through an API List the bucket in storage.publicBuckets
Storage: the public URL (GET /{bucket}/{object}) is served without a credential to a request that sends x-goog-api-client, because the Go client library (and the Firebase Admin Go SDK) reads through it with no credential against an emulator host; a browser fetch is refused Not available
Storage: every Origin is reflected in the CORS headers, where production needs a CORS configuration on the bucket for browser downloads Not available
Storage: a Security Rule may call firestore.get() and firestore.exists() without the Firestore access grant production needs for the Storage service agent Not available
Storage: both default bucket names, <project>.appspot.com and <project>.firebasestorage.app, exist, where a production project has one of them Set storage.defaultBucket to the one your project has; the other name is then noted on its first write and its bucket resource answers 404
Every sign-in method starts enabled, email-link sign-in included; a new production project enables none (firebase deploy --only auth only turns on the providers it lists, never off) Declare auth.signIn to match your project, such as email.passwordRequired: true to refuse email-link sign-in
Built-in identity providers such as google.com sign in without being enabled in the console. oidc.* and saml.* providers must still be configured and enabled, or sign-in is refused as production refuses it auth.idpAssertions: "signed" (set by productionExact) refuses the built-in providers
Any Web API key is accepted until the project declares one; production answers API_KEY_INVALID for a key of another project Declare auth.apiKeys
Phone verification sends a code to any number with no reCAPTCHA, Play Integrity or APNs proof, which production requires outside test numbers No setting. Client SDKs run their verifier in emulator mode, so the app path is exercised; a raw REST client that omits the proof passes here
A reCAPTCHA ENFORCE state is stored and returned but not enforced, so a sign-in without a reCAPTCHA token is accepted No setting
accounts:batchCreate has no row limit; the Admin SDK checks 1000 users per call before it sends No setting
Sign-up rate limits (production limits new accounts per IP) are not simulated Set auth.quotaSimulation to auth.quotaSimulation.mode = "enforce"
Firestore serves a request naming a project other than the daemon’s from that project’s own empty data, which keeps parallel tests apart; a typo in a projectId splits data instead of failing as production’s PERMISSION_DENIED does Set projects.unknownProjects to "refuse"
The Admin SDK’s verifyIdToken inside a function accepts an unsigned ID token, because the SDK skips signature verification while FIREBASE_AUTH_EMULATOR_HOST is set; production refuses it No setting. See Auth
A Pub/Sub push endpoint may be plain http:// on loopback (127.0.0.1, localhost, [::1]); production requires HTTPS Use a non-loopback https:// URL in the subscription; Fireemu delivers push messages only to loopback, so there is no setting that makes it reach one
.env.local in a Functions codebase is applied after .env and .env.<projectId> although production never deploys it Put the value in .env or .env.<projectId> instead; a key that exists only in .env.local is unset in production
A function that fails with retry enabled is retried 4 times in total (events.maxAttempts, 10 s to 600 s backoff on the virtual clock), then dead-lettered; production retries for up to 24 hours (second generation) or 7 days (first generation) Raise events.maxAttempts; no value reproduces a time window
A function that is not public (invoker: "private" or a list of service accounts) and a task-queue function accept any bearer that looks like a Google-issued identity token (a JWT whose iss is https://accounts.google.com) on the public function URL, because Google’s verification of its signature and of its IAM role cannot run locally. A Firebase Auth ID token is refused with 401, as Cloud Run refuses it. A call without credentials is refused with 403, as production refuses it. The console and the control API call such functions with the control token There is none; send a call without credentials to see the refusal. ingressSettings is not enforced
Composite indexes from firestore.indexes.json are READY at once, and ones created through the Admin API after about 1.5 seconds; production builds an index for minutes. The CREATING state, and the “currently building” refusal of a query that needs the index, are kept No setting. Poll the index until it is READY, as against production; advancing the virtual clock also finishes a build
Managed export, import and bulk-delete operations are done on the first poll; production takes minutes No setting. Poll the operation until done, as against production
A transaction that holds a lock another writer is waiting for is rolled back after 60 seconds without activity (wall clock). Production’s own idle limit is about 110 to 123 seconds, and what it does to a contended holder earlier is not recorded No setting
  • Most projects: strict with the development defaults. Your tests run without keys or setup calls, and still meet production’s validation, refusals and error shapes.
  • Before a release, or in a separate CI job: strict with productionExact: true, to catch what the development defaults let through, such as an unsigned token.
  • Checking the official emulator: emulator, as an additional target only.