# fcm-django: push notifications through Firebase Cloud Messaging in Django

> Fcm-django is a Django application that turns Firebase Cloud Messaging into a model, a queryset API and a set of REST framework viewsets, so a project can register devices and send notifications without touching the Firebase SDK directly. It supports the HTTP v1 API, prunes devices that stop accepting messages, and reports per-device failures in a batch response.

**xtrinch/fcm-django** — FCM Django: Send push notifications via django to websites, iOS & android mobile devices through FCM (Firebase Cloud Messaging)

- Repository: https://github.com/xtrinch/fcm-django
- Website: http://fcm-django.readthedocs.io/
- Stars: 798 · Forks: 198
- Language: Python
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/xtrinch-fcm-django

## What the app wraps

Fcm-django is a Django app for Firebase Cloud Messaging, described as a unified platform for sending push notifications to mobile devices and browsers, covering Android, iOS, Chrome, Firefox and others.

The core value is that it turns the Firebase SDK into Django idioms. Instead of keeping your own table of device tokens, calling the messaging API yourself and handling the error responses, you get a model, a queryset with sending methods on it, migrations, admin pages and REST framework viewsets. The device registration a client performs becomes a row you can query, and sending becomes a method call on that queryset.

The package targets the current FCM HTTP v1 API. The README is explicit about the boundary: if you want the legacy API, use fcm-django below version 1. That is a useful line in the sand for anyone upgrading, because the legacy and current APIs differ in their authentication model and in the shape of the messaging calls, and a project that has not migrated should know which side of the line it is on.

There is also a migration guide for the pre-1.0 releases, for projects coming from the old pyfcm-based versions, in the documentation under docs/pages. The project is MIT licensed, the default branch is master, and the last push was on 2026-10-16 after releases 3.2.0, 3.1.0 and 3.0.0 in April 2026.

## The device model and how dead tokens are pruned

The FCMDevice model is the centre of the app, and its fields are small on purpose.

The registration_id is required and holds the FCM token. The name is optional. The active flag defaults to true, and it is the field the pruning logic flips. The user and device_id fields are both optional, with device_id described as usable to uniquely identify a device, and the type field carries one of android, web or ios.

The pruning behaviour is the feature that saves the most operational work. Devices to which notifications fail to send are marked as inactive, and devices marked as inactive are not sent notifications. In other words, a token that Firebase rejects stops generating failed sends, and the failure is visible in your database rather than in a log line.

The behaviour is configurable in three ways, and the settings block in the README shows them. DELETE_INACTIVE_DEVICES controls whether devices that cannot be sent to are deleted on receiving an error response from FCM, rather than just deactivated. EMIT_DEVICE_DEACTIVATED_SIGNAL turns on a device_deactivated signal when the library deactivates a device, which is how you would hook your own logging or analytics into the event. And ONE_DEVICE_PER_USER, which the README notes defaults to False, restricts each registered user to a single active device at a time, which is the right setting when a user signing in on a new phone should stop notifications going to the old one.

The rest of the app's functionality list is standard Django integration: all necessary migrations, model admins for the Django admin, admin actions for testing single and bulk notification sends, and Django REST framework viewsets for registering devices from a client.

## Installing it and wiring up the settings

The package is on PyPI and installs with pip:

```bash
pip install fcm-django
```

Then it is added to the installed applications and the Firebase app is initialised. The README's settings snippet shows both, with the Firebase credentials taken from the environment variable GOOGLE_APPLICATION_CREDENTIALS, which points at a JSON file, and the note that the initialisation call accepts credentials, options and name as further arguments.

The FCM_DJANGO_SETTINGS dictionary is where the app's own behaviour is configured. The first part:

```python
FCM_DJANGO_SETTINGS = {
     # an instance of firebase_admin.App to be used as default for all fcm-django requests
     # default: None (the default Firebase app)
    "DEFAULT_FIREBASE_APP": None,
     # default: _('FCM Django')
    "APP_VERBOSE_NAME": "[string for AppConfig's verbose_name]",
```

And the second part, which is where the pruning and per-user behaviour lives:

```python
     # true if you want to have only one active device per registered user at a time
     # default: False
    "ONE_DEVICE_PER_USER": True/False,
     # devices to which notifications cannot be sent,
     # are deleted upon receiving error response from FCM
     # default: False
    "DELETE_INACTIVE_DEVICES": True/False,
     # emit the ``device_deactivated`` signal when this library deactivates devices
     # default: False
    "EMIT_DEVICE_DEACTIVATED_SIGNAL": True/False,
}
```

