Flame Blog
Cómo activar manualmente Firebase Functions en el emulador
Una guía completa para llamar funciones HTTP, callable, Pub/Sub, Cloud Tasks y programadas en el emulador de Firebase.
Publicado el 24 de abril de 2026
Por Val — Artículo original escrito en inglés
El emulador de Firebase es capaz de ejecutar tus Firebase Functions, ya sean HTTP, callable, Pub/Sub, colas de tareas o funciones programadas.
Funciona out of the box cuando las llamas programáticamente desde tu app (excepto para las funciones programadas que nunca se ejecutan localmente), pero cuando trabajas en una función, suele ser más fácil llamarla directamente, especialmente para las que son desencadenadas por acciones no triviales en tu app.
La interfaz del emulador en localhost:4000 tiene un panel admin básico para Firebase Auth, Firestore y Cloud Storage, pero no para Functions. Solo este triste panel con logs:
En esta guía, veremos cómo activar manualmente:
- Funciones HTTP
- Funciones callable
- Funciones Pub/Sub
- Funciones de cola de tareas
- Funciones programadas
Llamar funciones con cURL
Para resolver esto, cURL es nuestro amigo.
Con el emulador Functions ejecutándose en el puerto por defecto 5001, tu llamada típica se verá así:
curl -X POST 'http://localhost:5001/{project}/{region}/{function}' \
-H 'Content-Type: application/json' \
-d '{data}'
Donde region es por ejemplo us-central1 (por defecto), y function es el nombre de tu función.
Así que si tu proyecto es my-project y tu función está 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 })
}) Su URL será:
http://localhost:5001/my-project/us-central1/httpFunction
Y con una región personalizada:
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 })
}) Obtenemos:
http://localhost:5001/my-project/europe-west1/httpFunction
Ahora el payload JSON va a ser diferente dependiendo del tipo de función, y en algunos casos, dependiendo de si usas funciones 1st gen o 2nd gen.
Funciones HTTP
Esta es la más fácil porque controlas el payload por completo, así que solo pasa tu payload directamente en el body HTTP y lo recibirás en la función.
Ejemplo de función:
export const httpFunction = functions.https.onRequest((request, response) => {
console.log({ url: request.url, body: request.body })
response.json({ ok: true })
})
Podemos llamarla así:
curl -X POST 'http://localhost:5001/my-project/us-central1/httpFunction' \
-H 'Content-Type: application/json' \
-d '{"foo": "bar"}'
Registrará:
{
url: '/',
body: {
foo: 'bar'
}
}
También podemos llamarla con una ruta personalizada:
curl -X POST 'http://localhost:5001/my-project/us-central1/httpFunction/hello' \
-H 'Content-Type: application/json' \
-d '{"foo": "bar"}'
Lo cual registrará:
{
url: '/hello',
body: {
foo: 'bar'
}
}
Esto es idéntico tanto para funciones 1st gen como 2nd gen.
Funciones callable
Se definen ligeramente diferente en 1st y 2nd gen, pero se llaman de la misma 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 }
}) Llamada no autenticada
curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/callableFunction' \
-H 'Content-Type: application/json' \
-d '{"data": {"foo": "bar"}}'
Registrará:
{
data: {
foo: 'bar'
},
auth: undefined
}
Llamada autenticada con ID token
También podemos pasar un header Authorization: Bearer. Toma un Firebase Auth ID token que será automáticamente parseado y validado por el SDK de functions.
Aquí está nuestra llamada cURL con ese 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"}}'
Lo cual registrará:
{
data: {
foo: 'bar',
},
auth: {
uid: '...',
token: { name, email, ... },
rawToken: '...'
}
}
Llamada autenticada con token arbitrario
Si por alguna razón quieres usar tu propio token no-Firebase, esto también 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'
}
}
Funciones Pub/Sub
Aquí cubriremos el llamado directo de funciones Pub/Sub, con un payload compatible con lo que el emulador de Pub/Sub enviaría de otro modo.
Si quieres publicar manualmente mensajes al emulador de Pub/Sub, consulta nuestra guía dedicada para llamar al emulador de Pub/Sub.
export const pubsubFunction = functions.pubsub.onMessagePublished(
{ topic: 'my-topic' },
event => {
// `event.data.message.json` es una propiedad computada así que necesitamos loguearla por separado
console.log({ event, json: event.data.message.json })
}
) export const pubsubFunction = functions.pubsub
.topic('my-topic')
.onPublish(message => {
// `message.json` es una propiedad computada así que necesitamos loguearla por separado
console.log({ message, json: message.json })
}) Por alguna razón en la URL del emulador, las funciones Pub/Sub se sufijan con -0, así que en este caso la URL será:
http://127.0.0.1:5001/my-project/us-central1/pubsubFunction-0
Podemos llamarla con:
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": {}}}' El payload Base64 en data de arriba es el mismo ejemplo {"foo": "bar"} de antes:
Buffer.from(JSON.stringify({ foo: 'bar' })).toString('base64')
// 'eyJmb28iOiJiYXIifQ=='
Para mayor conveniencia podemos embeber la codificación Base64 en el mismo 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": {}}}' La función registrará:
{
event: {
data: {
message: {
data: 'eyJmb28iOiJiYXIifQ==',
publishTime: '2026-04-20T00:00:00.000Z'
}
}
},
json: {
foo: 'bar'
}
} {
message: {
data: 'eyJmb28iOiJiYXIifQ==',
attributes: {}
},
json: {
foo: 'bar'
}
} Funciones de cola de tareas
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 en 1st como 2nd gen, necesitamos llamar a la función con una propiedad data en el body.
curl -X POST 'http://localhost:5001/my-project/us-central1/taskQueueFunction' \
-H 'Content-Type: application/json' \
-d '{"data": {"foo": "bar"}}'
Lo cual registrará:
{
data: {
foo: 'bar'
}
}
Esto llama directamente a la cola de tareas. Si te interesa invocar el emulador de Cloud Tasks, consulta nuestra guía del emulador de Cloud Tasks.
Funciones programadas
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 })
}) Al igual que las funciones Pub/Sub, estas se sufijan con -0 por alguna razón (aún tengo que averiguar en qué condiciones vemos un sufijo -1).
Las funciones programadas no reciben ningún payload, solo se activan.
curl -X POST 'http://127.0.0.1:5001/my-project/us-central1/scheduledFunction-0' \
-H 'Content-Type: application/json' \
-d '{}'
Esto registrará:
{
event: {
scheduleTime: '2026-04-20T00:00:00.000Z',
}
} {
context: {
// Esto se reenvía desde el body pero no estoy seguro de qué representa en producción
params: {
}
}
} Otros triggers
Estoy omitiendo intencionalmente los triggers de Auth, Firestore, Realtime Database, Cloud Storage y otros, debido a su estrecho acoplamiento con el servicio subyacente.
Es mejor probarlos realizando la acción real que los activa.
Si tienes un caso de uso para activar manualmente algunos de estos, ¡tengo curiosidad por saberlo! Házmelo saber en val@evetools.app.
Cómo Flame puede ayudar ✨
Todo esto funciona, pero ¿y si localhost:4000 tuviera una interfaz de functions que maneja todos los detalles subyacentes resaltados en esta guía?
Por eso estamos construyendo Flame, una interfaz de usuario para el emulador de Firebase con todo lo que le falta a localhost:4000.
Ve todas tus funciones y llámalas con un solo clic:
Ajusta el body, copia como cURL con toda la codificación manejada, y llámala tantas veces como quieras desde la interfaz.
No más lucha con la terminal, no más memorizar patrones de URL, no más codificación Base64 manual. Flame maneja funciones HTTP, callable, Pub/Sub, colas de tareas y funciones programadas, todo desde la misma interfaz.
También viene con una interfaz mejorada para Firebase Auth, Firestore, y más.
Si eso te parece interesante, ¡prueba Flame!