Zurück zum Blog

Flame Blog

So lösen Sie Firebase Functions manuell im Emulator aus

Eine vollständige Anleitung zum Aufrufen von HTTP-, Callable-, Pub/Sub-, Cloud Tasks- und geplanten Funktionen im Firebase-Emulator.

Veröffentlicht am 24. April 2026

Von Val — Ursprünglich auf Englisch verfasst

Der Firebase-Emulator kann Ihre Firebase Functions ausführen, ob HTTP, Callable, Pub/Sub, Task-Queues oder geplante Funktionen.

Es funktioniert out of the box, wenn Sie sie programmatisch aus Ihrer App aufrufen (außer für geplante Funktionen, die lokal nie ausgeführt werden), aber wenn man an einer Funktion arbeitet, ist es normalerweise einfacher, sie direkt aufzurufen, besonders für diejenigen, die durch nicht-triviale Aktionen in Ihrer App ausgelöst werden.

Die Emulator-Oberfläche auf localhost:4000 hat ein einfaches Admin-Panel für Firebase Auth, Firestore und Cloud Storage, aber nicht für Functions. Nur dieses traurige Panel mit Logs:

Firebase Emulator UI Funktionspanel, das nur Logs anzeigt

In dieser Anleitung zeigen wir, wie Sie manuell auslösen:

Funktionen mit cURL aufrufen

Um dies zu lösen, ist cURL unser Freund.

Mit dem Functions-Emulator, der auf dem Standardport 5001 läuft, sieht Ihr typischer Aufruf so aus:

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

Wobei region z. B. us-central1 (Standard) ist und function Ihr Funktionsname.

Wenn also Ihr Projekt my-project ist und Ihre Funktion wie folgt definiert ist:

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 })
})

Ist ihre URL:

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

Und mit einer benutzerdefinierten Region:

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 })
  })

Bekommen wir:

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

Nun wird der JSON-Payload je nach Funktionsart unterschiedlich sein, und in manchen Fällen, je nachdem, ob Sie 1st- oder 2nd-Gen-Funktionen verwenden.

HTTP-Funktionen

Das ist der einfachste Fall, da Sie den Payload vollständig kontrollieren, also übergeben Sie Ihren Payload einfach direkt im HTTP-Body und Sie erhalten ihn in der Funktion.

Beispielfunktion:

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

Wir können sie so aufrufen:

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

Es wird loggen:

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

Wir können sie auch mit einem benutzerdefinierten Pfad aufrufen:

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

Was loggt:

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

Das ist für 1st- und 2nd-Gen-Funktionen identisch.

Callable-Funktionen

Sie werden in 1st und 2nd Gen leicht unterschiedlich definiert, aber gleich aufgerufen.

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 }
})

Unauthentifizierter Aufruf

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

Wird loggen:

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

Authentifizierter Aufruf mit ID-Token

Wir können auch einen Authorization: Bearer-Header übergeben. Er nimmt ein Firebase Auth ID-Token, das automatisch vom Functions-SDK geparst und validiert wird.

Hier ist unser cURL-Aufruf mit diesem 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"}}'

Was loggt:

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

Authentifizierter Aufruf mit beliebigem Token

Wenn Sie aus irgendeinem Grund Ihr eigenes Nicht-Firebase-Token verwenden möchten, funktioniert das auch:

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'
  }
}

Pub/Sub-Funktionen

Hier behandeln wir das direkte Aufrufen von Pub/Sub-Funktionen mit einem Payload, der kompatibel ist mit dem, was der Pub/Sub-Emulator sonst senden würde.

Wenn Sie manuell Nachrichten an den Pub/Sub-Emulator veröffentlichen möchten, lesen Sie unsere detaillierte Anleitung zum Aufrufen des Pub/Sub-Emulators.

export const pubsubFunction = functions.pubsub.onMessagePublished(
  { topic: 'my-topic' },
  event => {
    // `event.data.message.json` ist eine berechnete Eigenschaft, daher müssen wir sie separat loggen
    console.log({ event, json: event.data.message.json })
  }
)
export const pubsubFunction = functions.pubsub
  .topic('my-topic')
  .onPublish(message => {
    // `message.json` ist eine berechnete Eigenschaft, daher müssen wir sie separat loggen
    console.log({ message, json: message.json })
  })

Aus irgendeinem Grund werden Pub/Sub-Funktionen in der Emulator-URL mit -0 suffixiert, also wird die URL in diesem Fall:

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

Wir können sie aufrufen mit:

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": {}}}'

Die Base64-Nutzlast in data oben ist das gleiche {"foo": "bar"}-Beispiel von vorhin:

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

Der Bequemlichkeit halber können wir die Base64-Kodierung im selben Befehl einbetten:

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": {}}}'

Die Funktion wird loggen:

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

Task-Queue-Funktionen

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 })
  })

Sowohl bei 1st als auch 2nd Gen müssen wir die Funktion mit einer data-Eigenschaft im Body aufrufen.

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

Was loggt:

{
  data: {
    foo: 'bar'
  }
}

Das ruft die Task-Queue direkt auf. Wenn Sie am Aufrufen des Cloud Tasks-Emulators interessiert sind, lesen Sie unsere Cloud Tasks-Emulator-Anleitung.

Geplante Funktionen

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 })
  })

Wie Pub/Sub-Funktionen werden diese aus irgendeinem Grund mit -0 suffixiert (ich muss noch herausfinden, unter welchen Bedingungen wir ein -1-Suffix sehen).

Geplante Funktionen nehmen keinen Payload entgegen, sie werden einfach ausgelöst.

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

Das wird loggen:

{
  event: {
    scheduleTime: '2026-04-20T00:00:00.000Z',
  }
}
{
  context: {
    // Dies wird aus dem Body weitergeleitet, aber ich bin mir nicht sicher, was das in der Produktion darstellt
    params: {
    }
  }
}

Andere Trigger

Ich überspringe absichtlich Auth-, Firestore-, Realtime Database-, Cloud Storage- und andere Trigger, wegen ihrer engen Kopplung mit dem zugrunde liegenden Service.

Sie testen diese am besten, indem Sie die tatsächliche Aktion ausführen, die sie auslöst.

Wenn Sie einen Anwendungsfall für das manuelle Auslösen einiger davon haben, würde ich das gerne wissen! Lassen Sie es mich wissen unter val@evetools.app.

Wie Flame helfen kann ✨

All das funktioniert, aber was wäre, wenn localhost:4000 eine Functions-Oberfläche hätte, die alle zugrunde liegenden Details bewältigt, die in dieser Anleitung hervorgehoben wurden?

Deshalb bauen wir Flame, eine Firebase-Emulator-Benutzeroberfläche mit allem, was auf localhost:4000 fehlt.

Sehen Sie alle Ihre Funktionen und rufen Sie sie mit einem einzigen Klick auf:

Funktionsliste in Flame

Verändern Sie den Body, kopieren Sie als cURL mit der gesamten Kodierung erledigt, und rufen Sie ihn so oft Sie wollen von der Oberfläche aus auf.

Funktions-Input-Bearbeitung in Flame

Kein Terminal-Gerangel, kein Auswendiglernen von URL-Mustern, keine manuelle Base64-Kodierung. Flame verwaltet HTTP-Funktionen, Callable-Funktionen, Pub/Sub-Funktionen, Task-Queues und geplante Funktionen, alles von derselben Oberfläche aus.

Kommt auch mit einer verbesserten Benutzeroberfläche für Firebase Auth, Firestore und mehr.

Wenn das für Sie spannend klingt, probieren Sie Flame aus!