npx skills add ...
npx skills add firebase/agent-skills --skill extension-to-functions-codebase
npx skills add firebase/agent-skills --skill extension-to-functions-codebase
Skill for converting an installed Firebase Extension (or extension source) to a standalone Cloud Functions for Firebase codebase or publishable npm package, including upgrading triggers from V1 to V2 and configuring lifecycle hooks and declarative security
This skill guides the agent in migrating a Firebase Extension repository or instance into either:
firebase-functions,
for end-user app integration), orIt leverages native Cloud Functions for Firebase GA capabilities to handle permissions, dependencies, and lifecycle hooks natively in code, and provides instructions for modernizing legacy V1 triggers to V2 using the Destructuring Compatibility Shim.
Activate this skill when a user asks to:
Before starting, determine the target destination with the developer:
Target A: Local Functions Codebase (End-User App Integration)
functions/src/ folder.defineString, defineSecret, etc.
in .env.firebase deploy --only functions.Target B: Publishable npm Package / Shareable Open Source Package
package.json with exports map and firebase-functions
declared in dependencies (or peerDependencies).npm i <package-name>) and re-export
functions in their index.ts.git cp (or copy files and commit) to copy the extension's source
directory to the target directory."Copying [extension-name] extension to [directory] in preparation for rewrite"Assume Cloud Functions for Firebase Workload Identities, Declarative Security, and SDK Lifecycle Hooks are fully GA.
gcloud
IAM commands or create service accounts.requiresAPI and requiresRole imports from the
SDK..value() on any parameter at global scope.onInit()
callback:
When upgrading triggers to V2:
cpu: "gcf_gen1" in the function's options object.Conduct a complete inventory of everything the extension declares, ships, and documents so that nothing is lost during migration:
extension.yaml:
params: Convert to Functions params (defineString, defineSecret,
etc.).apis: Convert to requiresAPI(...) declarations.roles: Convert to requiresRole(...) declarations.lifecycleEvents (onInstall, onUpdate, onConfigure): Convert to
afterFirstDeploy and afterRedeploy hooks.resources: Note all function triggers to convert from 1st gen
(firebase-functions/v1) to 2nd gen (firebase-functions/v2), including
standard event triggers, HTTP handlers, and task queues
(onTaskDispatched).functions/: Source code, triggers, helpers, and task queue handlers.README.md, PREINSTALL.md, and POSTINSTALL.md.scripts/: Note any backfill, import, or helper scripts shipped with the
extension.package.jsonCreate an npm package for the migrated extension code (either at project root or in a dedicated workspace directory):
name: "<package-name>").devDependencies,
test runners (jest, ts-jest, @types/jest, mocha, @types/mocha), and
test scripts ("test": "...") from the legacy extension
(functions/package.json or root package.json). Do not drop test frameworks
or type definitions.firebase-functions (e.g. ^7.0.0) in dependencies (or
peerDependencies if creating a lightweight middleware package where the root
consumer manages the runtime version):
Move the extension's function source into the package's src/ folder:
index.ts).import "<package-name>") is not enough. The Firebase
CLI only deploys functions exported from the user's root entry file.Convert each exported 1st gen function trigger (onWrite, onRequest,
tasks.taskQueue().onDispatch) to its 2nd gen equivalent (onDocumentWritten,
onRequest, onTaskDispatched from firebase-functions/v2/...):
{ shimmedKey, context }) to preserve V1 business logic without rewriting
function bodies. See signature-mapping.md
and destructuring-shim.md.auth.user().onCreate(), auth.user().onDelete()),
instruct the agent to check live whether a 2nd gen alternative exists in the
installed firebase-functions package or live documentation. If 2nd gen Auth
triggers are not yet supported for those events, warn the user clearly and
halt or refuse the migration for those specific triggers until a V2
alternative becomes available.Each parameter in extension.yaml becomes a Parameterized Configuration call:
string ->
defineString("PARAM_NAME", { label: "...", description: "...", default: "..." })secret -> defineSecret("PARAM_NAME")int -> defineInt("PARAM_NAME", { label: "...", default: 123 })boolean -> defineBoolean("PARAM_NAME", { default: true })select / multiSelect -> map options into input:
defineString("PARAM_NAME", { input: { select: { options: [{ value: "val", label: "Val" }] } } })validationRegex -> map into text input options:
defineString("PARAM_NAME", { input: { text: { validationRegex: "^[a-z]+$" } } })required: true / Non-empty validation -> map nonEmpty: true into input
options:
defineString("PARAM_NAME", { input: { text: { nonEmpty: true } } })paramName.value().COLLECTION_PATH, DATASET_ID, etc.) so that existing values carry over
seamlessly in .env.queue.enqueue(...))If your extension code enqueues tasks onto its own queue using the Admin SDK
(getFunctions().taskQueue(...)):
process.env.EXT_INSTANCE_ID.EXT_INSTANCE_ID) entirely. The Admin SDK automatically targets the current
codebase:
For parameters declared with type: secret:
Replace apis and roles from extension.yaml with declarative code in your
entry file:
At deploy time, the Firebase CLI automatically grants these roles to the managed runtime service account and enables the required APIs.
afterFirstDeploy & afterRedeploy)Replace lifecycleEvents (onInstall, onUpdate, onConfigure) with SDK
lifecycle hooks:
initBigQuerySync, setupBigQuerySync) to V2
onTaskDispatched from firebase-functions/v2/tasks (removing legacy
getExtensions().runtime().setProcessingState(...) calls).README.md)Preserve whatever documentation, secrets, and setup instructions the original
extension already documented in README.md (and PREINSTALL.md /
POSTINSTALL.md), updating them as needed:
extension.yaml installation prompts or ext-*.env files with standard
.env Parameterized Configuration setup matching the defineString
parameters.export * from "<package-name>";) so users know how to expose the
functions in their root index.ts.npm run build (tsc) to ensure no
TypeScript compilation errors in src/.__tests__/, test/) are present in the
extension:
@types/jest (or the original test framework types) are present
in devDependencies so test files type-check cleanly.({ change: mockChange, context: mockContext }) instead of positional
arguments (mockChange, mockContext)).npm test or type-check test files (npx tsc --noEmit) to verify
zero regressions.firebase-extensions-migrator-support-external@google.comfirebase-extensions-migrator-support-external+subscribe@google.comrunWith options, params, and secret bindings.