How to Use the NOTIFY Command in PostgreSQL

How to Use the NOTIFY Command in PostgreSQL

If you’ve ever built an application that needs to react instantly when something changes in your database — a new order comes in, a chat message gets sent, a job finishes processing — you’ve probably reached for polling. You know the drill: hit the database every few seconds, check if anything’s new, repeat forever. It works, but it’s wasteful, laggy, and honestly kind of embarrassing once you learn there’s a better way.

That better way, at least inside PostgreSQL, is the NOTIFY command. I want to walk you through exactly what it does, how to use it correctly, and where it fits into a real application, because once you understand it, you’ll probably find a dozen places in your own projects where it can replace clunky polling logic.

What Is NOTIFY in PostgreSQL?

NOTIFY is a PostgreSQL command that sends a lightweight notification, along with an optional payload of text, to a named channel. Any other database session that has issued a LISTEN command on that same channel will receive the notification, almost instantly, without needing to ask for it.

Think of it like a radio broadcast. NOTIFY is the transmitter, the channel name is the frequency, and any client tuned in with LISTEN picks up the signal. This is part of PostgreSQL’s built-in asynchronous messaging system, and it’s been around for a long time — it’s stable, well-tested, and doesn’t require any extensions or plugins to use.

The key thing to understand is that NOTIFY doesn’t deliver data directly to a table or a queue that persists. It’s a fire-and-forget signal. If nobody is listening when you send it, the notification is simply gone. That’s an important distinction I’ll come back to later when I talk about use cases and limitations.

Why NOTIFY Exists

PostgreSQL’s developers built NOTIFY and LISTEN to solve a very specific problem: how do you let one database connection tell other connections that something interesting just happened, without forcing everyone to constantly query the database to check?

Before this feature existed (and still, in databases that lack it), developers used workarounds like:

NOTIFY cuts out all of that overhead for a large class of problems. It’s especially popular in combination with LISTEN for building real-time features: live dashboards, chat applications, cache invalidation systems, and job queue workers.

Basic Syntax of NOTIFY

The syntax for NOTIFY is refreshingly simple:

NOTIFY channel_name;

or, if you want to send a payload with your notification:

NOTIFY channel_name, 'payload text here';

There’s also a function form, pg_notify(), which behaves the same way but is easier to use inside procedural code, like triggers or PL/pgSQL functions:

SELECT pg_notify('channel_name', 'payload text here');

I actually prefer pg_notify() in most real-world code because it’s a function, not a bare SQL statement, which means you can call it dynamically with variables far more easily inside triggers and stored procedures.

Parameters Explained

Let’s break down what each part actually means:

channel_name This is an identifier, not a string literal, when you use the plain NOTIFY syntax. It follows the same naming rules as other PostgreSQL identifiers — letters, digits, underscores, and it can’t start with a digit. If you use pg_notify(), the channel name is passed as a string literal instead, which gives you more flexibility, including the ability to build channel names dynamically.

payload This is an optional string, limited to 8000 bytes in most PostgreSQL versions. It’s plain text — PostgreSQL doesn’t parse or validate it in any special way, so you’re free to send anything: a JSON string, a row ID, a status code, whatever your application needs. Many developers send small JSON payloads here so listening clients can decide what to do without querying the database again.

If you don’t specify a payload, it defaults to an empty string.

A Practical Example: Basic NOTIFY and LISTEN

Let’s see this in action. Open two separate psql sessions (or two connections from your application) to really understand how this works.

Session 1 — the listener:

LISTEN order_updates;

Session 2 — the notifier:

NOTIFY order_updates, 'Order #10432 has shipped';

Switch back to Session 1, and if you’re using psql, you’ll immediately see something like:

Asynchronous notification "order_updates" with payload "Order #10432 has shipped" received from server process with PID 24187.

That’s it. No polling, no delay worth mentioning. The listening session was told, in real time, that something happened.

Using NOTIFY Inside a Trigger

The real power of NOTIFY shows up when you combine it with triggers. Instead of manually calling NOTIFY every time you insert a row, you let PostgreSQL do it automatically whenever a table changes.

Here’s a working example for an orders table:

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_name TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ DEFAULT now()
);

CREATE OR REPLACE FUNCTION notify_new_order()
RETURNS TRIGGER AS $$
BEGIN
    PERFORM pg_notify(
        'new_order_channel',
        json_build_object(
            'id', NEW.id,
            'customer_name', NEW.customer_name,
            'status', NEW.status
        )::text
    );
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_notify_new_order
AFTER INSERT ON orders
FOR EACH ROW
EXECUTE FUNCTION notify_new_order();