The DEFAULT_FIREBASE_APP key is what lets a multi-tenant project send with different Firebase projects, using a different admin app per request rather than the default one. Once the app is in INSTALLED_APPS, native Django migrations are used, and manage.py migrate installs and migrates the models.

## Notification messages and data messages

The message API is Firebase's own, and fcm-django passes it through rather than wrapping it in a second abstraction, which is why the docs point at the Firebase message concept documentation.

There are two kinds of message. A notification carries a title, a body and optionally an image, and is displayed by the platform when the app is in the background:

```python
from firebase_admin.messaging import Message, Notification
Message(
    notification=Notification(title="title", body="text", image="url"),
    topic="Optional topic parameter: Whatever you want",
)
```

A data message carries a dictionary of key and value pairs and is delivered to the app, which then decides what to do with it:

```python
from firebase_admin.messaging import Message
Message(
    data={
        "Nick" : "Mario",
        "body" : "great match!",
        "Room" : "PortugalVSDenmark"
       },
       topic="Optional topic parameter: Whatever you want",
)
```

You can send either one or both, and the README notes that the Android, iOS and web configuration can be customised along with additional FCM conditions, with the details in the Firebase Message reference.

One dependency detail is worth knowing: the asynchronous queryset send APIs require firebase-admin 6.9 or newer, because they use firebase_admin.messaging.send_each_async directly. The package metadata pins firebase-admin at 6.9 or newer and below 8, and Django at 4.2 or newer, with Python 3.10 as the floor and support declared for Python 3.10 through 3.14 and Django 4.2, 5.1 and 5.2.

## Sending to one device or to a queryset

The sending API is a method on the model and on any queryset derived from it, and the README is careful to note that you can keep using filter and the other queryset methods.

For a single device:

```python
from firebase_admin.messaging import Message
from fcm_django.models import FCMDevice

device = FCMDevice.objects.all().first()
device.send_message(Message(data={...}))
device.send_message(Message(data={...}), dry_run=True)
```

For a batch, the same method on a queryset, or on the manager itself:

```python
devices = FCMDevice.objects.all()
devices.send_message(Message(data={...}))
devices.send_message(Message(data={...}), dry_run=True)
FCMDevice.objects.send_message(Message(...))
```

The send_message parameters are message, dry_run and app, and the app parameter is what pairs with the DEFAULT_FIREBASE_APP setting for a multi-project setup.

The dry_run flag is the one to remember in development. It validates the payload with Firebase without actually delivering a notification, which is what you want when you are checking that a message is constructed correctly without pushing it to a colleague's phone at midnight.

Error handling is the other thing to get right. Sending raises all the errors that firebase-admin raises, so your application code has to catch them. FirebaseError covers a failure while sending to the FCM service, and ValueError covers invalid input arguments. The README links to the Firebase BatchResponse reference for the details of the per-device results.

## Reading the failures in a batch response

Bulk sending is where the interesting behaviour lives, and the README explains it carefully.

The send_message call returns a FirebaseResponseDict, a wrapper around Firebase's BatchResponse. The point of the wrapper is that Firebase may return per-device failures in the response rather than raising an exception for the whole call, so a batch where two of fifty tokens are stale does not blow up your request handler.

The fields on the returned object are the ones you will actually use:

```python
response = FCMDevice.objects.send_message(Message(...))

if response.has_failures:
    print(response.failure_count)
    print(response.failed_registration_ids)
    print(response.failed_exceptions)
```

Alongside those, the object exposes success_count, failure_count, has_failures, all_failed, failed_registration_ids, failed_exceptions and a summary. The distinction between failure_count and all_failed is worth noting, since a batch can have failures without every message failing, and the reverse tells you something different about the state of your token list.

The README notes that this is especially useful for configuration-related failures, and gives the examples of APNS and credentials problems, which is where a batch of otherwise valid messages fails for reasons that have nothing to do with the individual devices.

## Where fcm-django is the wrong tool

The first limitation is that it is an FCM integration and nothing else. If your notifications go to iOS through APNs directly, or to a platform with its own push service, this package does not help, and the Firebase dependency in the metadata is a cost you would be paying for nothing.

