Voltar ao blog

Flame Blog

Como acionar manualmente Firebase Functions no emulador

Um guia completo para chamar funções HTTP, callable, Pub/Sub, Cloud Tasks e agendadas no emulador Firebase.

Publicado em 24 de abril de 2026

Por Val — Artigo original escrito em inglês

O emulador Firebase é capaz de executar suas Firebase Functions, sejam HTTP, callable, Pub/Sub, filas de tarefas ou funções agendadas.

Funciona out of the box quando você as chama programaticamente do seu app (exceto para funções agendadas que nunca rodam localmente), mas quando se trabalha em uma função, geralmente é mais fácil chamá-la diretamente, especialmente para as que são acionadas por ações não triviais no seu app.

A interface do emulador em localhost:4000 tem um painel admin básico para Firebase Auth, Firestore e Cloud Storage, mas não para Functions. Apenas este triste painel com logs:

Painel de funções da interface do emulador do Firebase mostrando apenas logs

Neste guia, veremos como acionar manualmente:

Chamando funções com cURL

Para resolver isso, cURL é nosso amigo.

Com o emulador Functions rodando na porta padrão 5001, sua chamada típica será assim:

curl -X POST 'http://localhost:5001/{project}/{region}/{function}' \
    -H 'Content-Type: application/json' \
    -d '{data}'

Onde region é por exemplo us-central1 (padrão), e function é o nome da sua função.

Então se seu projeto é my-project e sua função é definida como:

import * as functions from 'firebase-functions/v2'

export const httpFunction = functions.https.onRequest((request, response) => {
  response.json({ ok: true })
})
import * as functions from 'firebase-functions/v1'

export const httpFunction = functions.https.onRequest((request, response) => {
  response.json({ ok: true })
})

Sua URL será:

http://localhost:5001/my-project/us-central1/httpFunction

E com uma região customizada:

export const httpFunction = functions.https.onRequest(
  { region: 'europe-west1' },
  (request, response) => {
    response.json({ ok: true })
  }
)
export const httpFunction = functions
  .region('europe-west1')
  .https.onRequest((request, response) => {
    response.json({ ok: true })
  })

Obtemos:

http://localhost:5001/my-project/europe-west1/httpFunction

Agora o payload JSON será diferente dependendo do tipo de função, e em alguns casos, dependendo de se você usa funções 1st gen ou 2nd gen.

Funções HTTP

Esta é a mais fácil porque você controla o payload inteiramente, então apenas passe seu payload diretamente no body HTTP e você o receberá na função.

Exemplo de função:

export const httpFunction = functions.https.onRequest((request, response) => {
  console.log({ url: request.url, body: request.body })
  response.json({ ok: true })
})

Podemos chamá-la assim:

curl -X POST 'http://localhost:5001/my-project/us-central1/httpFunction' \
    -H 'Content-Type: application/json' \
    -d '{"foo": "bar"}'

Isso vai logar:

{
  url: '/',
  body: {
    foo: 'bar'
  }
}

Também podemos chamá-la com um caminho customizado:

curl -X POST 'http://localhost:5001/my-project/us-central1/httpFunction/hello' \
    -H 'Content-Type: application/json' \
    -d '{"foo": "bar"}'

O que vai logar:

{
  url: '/hello',
  body: {
    foo: 'bar'
  }
}

Isso é idêntico tanto para funções 1st gen quanto 2nd gen.

Funções callable

Elas são definidas ligeiramente diferente em 1st e 2nd gen, mas são chamadas da mesma forma.

export const callableFunction = functions.https.onCall(request => {
  console.log({ data: request.data, auth: request.auth })
  return { ok: true }
})
export const callableFunction = functions.https.onCall((data, context) => {
  console.log({ data, auth: context.auth })
  return { ok: true }
})

Chamada não autenticada

curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/callableFunction' \
    -H 'Content-Type: application/json' \
    -d '{"data": {"foo": "bar"}}'

Vai logar:

{
  data: {
    foo: 'bar'
  },
  auth: undefined
}

Chamada autenticada com ID token

Também podemos passar um header Authorization: Bearer. Ele recebe um Firebase Auth ID token que será automaticamente parseado e validado pelo SDK de functions.

Aqui está nossa chamada cURL com esse token:

curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/callableFunction' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer {firebaseAuthIdToken}' \
    -d '{"data": {"foo": "bar"}}'

O que vai logar:

{
  data: {
    foo: 'bar',
  },
  auth: {
    uid: '...',
    token: { name, email, ... },
    rawToken: '...'
  }
}

Chamada autenticada com token arbitrário

Se por alguma razão você quiser usar seu próprio token não-Firebase, isso também funciona:

curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/callableFunction' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer my-token' \
  -d '{"data": {"foo": "bar"}}'
{
  data: {
    foo: 'bar',
  },
  auth: {
    token: {},
    rawToken: 'my-token'
  }
}

Funções Pub/Sub

Aqui cobriremos a chamada direta de funções Pub/Sub, com um payload compatível com o que o emulador Pub/Sub enviaria de outra forma.

Se você quiser publicar manualmente mensagens no emulador Pub/Sub, confira nosso guia dedicado para chamar o emulador Pub/Sub.

