Configuration.
Every variable, where it lives, what it does.
Muse AI has very few environment variables on purpose. Keys and product settings are entered in the console and stored in Firestore, so most buyers only ever touch two files: the console's .env and, optionally, the backend region. This page lists the build-time settings of the app, the environment of the console and the backend, and the two configuration documents.
01App: build-time settings (--dart-define)
Passed to flutter run and flutter build. They are compiled into the app, so changing one needs a new build.
| Define | Purpose | Where to get the value |
|---|---|---|
API_BASE_URL Required | Address of your backend, no slash at the end. Paths are added to it (<API_BASE_URL>/v1/config); any prefix before /v1 is allowed. Without it the app shows "Set your API address". | The URL printed by firebase deploy: https://<region>-<project-id>.cloudfunctions.net/api, or your Hosting rewrite such as https://your-domain/api (own domain). |
DEMO_MODE=true | Web demo only (it has no effect in Android and iOS builds): no Firebase, a demo sign-in per browser, simulated and labelled ads and purchases ("Demo — no charge"). Never use it for a store build. | Only for the demo and the Quick start. |
USE_EMULATOR=true, EMULATOR_HOST | Use the local Firebase Auth and Firestore emulators. EMULATOR_HOST is optional (default localhost, Android emulator 10.0.2.2). | Only for local development (emulators). |
Firebase itself is configured by flutterfire configure, which writes lib/firebase_options.dart, android/app/google-services.json and ios/Runner/GoogleService-Info.plist. Those files contain your Firebase project's public client identifiers, not secrets.
02Console: environment (admin/.env)
Copy admin/.env.example to admin/.env (never commit it) or set the same names in your host's environment.
| Variable | Purpose | Where to get it |
|---|---|---|
ADMIN_SECRET_KEY Required | 32 random bytes (64 hex characters) that encrypt every secret the console stores: AI keys, image keys, store keys, reporting tokens. Without it the console creates data/.admin-secret-key and warns; on serverless or Postgres setups it must be set. Back it up. | Generate: openssl rand -hex 32. See Secrets at rest. |
GOOGLE_APPLICATION_CREDENTIALS Required (except on Google Cloud) | Path to a service-account key file of your Firebase project, used by the Admin SDK. Leave empty on Cloud Run or Compute Engine to use the attached service account. | Firebase console → Project settings → Service accounts → Generate new private key. |
FIREBASE_PROJECT_ID Optional | Only when the credentials do not name the project. | Firebase console → Project settings → Project ID. |
FIREBASE_STORAGE_BUCKET Optional | Only to preview a reported image in the Reports queue. | Firebase console → Storage, for example your-app.firebasestorage.app. |
FIRESTORE_EMULATOR_HOST Development | Point the console at the Firestore emulator, for example 127.0.0.1:8080 (project demo-museai unless FIREBASE_PROJECT_ID is set). | Local only. |
ADMIN_DATA | sample = built-in sample data, no Firebase (public demo, quick start). Empty = your Firestore. | Leave empty on your install. |
ADMIN_DEMO_PASSWORD | Only on a public demo host: makes the console read-only with the account demo@museai.demo (a viewer). Every write answers 403. | Leave empty on your install. |
PORT | Port of the console. Default 8790. | Your host may set it for you. |
DATABASE_URL Optional | A pooled Postgres URL (sslmode=require) for the console's own data (team, sessions, settings, audit) instead of data/admin.sqlite. Needed on serverless hosts or with several instances. Tables are named mk_museai_*. | Your Postgres provider. |
03Backend: environment (firebase/functions/.env)
| Variable | Purpose | Where to get it |
|---|---|---|
FUNCTIONS_REGION Optional | Cloud Functions region, default us-central1. Use the same region in API_BASE_URL. | Copy functions/.env.example to functions/.env before deploying. |
No key goes into this file. The backend reads every key from config/private in Firestore. The variables DEMO_AI_PROVIDER, DEMO_AI_KEY, DEMO_AI_MODEL, DEMO_AI_BASE_URL, DEMO_IMAGE_PROVIDER, DEMO_IMAGE_KEY and DEMO_IMAGE_MODEL exist only for the local dev API and a public demo host; export them in a shell, never put them in a file of the project.
04The configuration documents
Two Firestore documents hold every product setting. The console is their only writer: it writes both in one transaction on every save and once when it starts, raises the shared version and sets updatedAt. Do not edit them by hand, because the next console save overwrites them. An invalid line (for example a catalog entry over a limit) is skipped, never guessed, and Console → Status, check App config written to Firestore, names it.
| Document | Who can read it | Holds |
|---|---|---|
config/public | The backend; the app gets a filtered copy through GET /v1/config. Firestore rules deny direct client access. | Everything the app needs to draw itself: brand, switches, catalogs, plans, store product ids, ad unit ids |
config/private | The backend only. Never sent to the app. | Provider keys, store verification keys, global caps |
The TypeScript types that define both documents are PublicConfig and PrivateConfig in admin/src/sync.ts; the backend reads them with the same defaults in firebase/functions/src/api/defaults.ts. If you extend the backend, add a field in both places and in the console manifest (admin/src/manifest.ts).
config/public
| Field | Type · default | What it controls · console page |
|---|---|---|
version, updatedAt | number, timestamp | Raised on every write (one counter shared by both documents); time of the write |
brand.appName | string Default: "Muse AI" | Name shown inside the app (the name under the icon is set in the native files) Console → Brand |
brand.accent | string #RRGGBBDefault: "#8B5CF6" | Accent colour of the app Console → Brand |
brand.tagline, brand.supportEmail | string Default: tagline; "" | Short line under the app name; the support e-mail address shown to users Console → Brand |
brand.privacyUrl, brand.termsUrl | string (URL) Default: "" | Your privacy policy and terms pages Console → Legal |
legal.aiNotice, legal.imageBadge | string Default: "AI can make mistakes. Check important information.", "AI-generated" | Notice under the composer; label on generated images Console → Legal |
legal.minAge, legal.ageGate | number (13, 16, 17, 18), boolean Default: 17, true | Age self-confirmation on first start Console → Legal |
features.<name> | boolean each Default: see Defaults | Module switches: chat, images, voice, vision, pdf, assistants, prompts, webSearch, cloudSync, share, discover, voiceMode, tts, transcribe, videoEditor, captions, videoGen, imageEdit, imageTools, incognito, followUps Console → Features |
auth.google, auth.apple, auth.email | boolean Default: true | Which sign-in buttons the app shows Console → Sign-in methods |
chat.defaultModel, chat.titleModel | string (model id) Default: "" = first enabled model | Model for new chats; model that names chats Console → Chat |
chat.maxContextMessages | number 2–100 Default: 30 | How many earlier messages are sent with each question Console → Chat |
chat.webSearch, chat.followUps | boolean Default: false, true | Web search for models that support it; follow-up suggestions Console → Chat |
chat.suggestions | string[] (≤ 8, ≤ 80 chars) Default: 4 examples | Starter chips on the empty chat Console → Chat |
models[] | array of objects Default: 3 models, disabled | Chat models: id, label, provider (anthropic, openai, gemini, openai_compatible), model, tier (free or a plan id), creditCost, vision, pdf, webSearch, reasoning, fast, maxOutputTokens, enabled, order, badge, priceIn/priceOut (your price per 1M tokens, console only) Console → Chat models |
imageModels[], imageStyles[] | arrays Default: 2 models (disabled), 12 styles | Image models (id, label, provider, model, tier, creditCost, enabled, order, pricePerImage) and styles (id, label, promptSuffix, preview) Console → Image models & styles |
imageEditModels[] | array Default: 2, disabled | Image edit models (provider openai or fal, mask, creditCost, …) Console → Image tools |
assistants[], prompts[] | arrays Default: 24, 30 | Assistants (id, name, emoji, category, description, systemPrompt, starters, model, tier, active, order) and prompt library (id, title, category, text, active, order) Console → Assistants, Prompts |
voices[], videoModels[], captionStyles[] | arrays Default: 6, 2, 8 (voices and video disabled) | Natural voices, AI video models and caption styles (fields in docs/CONTRACT.md §17.1) Console → Voices & transcription, Video, Caption styles |
limits.free.<key> | number each Default: messages 20, images 2, imageEdits 2, videoSeconds 0, captionMinutes 3, ttsChars 3000, transcribeMinutes 5, discoverBriefs 5, rewarded 3 | Free allowance per user (videoSeconds and captionMinutes per month, the rest per day) Console → Limits |
plans[] | array (≤ 4) Default: 1 example plan "Pro", inactive | id, name, tagline, order, highlight, active, color, rank, products.<period>.<platform> (store product ids; period weekly, monthly, yearly, lifetime; platform android, ios), trialDays, features[], limits (same keys as free), noAds Console → Plans |
paywall.* | object Default: cards, yearly, on limit | layout (cards/list), defaultPeriod, showOnLaunch, everyNLaunches, onLimit, closeDelaySeconds (0–5), title, subtitle, cancelText Console → Paywall |
iap.enabled | boolean Default: false | Whether the app sells anything Console → Store & purchases |
iap.removeAdsId, iap.credits.<pack> | string (store product id) Default: remove_ads, credits_small, credits_medium, credits_large | Product ids you create in your own store accounts; <pack> is small, medium or large (In-app purchases) Console → Store & purchases |
iap.creditPacks.<pack> | number Default: 50, 200, 600 | Credits granted per pack Console → Store & purchases |
discover.* | object Default: off, 6 categories | enabled, categories[] (id, name), forYou, briefTier Console → Discover |
ads.enabled | boolean Default: false | Master switch Show ads Console → Ads |
ads.mode, ads.network | "single" | "waterfall"; "admob" | "unity" | "ironsource"Default: "single", "admob" | How ads are filled (Waterfall) Console → Ads |
ads.waterfall.<format> | string[] of network names Default: ["admob"] each | Order of networks per format in waterfall mode; <format> is banner, interstitial, rewarded, native or appOpen Console → Ads |
ads.ssv | boolean Default: false | AdMob server-side verification of rewarded ads Console → Ads |
ads.placements.* | object Default: all off; interstitialMinSeconds 120 | appOpen, bannerHome, bannerGallery (boolean), nativeAssistantsEvery, interstitialEveryNMessages (0–100, 0 = off), interstitialMinSeconds (30–3600), rewardedMessages, rewardedImages (boolean) Console → Ads |
ads.admob.* | string each Default: "" | androidAppId, iosAppId, android/ios + Banner, Interstitial, Rewarded, Native, AppOpen Console → AdMob ids |
ads.unity.* | string each Default: "" | androidGameId, iosGameId, android/ios + Banner, Interstitial, Rewarded Console → Unity Ads ids |
ads.ironsource.* | string each Default: "" | androidAppKey, iosAppKey, android/ios + Banner, Interstitial, Rewarded Console → ironSource LevelPlay ids |
push.topicAll | string Default: "all" | FCM topic every device subscribes to |
GET /v1/config sends this document to the app without your prices (priceIn, priceOut, pricePerImage, pricePerSecond, pricePerMillionChars), provider names, provider model ids and voice ids, with only the models whose provider has a key, and with "demo":true|false added.
config/private (backend only)
| Field | Type · default | What it is · console page |
|---|---|---|
ai.anthropicKey, ai.openaiKey, ai.geminiKey | string (secret) Default: "" | Chat provider keys (OpenAI also serves moderation; OpenAI and Gemini are the fallback for images and voice) Console → AI providers & keys |
ai.openaiBaseUrl | string (URL) Default: "" | Only for an OpenAI proxy Console → AI providers & keys |
ai.compatible[] | array of objects Default: [] | Up to 4 OpenAI-compatible endpoints, each id, name, baseUrl, apiKey (ids compat1–compat4) Console → AI providers & keys |
ai.maxCallsPerDay | number Default: 2000 | Global cap on AI calls per day, over all users Console → AI providers & keys |
images.openaiKey, geminiKey, falKey, stabilityKey | string (secret) Default: "" | Image provider keys Console → Image providers & keys |
images.maxPerDay | number Default: 200 | Global cap on images, edits and tool calls per day Console → Image providers & keys |
images.tools.removeBg, upscale, removeBgPrice, upscalePrice | string Default: fal-ai/birefnet, fal-ai/esrgan, "", "" | fal.ai model ids of the two tools (empty = tool hidden) and your prices for the spend view Console → Image tools |
voice.openaiKey, elevenlabsKey, geminiKey, deepgramKey | string (secret) Default: "" | Voice and transcription keys Console → Voices & transcription |
voice.transcribeProvider, transcribeModel | "openai" | "deepgram" | "gemini"; stringDefault: "openai", "" | Transcription provider and optional model Console → Voices & transcription |
voice.maxTtsCharsPerDay, voice.maxTranscribeMinutesPerDay, voice.transcribePricePerMinute | number, number, string Default: 1000000, 1000, "" | Global daily caps; your price for the spend view Console → Voices & transcription |
video.falKey, replicateKey, maxPerDay | string, string, number Default: "", "", 50 | AI video keys and the global cap on jobs per day Console → Video |
discover.sources[], refreshMinutes, maxAgeDays | array of objects, number, number Default: 12 feeds (off), 30, 3 | News feeds (each id, name, url, category, enabled), refresh interval (10–1440 minutes), item age (1–14 days) Console → Discover |
moderation.provider, blocklist, blockMinorsSexual | "none" | "openai", string[] (≤ 500), trueDefault: "none", [], true (locked) | Moderation before a request reaches a provider Console → Moderation |
iap.googlePackageName, googleServiceAccountJson | string, string (secret) Default: "" | Google Play purchase verification with your service account Console → Store & purchases |
iap.appleIssuerId, appleKeyId, applePrivateKey, appleBundleId, appleEnvironment | string; key secret; "production" | "sandbox"Default: "", "production" | App Store Server API verification with your key Console → Store & purchases |
adReporting.* | string (the last three secret) Default: "" | Optional AdMob reporting for the revenue page: admobPublisherId, admobClientId, admobClientSecret, admobRefreshToken Console → Ad revenue reporting |
admobSsv.enabled | boolean Default: false | Copy of ads.ssv; the SSV callback is accepted only when both are true Console → Ads |
Secret values are stored encrypted in the console's own database with ADMIN_SECRET_KEY and written in plain form only to config/private, which the Firestore rules close to every client (Secrets at rest).
05Defaults of a fresh install
- Every chat, image, image-edit, voice and video model is disabled.
- Ads, every ad placement, in-app purchases and every plan are off.
- Modules that call a paid provider are off: Discover, natural voices, transcription, auto captions, AI video, image edit and image tools. Every default Discover feed is off.
- Modules on: chat, images, voice input and read-aloud, voice mode, photos, PDF, assistants, prompts, cloud backup, share, the on-device video editor, incognito chats and follow-up suggestions. Web search off.
- Free users: per day 20 messages, 2 images, 2 image edits and tools, 3,000 voice characters, 5 transcription minutes, 5 Discover briefs and 3 rewarded unlocks; per month 0 AI video seconds and 3 caption minutes. One example plan "Pro" (rank 1, inactive) with the product ids
pro_monthlyandpro_yearly. - Minimum age 17, age check on, notice under the composer "AI can make mistakes. Check important information.", image badge "AI-generated".
- Sign-in buttons Google, Apple and e-mail on (they only work once enabled in Firebase Authentication).
- Catalog: 3 chat models (Muse Fast, Muse Pro, Muse Max on Anthropic), 2 image models (OpenAI
gpt-image-1, fal.ai FLUX schnell), 12 styles, 24 assistants, 30 prompts, 2 image-edit models, 6 voices (OpenAI), 2 video models (fal.ai Kling), 8 caption styles, 6 Discover categories and 12 feeds.
An untouched console writes exactly these defaults, and the backend uses the same values until the console has saved once.