From 2cfb83d8f728bdb1a561723a690f23171cf3a5e2 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 17 Oct 2025 16:55:01 -0700 Subject: [PATCH 1/7] BREAKING: hide functions:config:* commands by default We'll gate all mutating functions:config:* commands behind the new legacyRuntimeConfigCommands experiment. We'll keep functions:config:{get,export} available for inspection. Some minor refactoring to align deprecation message across all surfaces. --- src/commands/functions-config-clone.ts | 4 +- src/commands/functions-config-export.ts | 2 + src/commands/functions-config-get.ts | 3 +- src/commands/functions-config-set.ts | 4 +- src/commands/functions-config-unset.ts | 4 +- .../internaltesting-functions-discover.ts | 2 +- src/deploy/functions/prepare.ts | 2 +- .../functions/prepareFunctionsUpload.ts | 3 +- src/experiments.ts | 8 ++-- src/functions/deprecationWarnings.ts | 22 --------- src/functionsConfig.ts | 45 +++++++++++++++++++ 11 files changed, 61 insertions(+), 38 deletions(-) delete mode 100644 src/functions/deprecationWarnings.ts diff --git a/src/commands/functions-config-clone.ts b/src/commands/functions-config-clone.ts index 12b0d2c129d..d5fda76983b 100644 --- a/src/commands/functions-config-clone.ts +++ b/src/commands/functions-config-clone.ts @@ -8,13 +8,13 @@ import { requirePermissions } from "../requirePermissions"; import * as functionsConfig from "../functionsConfig"; import { functionsConfigClone } from "../functionsConfigClone"; import * as utils from "../utils"; -import { logFunctionsConfigDeprecationWarning } from "../functions/deprecationWarnings"; export const command = new Command("functions:config:clone") .description("clone environment config from another project") .option("--from ", "the project from which to clone configuration") .option("--only ", "a comma-separated list of keys to clone") .option("--except ", "a comma-separated list of keys to not clone") + .before(functionsConfig.ensureLegacyRuntimeConfigCommandsEnabled) .before(requirePermissions, [ "runtimeconfig.configs.list", "runtimeconfig.configs.create", @@ -57,5 +57,5 @@ export const command = new Command("functions:config:clone") "firebase deploy --only functions", )}\n`, ); - logFunctionsConfigDeprecationWarning(); + functionsConfig.logFunctionsConfigDeprecationWarning(); }); diff --git a/src/commands/functions-config-export.ts b/src/commands/functions-config-export.ts index fc50bbbd764..22d20977506 100644 --- a/src/commands/functions-config-export.ts +++ b/src/commands/functions-config-export.ts @@ -12,6 +12,7 @@ import { requirePermissions } from "../requirePermissions"; import { logBullet, logWarning } from "../utils"; import { zip } from "../functional"; import * as configExport from "../functions/runtimeConfigExport"; +import * as functionsConfig from "../functionsConfig"; import { requireConfig } from "../requireConfig"; import type { Options } from "../options"; @@ -88,6 +89,7 @@ function fromEntries(itr: Iterable<[string, V]>): Record { export const command = new Command("functions:config:export") .description("export environment config as environment variables in dotenv format") + .before(functionsConfig.ensureLegacyRuntimeConfigCommandsEnabled) .before(requirePermissions, [ "runtimeconfig.configs.list", "runtimeconfig.configs.get", diff --git a/src/commands/functions-config-get.ts b/src/commands/functions-config-get.ts index 3827b80d2cf..1b944686a4e 100644 --- a/src/commands/functions-config-get.ts +++ b/src/commands/functions-config-get.ts @@ -6,7 +6,6 @@ import { logger } from "../logger"; import { needProjectId } from "../projectUtils"; import { requirePermissions } from "../requirePermissions"; import * as functionsConfig from "../functionsConfig"; -import { logFunctionsConfigDeprecationWarning } from "../functions/deprecationWarnings"; async function materialize(projectId: string, path?: string): Promise { if (path === undefined) { @@ -32,6 +31,6 @@ export const command = new Command("functions:config:get [path]") .action(async (path, options) => { const result = await materialize(needProjectId(options), path); logger.info(JSON.stringify(result, null, 2)); - logFunctionsConfigDeprecationWarning(); + functionsConfig.logFunctionsConfigDeprecationWarning(); return result; }); diff --git a/src/commands/functions-config-set.ts b/src/commands/functions-config-set.ts index 6ab9aa1f67a..2059c45c8ae 100644 --- a/src/commands/functions-config-set.ts +++ b/src/commands/functions-config-set.ts @@ -7,10 +7,10 @@ import { needProjectId } from "../projectUtils"; import { requirePermissions } from "../requirePermissions"; import * as functionsConfig from "../functionsConfig"; import * as utils from "../utils"; -import { logFunctionsConfigDeprecationWarning } from "../functions/deprecationWarnings"; export const command = new Command("functions:config:set [values...]") .description("set environment config with key=value syntax") + .before(functionsConfig.ensureLegacyRuntimeConfigCommandsEnabled) .before(requirePermissions, [ "runtimeconfig.configs.list", "runtimeconfig.configs.create", @@ -50,5 +50,5 @@ export const command = new Command("functions:config:set [values...]") "firebase deploy --only functions", )}\n`, ); - logFunctionsConfigDeprecationWarning(); + functionsConfig.logFunctionsConfigDeprecationWarning(); }); diff --git a/src/commands/functions-config-unset.ts b/src/commands/functions-config-unset.ts index 4b326876236..efcf83a7530 100644 --- a/src/commands/functions-config-unset.ts +++ b/src/commands/functions-config-unset.ts @@ -8,10 +8,10 @@ import * as functionsConfig from "../functionsConfig"; import * as runtimeconfig from "../gcp/runtimeconfig"; import * as utils from "../utils"; import { FirebaseError } from "../error"; -import { logFunctionsConfigDeprecationWarning } from "../functions/deprecationWarnings"; export const command = new Command("functions:config:unset [keys...]") .description("unset environment config at the specified path(s)") + .before(functionsConfig.ensureLegacyRuntimeConfigCommandsEnabled) .before(requirePermissions, [ "runtimeconfig.configs.list", "runtimeconfig.configs.create", @@ -45,5 +45,5 @@ export const command = new Command("functions:config:unset [keys...]") "firebase deploy --only functions", )}\n`, ); - logFunctionsConfigDeprecationWarning(); + functionsConfig.logFunctionsConfigDeprecationWarning(); }); diff --git a/src/commands/internaltesting-functions-discover.ts b/src/commands/internaltesting-functions-discover.ts index 981b3411596..8cb672585a6 100644 --- a/src/commands/internaltesting-functions-discover.ts +++ b/src/commands/internaltesting-functions-discover.ts @@ -24,7 +24,7 @@ export const command = new Command("internaltesting:functions:discover") } let runtimeConfig: Record = { firebase: firebaseConfig }; - const allowFunctionsConfig = experiments.isEnabled("dangerouslyAllowFunctionsConfig"); + const allowFunctionsConfig = experiments.isEnabled("legacyRuntimeConfigCommands"); if (allowFunctionsConfig) { try { diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index e947945379f..eb70fd13ea0 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -94,7 +94,7 @@ export async function prepare( // ===Phase 1. Load codebases from source with optional runtime config. let runtimeConfig: Record = { firebase: firebaseConfig }; - const allowFunctionsConfig = experiments.isEnabled("dangerouslyAllowFunctionsConfig"); + const allowFunctionsConfig = experiments.isEnabled("legacyRuntimeConfigCommands"); // Load runtime config if experiment allows it and API is enabled if (allowFunctionsConfig && checkAPIsEnabled[1]) { diff --git a/src/deploy/functions/prepareFunctionsUpload.ts b/src/deploy/functions/prepareFunctionsUpload.ts index 6f2ba2cdd3d..64407702c1e 100644 --- a/src/deploy/functions/prepareFunctionsUpload.ts +++ b/src/deploy/functions/prepareFunctionsUpload.ts @@ -13,7 +13,6 @@ import * as functionsConfig from "../../functionsConfig"; import * as utils from "../../utils"; import * as fsAsync from "../../fsAsync"; import * as projectConfig from "../../functions/projectConfig"; -import { logFunctionsConfigDeprecationWarning } from "../../functions/deprecationWarnings"; const CONFIG_DEST_FILE = ".runtimeconfig.json"; @@ -104,7 +103,7 @@ async function packageSource( // Only warn about deprecated runtime config if there are user-defined values // (i.e., keys other than the default 'firebase' key) if (Object.keys(runtimeConfig).some((k) => k !== "firebase")) { - logFunctionsConfigDeprecationWarning(); + functionsConfig.logFunctionsConfigDeprecationWarning(); } } await pipeAsync(archive, fileStream); diff --git a/src/experiments.ts b/src/experiments.ts index 8c16408cb06..c9f60ac23c8 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -57,11 +57,11 @@ export const ALL_EXPERIMENTS = experiments({ "of how that image was created.", public: true, }, - dangerouslyAllowFunctionsConfig: { - shortDescription: "Allows the use of deprecated functions.config() API", + legacyRuntimeConfigCommands: { + shortDescription: "Expose legacy functions.config() CLI commands", fullDescription: - "The functions.config() API is deprecated and will be removed on December 31, 2025. " + - "This experiment allows continued use of the API during the migration period.", + "The Cloud Runtime Config API is deprecated. Enable this experiment to continue using the " + + "`functions:config:*` commands while you migrate to the Firebase Functions params APIs.", default: true, public: true, }, diff --git a/src/functions/deprecationWarnings.ts b/src/functions/deprecationWarnings.ts deleted file mode 100644 index 68c3c0b38d4..00000000000 --- a/src/functions/deprecationWarnings.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { logWarningToStderr } from "../utils"; - -const FUNCTIONS_CONFIG_DEPRECATION_MESSAGE = `DEPRECATION NOTICE: Action required to deploy after March 2026 - - functions.config() API is deprecated. - Cloud Runtime Configuration API, the Google Cloud service used to store function configuration data, will be shut down in March 2026. As a result, you must migrate away from using functions.config() to continue deploying your functions after March 2026. - - What this means for you: - - - The Firebase CLI commands for managing this configuration (functions:config:set, get, unset, clone, and export) are deprecated. These commands will no longer work after March 2026. - - firebase deploy command will fail for functions that use the legacy functions.config() API after March 2026. - - Existing deployments will continue to work with their current configuration. - - See your migration options at: https://firebase.google.com/docs/functions/config-env#migrate-to-dotenv`; - -/** - * Logs a deprecation warning for functions.config() usage - */ -export function logFunctionsConfigDeprecationWarning(): void { - logWarningToStderr(FUNCTIONS_CONFIG_DEPRECATION_MESSAGE); -} diff --git a/src/functionsConfig.ts b/src/functionsConfig.ts index eda054cf627..93f83557c38 100644 --- a/src/functionsConfig.ts +++ b/src/functionsConfig.ts @@ -8,11 +8,56 @@ import { FirebaseError } from "./error"; import { needProjectId } from "./projectUtils"; import * as runtimeconfig from "./gcp/runtimeconfig"; import * as args from "./deploy/functions/args"; +import * as experiments from "./experiments"; +import { logWarningToStderr } from "./utils"; export const RESERVED_NAMESPACES = ["firebase"]; const apiClient = new Client({ urlPrefix: firebaseApiOrigin() }); +const LEGACY_RUNTIME_CONFIG_EXPERIMENT = "legacyRuntimeConfigCommands"; + +const FUNCTIONS_CONFIG_DEPRECATION_MESSAGE = `DEPRECATION NOTICE: Action required before March 2026 + +The functions.config() API and the Cloud Runtime Config service are deprecated. Deploys that rely on functions.config() will fail once Runtime Config shuts down in March 2026. + +The legacy functions:config:* CLI commands are deprecated and will be removed before March 2026. + +Migrate configuration to the Firebase Functions params APIs: + + import { defineString, defineSecret } from "firebase-functions/params"; + + export const apiBaseUrl = defineString("API_BASE_URL"); + export const serviceApiKey = defineSecret("SERVICE_API_KEY"); + +To convert existing runtime config values, try the interactive migration command: + + firebase functions:config:export + +Learn more: https://firebase.google.com/docs/functions/config-env#migrate-to-dotenv`; + +const LEGACY_GUIDANCE_MESSAGE = `${FUNCTIONS_CONFIG_DEPRECATION_MESSAGE} + +To run this legacy command temporarily, run the following command and try again: + + firebase experiments:enable ${LEGACY_RUNTIME_CONFIG_EXPERIMENT} +`; + +export function getFunctionsConfigDeprecationMessage(): string { + return FUNCTIONS_CONFIG_DEPRECATION_MESSAGE; +} + +export function logFunctionsConfigDeprecationWarning(): void { + logWarningToStderr(FUNCTIONS_CONFIG_DEPRECATION_MESSAGE); +} + +export function ensureLegacyRuntimeConfigCommandsEnabled(): void { + if (experiments.isEnabled(LEGACY_RUNTIME_CONFIG_EXPERIMENT)) { + return; + } + throw new FirebaseError(LEGACY_GUIDANCE_MESSAGE, { exit: 1 }); +} + interface Id { config: string; variable: string; From 4f6a5cb0e133c0b279789dace1d0998a6676c5e6 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 17 Oct 2025 17:00:03 -0700 Subject: [PATCH 2/7] set legacyRuntimeConfigCommands experiment to default false. --- src/experiments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/experiments.ts b/src/experiments.ts index c9f60ac23c8..e200e4da5f0 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -39,7 +39,7 @@ export const ALL_EXPERIMENTS = experiments({ "of deploys. This has been made an experiment due to backend bugs that are " + "temporarily causing failures in some regions with this optimization enabled", public: true, - default: true, + default: false, }, deletegcfartifacts: { shortDescription: `Add the ${bold( From b9bbb990f72e4b7fa9eab7b474c2559bae491919 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 19 Oct 2025 08:36:46 -0700 Subject: [PATCH 3/7] revert accidentally flipped experiment default. --- src/experiments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/experiments.ts b/src/experiments.ts index e200e4da5f0..3e7ef0f7aee 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -55,7 +55,7 @@ export const ALL_EXPERIMENTS = experiments({ `Registry. The ${bold("functions:deletegcfartifacts")} command ` + "will delete all Docker images created by Google Cloud Functions irrespective " + "of how that image was created.", - public: true, + public: false, }, legacyRuntimeConfigCommands: { shortDescription: "Expose legacy functions.config() CLI commands", From 169cb91d88dae39e585175003dec4ce734eec8b3 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 19 Oct 2025 16:34:06 -0700 Subject: [PATCH 4/7] don't hide functions:config:export command. --- src/commands/functions-config-export.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/functions-config-export.ts b/src/commands/functions-config-export.ts index 22d20977506..9bb0fafa85f 100644 --- a/src/commands/functions-config-export.ts +++ b/src/commands/functions-config-export.ts @@ -89,7 +89,6 @@ function fromEntries(itr: Iterable<[string, V]>): Record { export const command = new Command("functions:config:export") .description("export environment config as environment variables in dotenv format") - .before(functionsConfig.ensureLegacyRuntimeConfigCommandsEnabled) .before(requirePermissions, [ "runtimeconfig.configs.list", "runtimeconfig.configs.get", From 0ad0b2b0302b69d037e2acb0b502a95f7f408871 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 19 Oct 2025 16:34:06 -0700 Subject: [PATCH 5/7] don't hide functions:config:export command. --- src/commands/functions-config-export.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/commands/functions-config-export.ts b/src/commands/functions-config-export.ts index 22d20977506..fc50bbbd764 100644 --- a/src/commands/functions-config-export.ts +++ b/src/commands/functions-config-export.ts @@ -12,7 +12,6 @@ import { requirePermissions } from "../requirePermissions"; import { logBullet, logWarning } from "../utils"; import { zip } from "../functional"; import * as configExport from "../functions/runtimeConfigExport"; -import * as functionsConfig from "../functionsConfig"; import { requireConfig } from "../requireConfig"; import type { Options } from "../options"; @@ -89,7 +88,6 @@ function fromEntries(itr: Iterable<[string, V]>): Record { export const command = new Command("functions:config:export") .description("export environment config as environment variables in dotenv format") - .before(functionsConfig.ensureLegacyRuntimeConfigCommandsEnabled) .before(requirePermissions, [ "runtimeconfig.configs.list", "runtimeconfig.configs.get", From 6860e4c7028d898d5096a734c6efe0720a9b1612 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 19 Oct 2025 16:55:11 -0700 Subject: [PATCH 6/7] update messaging to reflect new export flow --- src/functionsConfig.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/functionsConfig.ts b/src/functionsConfig.ts index 93f83557c38..b8ff6a04b8b 100644 --- a/src/functionsConfig.ts +++ b/src/functionsConfig.ts @@ -25,16 +25,22 @@ The legacy functions:config:* CLI commands are deprecated and will be removed be Migrate configuration to the Firebase Functions params APIs: - import { defineString, defineSecret } from "firebase-functions/params"; + import { defineJsonSecret } from "firebase-functions/params"; - export const apiBaseUrl = defineString("API_BASE_URL"); - export const serviceApiKey = defineSecret("SERVICE_API_KEY"); + const config = defineJsonSecret("RUNTIME_CONFIG"); + + exports.myFunction = functions + .runWith({ secrets: [config] }) + .https.onRequest((req, res) => { + const apiKey = config.value().service.key; + // ... + }); To convert existing runtime config values, try the interactive migration command: firebase functions:config:export -Learn more: https://firebase.google.com/docs/functions/config-env#migrate-to-dotenv`; +Learn more: https://firebase.google.com/docs/functions/config-env#migrate-config`; const LEGACY_GUIDANCE_MESSAGE = `${FUNCTIONS_CONFIG_DEPRECATION_MESSAGE} From 429a62acc49a5ef8fa9ee146cd5307382c85a45d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 20 Oct 2025 12:50:10 -0700 Subject: [PATCH 7/7] Update src/functionsConfig.ts Co-authored-by: Jeff <3759507+jhuleatt@users.noreply.github.com> --- src/functionsConfig.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/functionsConfig.ts b/src/functionsConfig.ts index b8ff6a04b8b..be5d698614c 100644 --- a/src/functionsConfig.ts +++ b/src/functionsConfig.ts @@ -23,24 +23,14 @@ The functions.config() API and the Cloud Runtime Config service are deprecated. The legacy functions:config:* CLI commands are deprecated and will be removed before March 2026. -Migrate configuration to the Firebase Functions params APIs: +Learn how to migrate from functions.config() to the params package: - import { defineJsonSecret } from "firebase-functions/params"; +https://firebase.google.com/docs/functions/config-env#migrate-config - const config = defineJsonSecret("RUNTIME_CONFIG"); - - exports.myFunction = functions - .runWith({ secrets: [config] }) - .https.onRequest((req, res) => { - const apiKey = config.value().service.key; - // ... - }); - -To convert existing runtime config values, try the interactive migration command: +To convert existing functions.config() values to params, try the interactive migration command: firebase functions:config:export - -Learn more: https://firebase.google.com/docs/functions/config-env#migrate-config`; +`; const LEGACY_GUIDANCE_MESSAGE = `${FUNCTIONS_CONFIG_DEPRECATION_MESSAGE}