Bojan Josifoski < wp developer />

The WordPress Notification System That Shouldn’t Work (But Does)

November 9, 2025 • Bojan

Most people think WordPress’s post system is only for blog posts or pages.
But I used it to build a real-time in-app notification system – and it works better than you’d expect.

Here’s how.


The Core Idea: Use Posts as Notifications

Instead of creating a new database table, I registered a hidden custom post type called sf_notification.

register_post_type('sf_notification', array(
    'public' => false,
    'show_ui' => false,
    'show_in_menu' => false,
));

Every notification is just a “post.”
This means:

It’s a little unorthodox, but it gives you a free data layer that scales across hundreds of sites.


Making It Work Like a Real System

1. Idempotency: No Duplicate Notifications

A common problem with async actions is duplicate notifications – especially under load.
The solution: an idempotency hash that expires after 5 minutes.

$idempotency_key = md5($type . '_' . $user_id . '_' . serialize($meta) . '_' . $triggered_by);
$last = get_user_meta($user_id, '_sf_last_notification_' . $idempotency_key, true);

if ($last && (time() - intval($last)) < 300) {
    return false; // Skip duplicate
}

This makes the system safe to call multiple times without spamming the user.


2. Role-Based Delivery Rules

Not every event should go to every user.
The system checks roles and context dynamically – and ignores self-triggered events.

Example:

It’s fully extensible and easy to map to WordPress roles or custom logic.


3. Performance: Batch Loading Meta

Storing notifications as posts could be slow if you’re not careful.
So it batch-loads all meta in one go to prevent N+1 queries.

if (!empty($notifications)) {
    $ids = wp_list_pluck($notifications, 'ID');
    $meta_keys = ['_notification_type', '_notification_priority', '_notification_status', '_notification_created'];
    
    foreach ($meta_keys as $key) {
        foreach ($ids as $id) {
            $meta[$key][$id] = get_post_meta($id, $key, true);
        }
    }
}

Everything is pre-fetched, so the frontend can render instantly.


4. Frontend: Alpine.js + AJAX

The UI is powered by Alpine.js, making it reactive without needing a full SPA framework.
Notifications are fetched via AJAX and update live without a refresh.

Simple, lightweight, and no need for extra dependencies.


5. Built-In Multisite Support

The best part?
Because notifications are stored as posts, each site in a Multisite automatically has its own notifications table (wp_X_posts).
No extra schema, no cross-site pollution, no migration headaches.

It just works.


Why It Shouldn’t Work – But Does

This is one of those WordPress hacks that feels like it shouldn’t scale, yet it does.

It works because:

It’s not just a hack.
It’s a repurposed content system turned into a distributed notification engine.


Why I Love This Pattern

This is WordPress doing what it was never meant to do – and doing it surprisingly well.


Lessons from This Build

  1. Don’t fight WordPress – repurpose it.
  2. Idempotency is critical for real-time systems.
  3. Caching and batching make unconventional models viable.
  4. Multisite is a hidden scaling advantage if used right.
  5. You don’t need new infrastructure for every feature – sometimes the old tools are enough.

The Takeaway

Everyone talks about “headless WordPress,” but sometimes the smartest move is using WordPress itself as your backend framework.

A hidden post type, a few smart checks, and some front-end reactivity – and you’ve got a modern notification system running on the world’s most battle-tested CMS.

About the Author

About the Author

I’m Bojan Josifoski - I’m a WordPress systems engineer who developed and maintained a proprietary WordPress-based framework used by U.S. financial institutions between 2016 and 2025.

← Back to Blog