Neste guia, mostramos como migrar suas extensões do ambiente Firebase Extensions descontinuado para uma função que seus usuários instalam e implantam no próprio Cloud Functions para a base de código do Firebase (2ª geração).
Esse é o caminho de migração recomendado. O Firebase vai manter uma lista de extensões com equivalentes oficiais do npm. Este guia vai mostrar como criar as suas.
Neste guia, a extensão Transmitir o Firestore para BigQuery (firestore-bigquery-export) é usada como exemplo. Cada seção termina com um Exemplo prático que mostra como era a extensão antes e depois da migração, como o pacote @firebase/firestore-bigquery-export.
Inscreva-se para receber mais informações e ajuda na migração de extensões
Se tiver dúvidas sobre como migrar do Firebase Extensions, entre em contato com firebase-extensions-migrator-support-external@google.com. Também vamos enviar um e-mail para esse grupo quando atualizarmos o guia com mais informações sobre empacotamento, teste e distribuição das funções de 2ª geração.
Para participar desse grupo, envie uma mensagem para firebase-extensions-migrator-support-external+subscribe@google.com, que vai responder com um e-mail de solicitação de participação. Você precisa responder a esse e-mail, não clicar no botão "Participar deste grupo".
Antes de começar
Para concluir essa migração, você vai usar os seguintes recursos do Cloud Functions:
Configuração parametrizada. Cada parâmetro declarado em
extension.yamlse torna um parâmetro definido no código do pacote.Papéis declarativos do IAM e APIs necessárias. Cada função declarada em
extension.yamlse torna uma chamadarequiresRole(...), e cada API se torna uma chamadarequiresAPI(...)no código da função. No momento da implantação, a CLI Firebase concede os papéis declarados a uma conta de serviço de ambiente de execução gerenciado e ativa as APIs declaradas em seu nome.Eventos de ciclo de vida para bases de código Cloud Functions. Os bases de código Cloud Functions agora oferecem suporte a eventos de ciclo de vida análogos a Firebase Extensions. Declare a configuração de tempo de instalação e de atualização com os hooks de ciclo de vida
afterFirstDeploy(...)eafterRedeploy(...). Eles substituem olifecycleEventsdeclarado emextension.yaml.
Inventário da extensão
Comece fazendo um inventário da sua extensão: uma lista completa de tudo que a extensão declara, envia e documenta. Assim, cada comportamento tem um destino definido na função de 2ª geração, e nada é perdido na migração.
Analise cada um dos itens a seguir e anote o que você encontrar:
extension.yaml, que declara seus parâmetros, funções, eventos, papéis do IAM, APIs obrigatórias, secrets e hooks de ciclo de vida.functions/, que contém o código da função, dependências, configuração de build, gatilhos e funções de fila de tarefas.README.md,PREINSTALL.mdePOSTINSTALL.md, que contêm etapas de configuração, avisos e observações de faturamento.scripts/, que contém qualquer utilitário de importação, backfill, IAM, reparo ou migração, além de outras ferramentas enviadas com a extensão.
Em seguida, para cada item em
extension.yaml, decida onde ele
será colocado no pacote npm:
Converter a configuração do usuário em parâmetros Cloud Functions (seção 5).
Converter secrets em Cloud Functions secrets (seção 6).
Converter papéis do IAM em declarações
requiresRole(...)(seção 8).Converta as APIs do Google necessárias em declarações
requiresAPI(...)quando apropriado (seção 8).Converter hooks de instalação e atualização em declarações
afterFirstDeploy(...)eafterRedeploy(...)(seção 8).
Exemplo prático:transmitir o Firestore para BigQuery
A leitura de firestore-bigquery-export/extension.yaml e functions/ produz este inventário:
| Em extension.yaml | Contagem / valor | Para onde ele vai |
|---|---|---|
| params | 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) | Parâmetros Cloud Functions (seção 5) |
| apis | bigquery.googleapis.com | requiresAPI(...) (seção 7) |
| papéis | bigquery.dataEditor, datastore.user, bigquery.user | requiresRole(...) (seção 7) |
| recursos | 1 gatilho de evento (fsexportbigquery) + funções da fila de tarefas (initBigQuerySync, setupBigQuerySync) | Funções de pacote exportadas (seção 3) |
| lifecycleEvents | onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync | afterFirstDeploy / afterRedeploy (seção 9) |
| scripts/ | import/ (preenchimento), gen-schema-view/ | Mantidos como scripts (fora do escopo aqui) |
A extensão não declara nenhum tipo: parâmetros secretos. Portanto, não há nada para migrar na seção 6 deste guia. O gatilho de evento já é da 2ª geração. Somente as funções da fila de tarefas ainda são da 1ª geração (relevante na seção 3).
Atualizar package.json
Atualize o arquivo package.json da extensão. Se você estiver migrando uma extensão,
esse pode ser o package.json raiz. Se você estiver migrando muitas extensões em um
repositório, dê a cada uma delas um pacote próprio.
Versões mínimas do SDK. Declare firebase-functions >= 7.3 e firebase-admin >= 14.2.0 como dependências. Declare sua versão do firebase-functions como uma dependência de mesmo nível também.
{
"name": "<package-name>",
"version": "1.0.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
}
},
"engines": {
"node": ">=22"
},
"peerDependencies": {
"firebase-functions": "^7.3.0"
},
"dependencies": {
"firebase-functions": "^7.3.0",
"firebase-admin": "^14.2.0"
}
}
Declare firebase-functions como uma dependência de mesmo nível, além da dependência normal, para que o projeto Cloud Functions dos usuários tenha a mesma versão do SDK com que sua biblioteca foi escrita.
Exemplo prático:transmitir o Firestore para BigQuery
Antes. O functions/package.json da extensão é particular, nomeia o ID da extensão e declara firebase-functions como uma dependência direta:
{
"name": "firestore-bigquery-export",
"main": "lib/index.js",
"private": true,
"dependencies": {
"@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
"firebase-admin": "^14.2.0",
"firebase-functions": "^6.3.2"
}
}
Depois. Um pacote publicável: nome com escopo, um mapa de exportações e
firebase-functions movido para peerDependencies:
{
"name": "@firebase/firestore-bigquery-export",
"version": "0.1.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" }, },
"engines": { "node": ">=22" },
"peerDependencies": { "firebase-functions": "^7.3.0" },
"dependencies": {
"@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
"firebase-admin": "^14.2.0",
"firebase-functions": "^7.3.0"
}
}
Fazer upgrade das funções da 1ª para a 2ª geração
Se a extensão ainda exportar funções de 1ª geração, converta cada uma delas para o equivalente de
2ª geração. Importe dos módulos firebase-functions/... e transmita
configurações de ambiente de execução nas opções de função.
É possível minimizar os esforços de reescrita com a desestruturação de eventos corrigidos de 2ª geração e evitar a reescrita da lógica da função, porque o SDK de 2ª geração agora expõe os parâmetros V1 como campos no objeto de evento, permitindo que você use parâmetros desestruturados/nomeados e mantenha a lógica de negócios inalterada.
Antes. 1ª geração:
import * as functions from "firebase-functions/v1";
export const sync = functions.firestore
.document("{collectionId}/{documentId}")
.onWrite(async (change, context) => {
await handleWrite(change.before, change.after, context.params);
});
Depois. 2ª geração:
import { onDocumentWritten } from "firebase-functions/firestore";
export const syncV2 = onDocumentWritten(
{ document: "{collectionId}/{documentId}" },
async ({change,context}) =>
await handleWrite(change.before, change.after, context.params);
);
Consulte a comparação de versões do Cloud Functions para uma lista completa das diferenças entre as funções de 1ª geração e 2ª geração.
Converter parâmetros e secrets de extensão
Parâmetros de conversão
Cada parâmetro declarado em extension.yaml se torna um parâmetro Cloud Functions.
Converter leituras diretas do ambiente:
const collectionPath = process.env.COLLECTION_PATH;
em parâmetros Cloud Functions:
import { defineString } from "firebase-functions/params";
import { onDocumentWritten} from "firebase-functions/firestore";
const collectionPath = defineString("COLLECTION_PATH");
// Pass the param directly when used as a placeholder (e.g. trigger path)
export const sync = onDocumentWritten(
{ document: collectionPath },
async (event) => {
// Call .value() to read the string inside a handler
const path = collectionPath.value();
await handleWrite(path, event);
}
);
Use collectionPath.value() para ler a string dentro de um gerenciador; use collectionPath diretamente onde um marcador de posição é esperado, como um caminho de acionador de função.
A CLI Firebase descobre seus parâmetros e lê os valores deles em .env, .env.projectId ou solicita aos usuários durante a implantação. Mantenha os mesmos nomes de parâmetros para que os valores de uma instalação atual sejam transferidos.
É importante não mudar os nomes de parâmetros declarados no seu código de forma alguma. A migração da extensão preserva automaticamente o valor do parâmetro do usuário final, mas apenas quando os nomes permanecem inalterados.
Exemplo prático:transmitir o Firestore para BigQuery
Antes Um parâmetro declarado em extension.yaml, lido como uma variável de ambiente
bruta em config.ts:
# extension.yaml
- param: COLLECTION_PATH
label: Collection path
type: string
required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,
Depois. Um defineString. A CLI o descobre e lê de .env:
// src/config.ts
import { defineString } from "firebase-functions/params";
collectionPath: defineString("COLLECTION_PATH", {
label: "Collection path",
// We now support "nonEmpty: true" to ensure a value other than the empty string
// is entered, analagous to "required: true" in extensions.yaml
input: { text: { nonEmpty: true} }
}),
O nome do parâmetro não mudou, então um .env atual continua funcionando.
Converter secrets
Em extension.yaml, você declara secrets com type: secret. O ambiente de execução das extensões armazena e vincula esses parâmetros, para que o código da extensão possa ler process.env.PARAM_NAME diretamente. Em uma base de código Cloud Functions típica, você declara e vincula cada segredo explicitamente:
import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";
const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);
Depois que a extensão for migrada para um pacote/kit npm, as referências secretas serão
gerenciadas no arquivo .env do usuário final. É importante não mudar os nomes de segredos declarados no seu código de forma alguma. Durante a migração, os segredos do usuário final serão migrados de acordo.
Exemplo prático:acionar e-mail de Cloud Firestore
Antes. MAIL_COLLECTION e SMTP_PASSWORD são lidos como variáveis de ambiente brutas em config.ts:
# extension.yaml
- param: MAIL_COLLECTION
label: Email documents collection
type: string
default: mail
required: true
- param: SMTP_PASSWORD
label: SMTP password
type: secret
// functions/src/config.ts
mailCollection: process.env.MAIL_COLLECTION,
smtpPassword: process.env.SMTP_PASSWORD,
Depois. Um defineString e um defineSecret. A CLI descobre os dois e lê de .env.
import { defineString, defineSecret } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";
const mailCollection = defineString("MAIL_COLLECTION",{ label: "Email documents collection",
default: "mail"
});
const smtpPassword = defineSecret("SMTP_PASSWORD", { label: "SMTP password" });
export const processQueue = onDocumentWritten(
{ document: `${mailCollection}/{documentId}`, secrets: [smtpPassword] },
async (event) => {
const collection = mailCollection.value();
const password = smtpPassword.value();
// ...
}
);
Migrar chamadas internas da fila de tarefas
Algumas extensões enfileiram o trabalho nas próprias filas de tarefas de dentro do código da função, usando o Firebase Admin SDK. Isso é diferente de receber uma tarefa despachada (abordado nas seções Fazer upgrade de funções e Converter hooks de ciclo de vida). Aqui, seu código é o produtor que chama queue.enqueue(...).
As versões anteriores do Admin SDK exigiam que as extensões transmitissem o próprio ID da instância da extensão como um segundo parâmetro para segmentar uma função da fila de tarefas na mesma extensão. A partir do `firebase-admin` 14.2.0, isso não é necessário nem recomendado. Por padrão, a API Task Queue agora vai segmentar filas de tarefas no mesmo contexto (por exemplo, extensão). É seguro e recomendável remover esse parâmetro no código, tanto como uma extensão quanto como funções independentes. A remoção desse parâmetro garante a portabilidade e a compatibilidade com versões futuras.
Todo o resto sobre a chamada de enfileiramento (o caminho do recurso locations/region/functions/name, o payload da tarefa e a lógica de nova tentativa) permanece o mesmo.
Consulte /docs/functions/task-functions para mais detalhes sobre como enfileirar funções com Cloud Tasks.
Antes. Extensão de 1ª geração
import { getFunctions } from "firebase-admin/functions";
const queue = getFunctions().taskQueue(
`locations/${config.location}/functions/syncBigQuery`,
process.env.EXT_INSTANCE_ID, // extension instance ID, injected by the runtime
);
await queue.enqueue(taskData);
Depois. Extensão de 2ª geração
import { getFunctions } from "firebase-admin/functions";
const queue = getFunctions().taskQueue(
`locations/${process.env.FUNCTION_REGION}/functions/syncBigQuery`);
await queue.enqueue(taskData);
Se a chamada de enfileiramento segmentar uma base de código prefixada, o nome da função descoberta também será prefixado (por exemplo, orders-syncBigQuery).
Declarar as APIs e os papéis do IAM necessários
Mova os requisitos de IAM e API da extensão de extension.yaml para código:
import { requiresAPI, requiresRole } from "firebase-functions"
requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");
Com a segurança declarativa, a CLI Firebase cria ou atualiza uma conta de serviço de tempo de execução gerenciado para a base de código e concede a ela a união de todos os papéis declarados. Documente para seus usuários que todas as funções no codebase são executadas com essas funções, a menos que a API final seja compatível com um modelo mais restrito.
Exemplo prático:transmitir o Firestore para BigQuery
Antes. Declarado em extension.yaml. O ambiente de execução das extensões ativou a API e concedeu os papéis a uma conta gerenciada:
apis:
- apiName: bigquery.googleapis.com
roles:
- role: bigquery.dataEditor
- role: datastore.user
- role: bigquery.user
Depois. Declarado no código com requiresAPI e requiresRole:
import { requiresAPI, requiresRole } from "firebase-functions/";
requiresAPI("bigquery.googleapis.com",
"Needed to write changelog rows and views");
requiresRole("roles/biguqery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");
Converter hooks de ciclo de vida
Se a extensão chamar getExtensions().runtime(), por exemplo,
setProcessingState ou setFatalError, exclua essas chamadas, porque elas vão gerar
um erro se forem chamadas de uma função de 2ª geração implantada normalmente. O estado do ciclo de vida agora é controlado por afterFirstDeploy e afterRedeploy, em que esse rastreamento de estado não é usado.
O Firebase Extensions pode executar a configuração quando um usuário instala, atualiza ou reconfigura uma extensão. No pacote npm, declare ações equivalentes do ciclo de vida no código.
Para uma configuração única:
import { afterFirstDeploy } from "firebase-functions/lifecycle";
import { onTaskDispatched } from "firebase-functions/tasks";
export const runInitialSetup = onTaskDispatched(async (request) => {
await initializeResources(request.data);
});
afterFirstDeploy({
task: {
function: "runInitialSetup",
body: {}
}
});
Para atualizações de configuração ou código:
import { afterRedeploy } from "firebase-functions/lifecycle";
afterRedeploy({
task: {
function: "runInitialSetup",
body: { reconcile: true }
}
});
Torne as ações de ciclo de vida idempotentes. Seus usuários talvez precisem executar novamente os comandos manualmente se o envio ou a execução falhar:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
Exemplo prático:transmitir o Firestore para BigQuery
Antes. lifecycleEvents em extension.yaml, causado pelo tempo de execução das extensões:
lifecycleEvents:
onInstall:
function: initBigQuerySync
processingMessage: Configuring BigQuery Sync.
onUpdate:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
onConfigure:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
Depois. Declarada no código, a tarefa provisiona BigQuery na primeira implantação:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";
afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });
O provisionamento é idempotente. Portanto, uma nova execução reconcilia o conjunto de dados, a tabela e as visualizações. Os usuários podem executar novamente de forma manual com firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.
Configuração de documentos para seus usuários
Escreva um pacote
READMEque explique, no mínimo:Os valores de
.envnecessários para o pacote.Os secrets que o pacote exige e como migrar valores de secrets existentes.
Os papéis do IAM que o pacote declara com
requiresRole(...).As APIs do Google que o pacote ativa ou exige.
Os hooks de ciclo de vida que o pacote declara e como executá-los novamente manualmente.
Observações de faturamento.
O que mudou em comparação com a extensão original.
Exemplo prático:transmitir o Firestore para BigQuery
O README do pacote envia uma tabela concreta de "o que mudou":
| Problema | Como a extensão | Como @firebase/firestore-bigquery-export |
|---|---|---|
| Configuração | Parâmetros de extensão | Parâmetros de funções via .env |
| IAM | Concedido por extensões | requiresRole(...), aplicado na implantação |
| Provisionamento | Tarefa de ciclo de vida por extensões | Tarefa afterFirstDeploy / afterRedeploy |
| Nomes de funções | ext-instanceId-fsexportbigquery | fsexportbigquery (com prefixo opcional) |
Testar a função de 2ª geração
Agora você tem uma função de 2ª geração que, quando implantada, se comporta de maneira idêntica a uma nova instalação da sua extensão. A última etapa é verificar e corrigir problemas introduzidos acidentalmente ao longo do processo.
Verifique se você está usando
firebase-tools
>= 15.25.1 e implante a função convertida de 2ª geração em um projeto de teste
com os recursos adequados para testar o comportamento dela. Se você já tiver um projeto de teste configurado para testar sua extensão, use o comando:
firebase deploy --only functions
Depois de inserir esse comando, preencha o assistente resultante solicitando os valores de parâmetro da mesma forma que você preencheria o formulário de instalação no console Firebase para a extensão.
Exemplo prático:transmitir o Firestore para BigQuery
Verificamos a sincronização de ponta a ponta do Cloud Firestore para o BigQuery:
- No console Cloud Firestore, crie a coleção definida como COLLECTION_PATH (usuários), se ela ainda não existir.
- Crie um documento chamado bigquery-mirror-test com campos e valores definidos por você.
- No console do BigQuery, consulte a tabela bruta de mudanças. Ele precisa conter uma única linha registrando a criação do documento:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- Consulte a visualização mais recente, que vai retornar o último evento de mudança do único documento presente:
bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- Exclua o documento
bigquery-mirror-testem Cloud Firestore. Ele desaparece da visualização mais recente, e um eventoDELETEé anexado à tabela bruta do changelog.
É possível inspecionar o histórico completo de um único documento com:
SELECT *
FROM `PROJECT_ID.analytics.users_raw_changelog`
WHERE document_name = "bigquery-mirror-test"
ORDER BY timestamp ASC
Diferenças em relação ao teste da extensão:
- O gatilho é implantado como
fsexportbigquery(opcionalmente com prefixo de base de código), nãoext-<instanceId>-fsexportbigquery. Procure esse nome no painel e nos registros Cloud Functions. - Agora seu código será executado no Firebase Local Emulator Suite como funções normais. É possível definir o valor dos parâmetros a serem usados no emulador com
.env.local. Também é possível testar seu código usando o SDK firebase-functions-test, conforme descrito em Teste de unidade de Cloud Functions - O provisionamento não é mais feito pelo ambiente de execução das extensões. Se a tabela de
changelog estiver faltando após a implantação, execute novamente a tarefa de configuração manualmente:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. A tarefa é idempotente. Portanto, ao executá-la novamente, o conjunto de dados, a tabela e as visualizações são reconciliados. - Os valores de parâmetro vêm de
.envem vez do formulário de instalação. Portanto, as novas execuções defirebase deploynão são interativas depois que.envé concluído.