Signals
Django signals are a notification system that lets certain senders tell a set of receiver functions that an action has taken place — for example, running code automatically every time a model instance is saved or deleted — so you can decouple side effects from the code that triggers them.
Learn Signals in our free Django course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Django course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll meet the built-in model signals, learn how to connect receivers with the @receiver decorator, build the classic "auto-create a profile when a user is created" pattern, and register everything correctly so it actually fires.
What You'll Learn in This Lesson
1 Built-in Model Signals
Every Django model fires signals at key moments in its lifecycle. The most important ones live in django.db.models.signals and fire around saving and deleting a row:
- pre_save — sent just before an instance is written to the database.
- post_save — sent right after the instance is saved (you get a created flag).
- pre_delete — sent just before an instance is deleted.
- post_delete — sent right after the instance is deleted.
A receiver function always accepts the same keyword arguments. Django passes sender (the model class), instance (the actual object), and for save signals a created boolean. You should always end the signature with **kwargs so future arguments don't break your code.
The example below builds a tiny signal dispatcher in plain Python so you can watch post_save fire without a database. It mirrors how Django calls each connected receiver.
2 Connecting Receivers with @receiver
A receiver is just a function. To make it listen for a signal you connect it. The cleanest way is the @receiver decorator, which takes the signal and a sender to filter on:
You can also connect receivers manually with .connect() — useful when the receiver is defined elsewhere or connected conditionally:
The example below recreates that "auto-create a profile" flow with the same mini dispatcher, so you can see the created guard doing its job.
3 Registering Signals & Custom Signals
Receivers only register when their module is imported and the @receiver decorators actually run. The documented place to make that happen is the ready() method of your app's config in apps.py :
Beyond the built-ins, you can define your own custom signals for events that matter to your app. Create one with Signal() and fire it with .send() :
The runnable example wires up a custom signal end to end — define it, connect a receiver, and send it.
🎯 Your Turn: Connect the Receiver
Fill in the blank so the receiver listens for the signal that fires right after a row is saved. The function should only print on creation.
Common Errors (And How to Fix Them)
The receiver module is never imported, so the @receiver decorators never run and nothing is connected.
✅ Fix: import your signals module from the ready() method of your app's AppConfig in apps.py .
❌ A duplicate profile is created on every update
The receiver runs on every save because the created flag isn't being checked.
✅ Fix: wrap the side effect in if created: so it only runs on the first save.
❌ RecursionError: maximum recursion depth exceeded
A post_save receiver calls .save() on the same instance, which fires post_save again, looping forever.
✅ Fix: update fields in a pre_save receiver instead, or use queryset.update() which does not re-fire the signal.
📋 Quick Reference
Signal / Tool
When It Fires / What It Does
pre_save
Just before an instance is written to the database
post_save
Right after a save; provides the created flag
pre_delete
Just before an instance is deleted
post_delete
Right after an instance is deleted
@receiver
Decorator that connects a function to a signal and sender
🏆 Mini Challenge: Audit Log Receiver
Build a small audit log. Connect receivers to both a save and a delete signal, then fire each one and watch the log fill up.
❓ Frequently Asked Questions
Lesson 21 complete — you can wire up signals!
You now know the built-in model signals, the sender/instance/created arguments, how to connect receivers with @receiver and .connect() , where to register them in ready() , and how to define your own custom signals.
🚀 Up next: Middleware — learn how to hook into every request and response as it passes through Django.
Practice quiz
What are Django signals used for?
- Running code automatically when an event like a save occurs
- Defining URL routes
- Validating forms
- Creating database indexes
Answer: Running code automatically when an event like a save occurs. Signals let receivers run code automatically when senders report an action, e.g. a save.
When does the pre_save signal fire?
- After the row is written
- Just before an instance is written to the database
- Only on delete
- When the server starts
Answer: Just before an instance is written to the database. pre_save fires just before the instance is saved; on a first save it has no primary key yet.
Which signal provides a 'created' boolean argument?
- pre_save
- pre_delete
- post_save
- post_delete
Answer: post_save. post_save fires after the save and passes created=True/False to the receiver.
Which decorator connects a function as a signal receiver?
- @signal
- @connect
- @listener
- @receiver
Answer: @receiver. The @receiver(signal, sender=...) decorator registers a function as a receiver.
Why should a receiver function end its signature with **kwargs?
- To rename arguments
- To stay future-proof against new signal arguments
- To make it run faster
- To skip the sender
Answer: To stay future-proof against new signal arguments. Ending with **kwargs keeps the receiver working if Django adds new keyword arguments.
In the auto-create-profile pattern, why guard with 'if created'?
- So a profile is created only on the first save, not every update
- To speed up queries
- To avoid importing models
- To delete old profiles
Answer: So a profile is created only on the first save, not every update. Without the created guard, the receiver would try to create a profile on every save.
Where is the documented place to import your signals module so receivers register?
- In settings.py
- In urls.py
- In the AppConfig.ready() method in apps.py
- In manage.py
Answer: In the AppConfig.ready() method in apps.py. Importing the signals module from AppConfig.ready() reliably registers the receivers.
Which method fires a signal to all connected receivers?
- signal.fire()
- signal.emit()
- signal.trigger()
- signal.send()
Answer: signal.send(). You dispatch a signal with signal.send(sender=..., **kwargs).
What argument does Django pass as the actual saved object?
- instance
- sender
- model
- obj
Answer: instance. instance is the actual object; sender is the model class.
How can you connect a receiver without the @receiver decorator?
- signal.add(func)
- signal.connect(func, sender=...)
- signal.register(func)
- signal.bind(func)
Answer: signal.connect(func, sender=...). signal.connect(receiver, sender=...) connects a receiver manually.
Continue this course
- Previous: Serializers & ViewSets
- Next: Middleware