Retour au blog

Flame Blog

Comment déclencher manuellement des Firebase Functions dans l'émulateur

Un guide complet pour appeler des fonctions HTTP, callable, Pub/Sub, Cloud Tasks et planifiées dans l'émulateur Firebase.

Publié le 24 avril 2026

Par Val — Article original rédigé en anglais

L’émulateur Firebase est capable d’exécuter vos Firebase Functions, qu’il s’agisse de fonctions HTTP, callable, Pub/Sub, files de tâches ou planifiées.

Ça fonctionne out of the box quand vous les appelez programmatiquement depuis votre app (sauf pour les fonctions planifiées qui ne tournent jamais localement), mais quand on travaille sur une fonction, il est généralement plus facile de l’appeler directement, surtout pour celles qui sont déclenchées par des actions non triviales dans votre app.

L’interface de l’émulateur sur localhost:4000 dispose d’un panneau admin basique pour Firebase Auth, Firestore et Cloud Storage, mais pas pour Functions. Juste ce triste panneau avec des logs uniquement :

Panneau des fonctions de l'interface Firebase Emulator ne montrant que les logs

Dans ce guide, nous verrons comment déclencher manuellement :

Appeler des fonctions avec cURL

Pour résoudre ça, cURL est notre ami.

Avec l’émulateur Functions tournant sur le port par défaut 5001, votre appel typique ressemblera à ceci :

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

region est par exemple us-central1 (défaut), et function est le nom de votre fonction.

Donc si votre projet est my-project et que votre fonction est définie comme :

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

Son URL sera :

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

Et avec une région personnalisée :

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

On obtient :

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

Maintenant le payload JSON va différer selon le type de fonction, et dans certains cas, selon que vous utilisez des fonctions 1st gen ou 2nd gen.

Fonctions HTTP

C’est le plus simple car vous contrôlez entièrement le payload, donc passez simplement votre payload directement dans le body HTTP et vous le récupérerez dans la fonction.

Exemple de fonction :

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

On peut l’appeler comme ceci :

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

Ça va logger :

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

On peut aussi l’appeler avec un chemin personnalisé :

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

Ce qui va logger :

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

C’est identique pour les fonctions 1st et 2nd gen.

Fonctions callable

Elles sont définies légèrement différemment en 1st et 2nd gen, mais elles s’appellent de la même manière.

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

Appel non authentifié

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

Va logger :

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

Appel authentifié avec un ID token

On peut aussi passer un header Authorization: Bearer. Il prend un Firebase Auth ID token qui sera automatiquement parsé et validé par le SDK functions.

Voici notre appel cURL avec ce 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"}}'

Ce qui va logger :

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

Appel authentifié avec un token arbitraire

Si vous voulez utiliser votre propre token non-Firebase pour une raison quelconque, cela fonctionne aussi :

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

Fonctions Pub/Sub

Ici nous couvrirons l’appel direct des fonctions Pub/Sub, avec un payload compatible avec ce que l’émulateur Pub/Sub enverrait autrement.

Si vous voulez publier manuellement des messages vers l’émulateur Pub/Sub, consultez notre guide dédié pour appeler l’émulateur Pub/Sub.

export const pubsubFunction = functions.pubsub.onMessagePublished(
  { topic: 'my-topic' },
  event => {
    // `event.data.message.json` est une propriété calculée donc on doit la logger séparément
    console.log({ event, json: event.data.message.json })
  }
)
export const pubsubFunction = functions.pubsub
  .topic('my-topic')
  .onPublish(message => {
    // `message.json` est une propriété calculée donc on doit la logger séparément
    console.log({ message, json: message.json })
  })

Pour une raison quelconque dans l’URL de l’émulateur, les fonctions Pub/Sub sont suffixées avec -0, donc dans ce cas l’URL sera :

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

On peut l’appeler avec :

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

Le payload Base64 dans data ci-dessus est le même exemple {"foo": "bar"} d’avant :

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

Pour plus de commodité, on peut intégrer l’encodage Base64 dans la même commande :

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 fonction va logger :

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

Fonctions de file de tâches

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

En 1st et 2nd gen, nous devons appeler la fonction avec une propriété data dans le body.

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

Ce qui va logger :

{
  data: {
    foo: 'bar'
  }
}

Cela appelle directement la file de tâches. Si vous êtes intéressés par l’invocation de l’émulateur Cloud Tasks, consultez notre guide de l’émulateur Cloud Tasks.

Fonctions planifiées

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

Comme les fonctions Pub/Sub, celles-ci sont suffixées avec -0 pour une raison quelconque (je n’ai toujours pas trouvé dans quelles conditions on voit un suffixe -1).

Les fonctions planifiées ne prennent aucun payload, elles sont simplement déclenchées.

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

Ça va logger :

{
  event: {
    scheduleTime: '2026-04-20T00:00:00.000Z',
  }
}
{
  context: {
    // Cela est forward depuis le body mais je ne sais pas ce que ça représente en production
    params: {
    }
  }
}

Autres triggers

Je saute intentionnellement les triggers Auth, Firestore, Realtime Database, Cloud Storage et autres, à cause de leur couplage étroit au service sous-jacent.

Vous feriez mieux de les tester en effectuant l’action réelle qui les déclenche.

Si vous avez un use case pour déclencher manuellement certains d’entre eux, je suis curieux de le connaître ! Faites-moi savoir à val@evetools.app.

Comment Flame peut aider ✨

Tout cela fonctionne, mais et si localhost:4000 avait une interface functions qui gère tous les détails sous-jacents mis en évidence dans ce guide ?

C’est pourquoi nous construisons Flame, une interface utilisateur pour l’émulateur Firebase avec tout ce qui manque à localhost:4000.

Voyez toutes vos fonctions et appelez-les en un seul clic :

Liste des fonctions dans Flame

Modifiez le body, copiez en cURL avec tout l’encodage géré, et appelez autant de fois que vous voulez depuis l’interface.

Édition de l'input des fonctions dans Flame

Plus de lutte avec le terminal, plus de mémorisation de patterns d’URL, plus d’encodage Base64 manuel. Flame gère les fonctions HTTP, callable, Pub/Sub, les files de tâches et les fonctions planifiées, tout depuis la même interface.

Vient également avec une interface améliorée pour Firebase Auth, Firestore, et plus encore.

Si cela vous semble intéressant, essayez Flame !