PyFCM: sending Firebase Cloud Messaging from Python
Python client for FCM - Firebase Cloud Messaging (Android, iOS and Web)
At a glance
- What is it?
- PyFCM is a Python client for Firebase Cloud Messaging that covers notification, data and topic messages for Android, iOS and Web clients, with several ways to supply credentials. The judgement that matters is version 2.0.1 from June 2024, which is the release that moved the library to the FCM HTTP v1 API.
- Who is it for?
- PyFCM suits a Python backend that already uses service account credentials and wants to send push notifications without hand building FCM HTTP v1 request bodies. It is the wrong choice if you are on GCM, since the legacy protocol has been superseded and the library has been on HTTP v1 since version 2.0.0, and it is the wrong choice for a team that wants the raw REST API, because the wrapper is what removes the fiddly parts.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 66 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A client library for a push service, not a push service
PyFCM is a Python client for FCM, Firebase Cloud Messaging, covering Android, iOS and Web clients.
That description matters because FCM itself is Google's infrastructure, and PyFCM is the piece that lets a Python application talk to it. The library's own framing of FCM is useful context: FCM is the new version of GCM, it inherits the reliable and scalable GCM infrastructure plus new features, and GCM users are strongly recommended to upgrade to FCM.
The use cases the documentation names are ordinary product cases. You can notify a client app that new email or other data is available to sync, send notifications to drive user reengagement and retention, or transfer a payload of up to 4KB for instant messaging style use cases.
The feature list is short, which is the point of a wrapper: all FCM functionality covered, and Tornado support. The second one deserves a mention because it says something about how the library is built. Tornado is an asynchronous Python web framework, and supporting it means the library can be used from an async request handler without blocking the event loop on the network round trip to Google, which matters if you are sending a notification in the middle of handling a request.
The primary language is Python, the licence is MIT, and the package is on PyPI with the project page hosted separately. The dependency list is correspondingly light: requests, urllib3, google-auth and aiohttp, with pytest and pytest-mock for the tests.
Three ways to supply credentials
Authenticating to FCM is the part that goes wrong most often, and PyFCM gives you three documented routes, which is the main practical reason to use the wrapper rather than raw HTTP.
The first is a service account file, which is the simplest:
from pyfcm import FCMNotification
fcm = FCMNotification(service_account_file="<service-account-json-path>", project_id="<project-id>")The documentation points you at the Firebase console service accounts page, the adminsdk path, to get that file.
The second is to skip the file entirely and pass Google oauth2 credentials directly. The documentation says Google oauth2 credentials, such as ADC or impersonate credentials, can be used instead of a service account file:
fcm = FCMNotification(
service_account_file=None, credentials=your_credentials, project_id="<project-id>"
)That route matters on managed platforms, where the credential comes from the environment rather than from a JSON file on disk, and it is the reason the package depends on google-auth with a floor of version 2.22.0.
The third is an environment variable, which is what you want in a container. The documented pattern reads the JSON from GCP_CREDENTIALS, with a tip for loading it quickly from a file:
from google.oauth2 import service_account
gcp_json_credentials_dict = json.loads(os.getenv('GCP_CREDENTIALS', None))
credentials = service_account.Credentials.from_service_account_info(gcp_json_credentials_dict, scopes=['https://www.googleapis.com/auth/firebase.messaging'])
fcm = FCMNotification(service_account_file=None, credentials=credentials, project_id="<project-id>")There is also a proxy option, since a server behind a corporate proxy cannot reach the FCM endpoint directly, and the constructor takes a proxy_dict with http and https entries.
Whichever route you pick, note that the scope is fixed at the Firebase messaging scope, and that a service account key on disk is a credential with real sending rights.
Notification, data, and the rules the payload has to obey
The documentation is precise about the three kinds of message, and the distinction matters when you are debugging why a message arrived but did nothing.
A notification message is what the client displays, sometimes called a display message. A data message is handled by the client app and carries only custom key value pairs. And a message can be a notification with an optional data payload, in which case FCM handles displaying the notification and the client app handles the data.
Data messages let developers send up to 4KB of custom key value pairs, and the client app is responsible for processing them.
The payload rules are where a wrapper earns its keep:
data_payload = {
"foo": "bar",
"body": "great match!",
"room": "PortugalVSDenmark"
}
result = fcm.notify(fcm_token=fcm_token, notification_body=notification_body, data_payload=data_payload)Only string keys and values are accepted. Booleans and nested dicts are not supported. If you need a nested structure, the documentation's advice is to serialise it yourself with json.dumps and put the resulting string in the payload, and it links the FCM message reference for the exact format.
That is a real constraint rather than a limitation of the library, since it mirrors what FCM itself accepts, but it is the kind of thing that produces a rejected request rather than a delivered message, and knowing it in advance saves an afternoon.
The single-device call is the simplest form, taking a token, a title, a body and an image URL, and it is the same call whether or not you attach a data payload.
Topics and their conditional expressions
Sending to one device is the easy case. Sending to a topic, which is a named group devices subscribe to, is where the FCM semantics get interesting, and PyFCM exposes both forms.
result = fcm.notify(topic_name="news", notification_body=message)
# Conditional topic messaging
topic_condition = "'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
result = fcm.notify(notification_body=message, topic_condition=topic_condition)The conditional form is FCM's own expression language, and the documentation explains how it evaluates: FCM first evaluates any conditions in parentheses, and then evaluates the expression from left to right. In the example, a user subscribed to any single topic does not receive the message, and likewise a user who does not subscribe to TopicA does not receive it. The combinations that do receive it are TopicA and TopicB, and TopicA and TopicC.
Conditions for topics support two operators per expression, and parentheses are supported.
The reason to read that evaluation order carefully is that the operators are not the ones programmers expect from a programming language. These are set membership tests combined with logical operators, evaluated in a specific order, and getting the parentheses wrong changes which devices receive the message rather than raising an error. There is no failure mode here, only a different audience.
For a news or announcement broadcast, the topic call is one line. For anything conditional, the expression is a string you build in your code and the effect is on delivery, so it deserves a test with a mock subscriber set before it goes near real devices.
Platform options are plain dictionaries
Beyond the core message, PyFCM exposes the platform specific configuration as optional dictionary arguments, which is the mechanism that covers Android, iOS and Web from one call.
android_config takes Android specific options for messages, apns_config takes Apple Push Notification Service specific options, webpush_config takes Webpush protocol options, and fcm_options carries platform independent options for features provided by the FCM SDKs. Each has a reference link in the documentation.
The design choice here is worth naming. Rather than modelling each platform's options as typed arguments, the library passes dictionaries straight through to the FCM request body. That is what makes the wrapper keep up with a fast moving API: a new FCM option can be set without a new release of PyFCM, because it is already a dictionary.
The cost is that mistakes surface at the API rather than in your code. A misspelled key inside android_config is not caught by PyFCM, it is either ignored by FCM or rejected by it, and the error message will be about a message rather than about your call. If you use these options, treat them as configuration you own and validate them against the linked reference.
There is one more environment specific note in the documentation, aimed at a specific platform. App Engine users should define their environment, and the constructor takes an env argument with the value app_engine for that case.
HTTP v1 was the breaking change, and it landed in 2.0.0
The updates section of the README is short, and it is the most important thing on the page for anyone starting today.
The first entry is the migration to FCM HTTP v1, dated June 2024, with a link to the 2.0.0 release and a note crediting the pull request that did the work. The second is the major updates of August 2017, which is the 1.4.0 release.
That gives the whole version history a shape. The library was substantially reworked once, for the 1.4.0 line in 2017, and then again for the HTTP v1 change in 2024, and between those two points the releases were mostly fixes, such as 1.4.8 in November 2020 whose title is Fix timeout errors.
The version list makes the current state plain: 2.0.1 from 2024-06-18, 1.4.8 from 2020-11-28 and 1.4.0 from 2017-08-16. The last push to the repository was on 2026-08-18, which is inside the window where the project can be described as receiving work, but there has been no release since June 2024.
So the practical reading is that the API surface you are calling is stable and the code is being worked on, but the distribution you can install is two years old. For a library whose job is to send a message and get out of the way, that is a reasonable place to be. It is also a reason to pin the version and read the changelog, CHANGES.rst, before upgrading.
The packaging is conventional for the era: setup.py with the version read from a metadata module, a publish mode that tags, builds and uploads with twine, and a release-please configuration in the repository that automates version bumps and changelog entries.
Editorial conclusion
PyFCM suits a Python backend that already uses service account credentials and wants to send push notifications without hand building FCM HTTP v1 request bodies. It is the wrong choice if you are on GCM, since the legacy protocol has been superseded and the library has been on HTTP v1 since version 2.0.0, and it is the wrong choice for a team that wants the raw REST API, because the wrapper is what removes the fiddly parts. Verify three things before you build on it: that you have moved your service account to the FCM HTTP v1 era setup the 2.0.0 release describes, that the platform specific options you need exist in the extra argument set, and that your payload stays inside the documented limits, since the library will not flatten nested structures for you.
Frequently asked questions
What is PyFCM?
It is a Python client for FCM, Firebase Cloud Messaging, supporting Android, iOS and Web clients. The project states that all FCM functionality is covered, with Tornado support for asynchronous applications, and it is MIT licensed and published on PyPI.
How do I install PyFCM?
With pip install pyfcm from PyPI, or pip install git+https://github.com/olucurious/PyFCM.git to work from the repository. The runtime dependencies are requests, urllib3, google-auth and aiohttp.
How do I authenticate to FCM with PyFCM?
Pass a service account file with service_account_file and a project_id, or pass Google oauth2 credentials directly with credentials, which the documentation says can be ADC or impersonate credentials, or load the service account JSON from the GCP_CREDENTIALS environment variable and build credentials from it. A proxy_dict argument is available for servers behind a proxy.
What payload rules does PyFCM enforce for data messages?
Data messages carry up to 4KB of custom key value pairs, and only string keys and values are accepted, so booleans and nested dicts are not supported. To send a nested structure you serialise it yourself with json.dumps and put the resulting string in the payload.
How do I send to a topic, including conditional messaging?
Use topic_name for a simple broadcast, or topic_condition for conditional messaging. The documentation explains that FCM evaluates conditions in parentheses first and then evaluates the expression from left to right, with two operators supported per expression and parentheses allowed.
What changed in PyFCM 2.0.0 and what is the current version?
Version 2.0.0, released in June 2024, was the migration to the FCM HTTP v1 API, which the README lists under breaking changes with a link to the Firebase migration documentation. The most recent release is 2.0.1 from 2024-06-18, and the last push to the repository was on 2026-08-18.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/olucurious-pyfcm)Was this useful?