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.

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:

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.

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.

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.

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.

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.

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.

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.