export const pubsubFunction = functions.pubsub.onMessagePublished(
  { topic: 'my-topic' },
  event => {
    // `event.data.message.json` é uma propriedade computada então precisamos logá-la separadamente
    console.log({ event, json: event.data.message.json })
  }
)
export const pubsubFunction = functions.pubsub
  .topic('my-topic')
  .onPublish(message => {
    // `message.json` é uma propriedade computada então precisamos logá-la separadamente
    console.log({ message, json: message.json })
  })

Por alguma razão na URL do emulador, as funções Pub/Sub são sufixadas com -0, então neste caso a URL será:

http://127.0.0.1:5001/my-project/us-central1/pubsubFunction-0

Podemos chamá-la com:

curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/pubsubFunction-0' \
    -H 'Content-Type: application/json' \
    -d '{"data": {"message": {"data": "eyJmb28iOiJiYXIifQ=="}}}'
curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/pubsubFunction-0' \
    -H 'Content-Type: application/json' \
    -d '{"data": {"data": "eyJmb28iOiJiYXIifQ==", "attributes": {}}}'

O payload Base64 em data acima é o mesmo exemplo {"foo": "bar"} de antes:

Buffer.from(JSON.stringify({ foo: 'bar' })).toString('base64')
// 'eyJmb28iOiJiYXIifQ=='

Para conveniência, podemos embutir a codificação Base64 no mesmo comando:

curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/pubsubFunction-0' \
    -H 'Content-Type: application/json' \
    -d '{"data": {"message": {"data": "'$(echo '{"foo": "bar"}' | base64)'"}}}'
curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/pubsubFunction-0' \
    -H 'Content-Type: application/json' \
    -d '{"data": {"data": "'$(echo '{"foo": "bar"}' | base64)'", "attributes": {}}}'

A função vai logar:

{
  event: {
    data: {
      message: {
        data: 'eyJmb28iOiJiYXIifQ==',
        publishTime: '2026-04-20T00:00:00.000Z'
      }
    }
  },
  json: {
    foo: 'bar'
  }
}
{
  message: {
    data: 'eyJmb28iOiJiYXIifQ==',
    attributes: {}
  },
  json: {
    foo: 'bar'
  }
}

Funções de fila de tarefas

export const taskQueueFunction = functions.tasks.onTaskDispatched(
  {
    retryConfig: {
      maxAttempts: 5,
      minBackoffSeconds: 60,
    },
    rateLimits: {
      maxConcurrentDispatches: 6,
    },
  },
  async request => {
    console.log({ data: request.data })
  }
)
export const taskQueueFunction = functions.tasks
  .taskQueue({
    retryConfig: {
      maxAttempts: 5,
      minBackoffSeconds: 60,
    },
    rateLimits: {
      maxConcurrentDispatches: 6,
    },
  })
  .onDispatch(async data => {
    console.log({ data })
  })

Tanto em 1st quanto em 2nd gen, precisamos chamar a função com uma propriedade data no body.

curl -X POST 'http://localhost:5001/my-project/us-central1/taskQueueFunction' \
  -H 'Content-Type: application/json' \
  -d '{"data": {"foo": "bar"}}'

O que vai logar:

{
  data: {
    foo: 'bar'
  }
}

Isso chama a fila de tarefas diretamente. Se você estiver interessado em invocar o emulador Cloud Tasks, confira nosso guia do emulador Cloud Tasks.

Funções agendadas

export const scheduledFunction = functions.scheduler.onSchedule(
  {
    schedule: 'every 30 minutes',
    timeZone: 'UTC',
  },
  event => {
    console.log({ event })
  }
)
export const scheduledFunction = functions.pubsub
  .schedule('every 30 minutes')
  .timeZone('UTC')
  .onRun(context => {
    console.log({ context })
  })

Assim como as funções Pub/Sub, estas são sufixadas com -0 por alguma razão (ainda tenho que descobrir em que condições vemos um sufixo -1).

Funções agendadas não recebem nenhum payload, apenas são acionadas.

curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/scheduledFunction-0' \
  -H 'Content-Type: application/json' \
  -d '{}'

Isso vai logar:

{
  event: {
    scheduleTime: '2026-04-20T00:00:00.000Z',
  }
}
{
  context: {
    // Isso é encaminhado do body mas não tenho certeza do que representa em produção
    params: {
    }
  }
}

Outros triggers

Estou omitindo intencionalmente triggers de Auth, Firestore, Realtime Database, Cloud Storage e outros, devido ao seu acoplamento estreito com o serviço subjacente.

É melhor testá-los realizando a ação real que os aciona.

Se você tem um caso de uso para acionar manualmente alguns desses, tenho curiosidade de saber! Me avise em val@evetools.app.

Como Flame pode ajudar ✨

Tudo isso funciona, mas e se localhost:4000 tivesse uma interface de functions que lida com todos os detalhes subjacentes destacados neste guia?

É por isso que estamos construindo o Flame, uma interface de usuário para o emulador Firebase com tudo o que falta em localhost:4000.

Veja todas as suas funções e chame-as com um único clique:

Lista de funções no Flame

Ajuste o body, copie como cURL com toda a codificação tratada, e chame quantas vezes quiser da interface.

Edição do input de funções no Flame

Sem mais lutas com terminal, sem memorizar padrões de URL, sem codificação Base64 manual. Flame lida com funções HTTP, callable, Pub/Sub, filas de tarefas e funções agendadas, tudo da mesma interface.

Também vem com uma interface melhorada para Firebase Auth, Firestore, e mais.

Se isso parece interessante para você, experimente o Flame!