The second is the scope of the wrapper. It gives you a device table, a way to send, and a failure report. It does not give you topic management, subscription handling, delivery receipts, analytics or scheduling, all of which remain Firebase's own concerns, and the README is candid that the message construction is Firebase's API that you pass through.

The third is the pruning heuristic. A device is deactivated when a send to it fails, which is right for a stale token and wrong for a transient error, and the setting that deletes inactive devices makes that permanent. On a project where a failed send can be caused by a temporary network condition, you would want to look at the failure reasons rather than let the library prune, and the response object gives you the exceptions to do that.

The fourth is the version floor. Django 4.2 and Python 3.10 are the minimums, and firebase-admin is capped below 8, so a project on an older Django or on a newer firebase-admin than the constraint allows is out of scope. The pre-1.0 pyfcm lineage also means that a project upgrading across the major versions has more than one migration to read, one from pyfcm and one to the v1 API.

## Maintenance, packaging and the licence

The releases tell a short story about where the project is. Version 3.0.0 and 3.1.0 both landed on 2026-04-01, and 3.2.0 followed on 2026-04-07, which looks like a deliberate reset of the major line with a follow-up patch. The last push was on 2026-10-16, so the repository is being worked on even if the last tagged release is a few months old.

The packaging is plain setuptools, with a pyproject.toml that requires setuptools 71.1.0 or newer and the wheel package, a dynamic version read from the fcm_django package attribute, and three listed packages: fcm_django, fcm_django.api and fcm_django.migrations. The requirements file additionally lists django-tastypie and djangorestframework, which is what the API layer is built on, alongside Django, firebase-admin and swapper, the library that allows the model to be swapped out for a custom one.

The project is classified as Production/Stable in its metadata, and the tooling around it is a conventional Python setup: tox.ini for the test matrix, a pre-commit configuration, separate requirements files for development and tests, a readthedocs configuration for the documentation hosted at fcm-django.readthedocs.io, and an AGENTS.md for assistant tooling.

The licence is MIT, with a LICENSE.txt in the repository, which places no conditions on commercial use or on modification. That is a description of the terms rather than legal advice, and the provider terms for Firebase Cloud Messaging are a separate agreement entirely.

## Conclusion

Adopt fcm-django if your Django project already uses Firebase Cloud Messaging and you want device registration, delivery and failure handling modelled as a Django model instead of hand-rolled tables and SDK calls. Do not adopt it if you send through APNs directly, since it is an FCM-only integration, and do not expect it to manage topics or delivery guarantees, which remain Firebase's job. Verify first that your firebase-admin version is at least 6.9, since the asynchronous queryset APIs depend on send_each_async being present, that your Django version is 4.2 or newer, and that your error handling catches FirebaseError, because a failing send raises rather than returning quietly.

## FAQ

### How do I install fcm-django in a Django project?

Run pip install fcm-django, add fcm_django to INSTALLED_APPS, initialise the Firebase admin app in your settings, and then run manage.py migrate to create the FCMDevice model and the rest of the migrations.

### What fields does the FCMDevice model hold?

A required registration_id holding the FCM token, an optional name, an active flag that defaults to true, optional user and device_id fields, and a type field of android, web or ios.

### What happens to a device whose notifications fail to send?

It is marked as inactive and inactive devices are not sent further notifications. You can also have inactive devices deleted on receiving an error response from FCM with DELETE_INACTIVE_DEVICES, and receive a device_deactivated signal with EMIT_DEVICE_DEACTIVATED_SIGNAL.

### How do I test a notification without sending it to a device?

Pass dry_run=True to send_message. The payload is validated with Firebase without the notification being delivered, which is what the README recommends during development and integration testing.

### How do I find out which devices in a batch failed?

The send_message call returns a FirebaseResponseDict wrapping Firebase's BatchResponse, with success_count, failure_count, has_failures, all_failed, failed_registration_ids, failed_exceptions and a summary. Firebase returns per-device failures in the batch response rather than raising for the whole call.

## Sources

- [License: MIT](https://github.com/xtrinch/fcm-django/blob/master/LICENSE)
- [Project website](http://fcm-django.readthedocs.io/)
- [README](https://github.com/xtrinch/fcm-django/blob/master/README.md)
- [Releases](https://github.com/xtrinch/fcm-django/releases)
- [xtrinch/fcm-django on GitHub](https://github.com/xtrinch/fcm-django)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/xtrinch-fcm-django