Now, any time a new row is inserted into orders, a JSON payload describing that row gets broadcast to new_order_channel. Any application listening on that channel — say, a Node.js backend using the pg library, or a Python service using psycopg2 — receives that payload instantly and can act on it, whether that’s pushing a WebSocket update to a browser or kicking off a background job.

Common Use Cases for NOTIFY

Over the years, developers have found a handful of scenarios where NOTIFY really shines:

  1. Cache invalidation — When a row changes, notify a cache layer so it knows to refresh or evict stale entries, instead of relying on a fixed TTL.
  2. Real-time dashboards — Send notifications when metrics or statuses change so a dashboard can update live instead of refreshing on a timer.
  3. Job queue signaling — Rather than having worker processes poll a jobs table every second, have them LISTEN on a channel, and NOTIFY them the moment a new job is inserted. They still typically re-check the table (since payloads aren’t guaranteed to be delivered to a worker that wasn’t listening), but the responsiveness improves dramatically.
  4. Chat and messaging apps — A classic use case. Insert a message, trigger a notification, and connected clients get it immediately.
  5. Multi-service coordination — When you have several services connected to the same PostgreSQL database and need a lightweight way for one to signal another without introducing a separate message broker like RabbitMQ or Kafka.

Limitations You Need to Know About

I don’t want to oversell NOTIFY, because it’s not a replacement for a proper message queue in every situation. Here’s what you need to keep in mind:

Notifications aren’t persisted. If a listener isn’t connected and actively listening at the moment NOTIFY fires, that notification is lost forever. There’s no replay, no history, nothing to catch up on. If your application absolutely cannot afford to miss an event, you need a durable queue (or at minimum, a fallback polling mechanism as a safety net).

Payload size is limited. The 8000-byte cap on payloads (this can vary slightly by PostgreSQL build, but it’s a hard limit) means you can’t stuff large amounts of data into a notification. Send an ID or small JSON object, not entire documents.

Notifications are delivered only within the same database. If you have multiple databases on the same PostgreSQL server, a NOTIFY in one won’t reach a LISTEN in another.

Delivery happens at transaction commit, not immediately at the NOTIFY call. If you call NOTIFY inside a transaction and then roll back, the notification never gets sent. If the transaction commits, PostgreSQL delivers it right after. This is actually a really useful property — it means listeners never get notified about changes that ultimately got rolled back — but it can surprise people who expect instant delivery inside a long transaction.

Duplicate notifications within a transaction are collapsed. If you call NOTIFY channel, 'payload' multiple times with the exact same channel and payload inside a single transaction, PostgreSQL only sends it once. If the payloads differ, each is sent separately.

Troubleshooting Common NOTIFY Issues

“My listener isn’t receiving anything.” The most common cause is that the listening connection wasn’t actually listening at the time the notification was sent, or it’s a completely separate database connection pool where each query grabs a random connection from the pool (common with ORMs). LISTEN is tied to a specific database session — if your connection pool hands out a different connection for every query, your LISTEN won’t stick. You need a dedicated, long-lived connection for listening.

“I sent NOTIFY but nothing happened until much later.” Check whether you’re inside a long-running transaction. Remember, delivery is deferred until commit. If you’re debugging in psql and forgot you’re inside a BEGIN block, that’s often the culprit.

“My application driver isn’t surfacing notifications.” Different client libraries handle this differently. In psycopg2 for Python, you need to poll the connection object for notifications after executing queries, or use a dedicated polling loop with select(). In node-postgres, you attach an event listener to the client’s notification event. Always check your specific driver’s documentation, since the low-level protocol detail of receiving NOTIFY messages is not automatically surfaced everywhere.

“Payload got truncated.” You’ve likely exceeded the byte limit. Switch to sending a lightweight reference — like a row ID — and have the listener query the database for full details rather than cramming everything into the payload.

Best Practices for Using NOTIFY

After using this feature across several production systems, here’s what I’ve settled on as good practice:

Wrapping Up

NOTIFY is one of those PostgreSQL features that feels almost too simple for how useful it is. It won’t replace a dedicated message broker for high-volume, mission-critical event streaming, but for a huge range of everyday problems — live updates, cache invalidation, lightweight job signaling — it does the job with zero extra infrastructure. Pair it with LISTEN, understand its delivery guarantees (or lack thereof), and it can genuinely simplify your architecture instead of adding another moving part to maintain.

If you’re building anything that currently polls the database on a timer, it’s worth asking yourself: could NOTIFY do this instead? More often than you’d expect, the answer is yes.

Exit mobile version