IA Engineering

Supabase Realtime : Broadcast, Presence et Postgres Changes

Diffuser les changements de la base, envoyer des messages entre clients et afficher qui est en ligne avec Supabase Realtime. Quand préférer Broadcast à Postgres Changes, code vérifié et limites par plan au 30/09/2026.

Formes tubulaires entrelacées dans les tons bleu et violet, entourées de lignes orbitales, de points lumineux et de petits fragments géométriques.

L’essentiel en 30 secondes

  • Realtime offre trois fonctions : Broadcast (messages entre clients ou émis par la base), Presence (qui est connecté) et Postgres Changes (écoute d'une table).
  • Supabase recommande Broadcast déclenché par trigger pour diffuser les changements de la base ; Postgres Changes reste plus simple mais vérifie la RLS pour chaque abonné.
  • Avec Postgres Changes, la RLS ne s'applique pas aux DELETE, et l'ancien enregistrement n'est envoyé qu'avec replica identity full.
  • Les limites dépendent du plan : 200 connexions simultanées en Free, 500 en Pro, 10 000 sans plafond de dépenses.

Supabase Realtime pousse des messages aux clients connectés par WebSocket, avec trois fonctions : Broadcast (messages à faible latence entre clients, ou émis par la base), Presence (qui est connecté) et Postgres Changes (écoute des INSERT, UPDATE et DELETE d'une table)1. Pour diffuser les changements de la base, Supabase recommande aujourd'hui Broadcast, déclenché par un trigger, pour des raisons d'échelle et de sécurité ; Postgres Changes reste la voie la plus simple pour une application modeste2.

Pour la vue d'ensemble de Supabase, voir notre tour d'horizon de la plateforme Supabase. Pour situer Realtime parmi les autres composants de la plateforme, voir l'architecture de Supabase.

Broadcast ou Postgres Changes : lequel choisir ?

—Postgres ChangesBroadcast depuis la base
Mise en placeAjouter la table à une publicationTrigger + policies RLS sur realtime.messages + canal privé
Contrôle d'accèsRLS de la table vérifiée pour chaque abonné (sauf DELETE)Policies sur le canal (topic)
Montée en chargeUne vérification par abonné et par changement, traitement sur un seul filChaque changement envoyé une fois puis diffusé
Quand l'utiliserPrototype, faible nombre d'abonnésAu-delà d'environ 3 000 abonnés simultanés aux mêmes changements, et par défaut selon Supabase

Le seuil d'environ 3 000 abonnés et le fonctionnement sur un seul fil (qui rend inutile une instance plus puissante pour ce cas) viennent de la documentation Supabase3.

Comment écouter les changements d'une table avec Postgres Changes ?

Ajoutez la table à la publication supabase_realtime, en SQL ou dans Database > Publications du tableau de bord3 :

alter publication supabase_realtime add table public.messages;
const channel = supabase
  .channel('messages-conversation-123')
  .on(
    'postgres_changes',
    {
      event: 'INSERT', // 'INSERT' | 'UPDATE' | 'DELETE' | '*'
      schema: 'public',
      table: 'messages',
      filter: `conversation_id=eq.${conversationId}`,
    },
    (payload) => addMessage(payload.new)
  )
  .subscribe()

// Quand l'écran se ferme
await supabase.removeChannel(channel)

Trois comportements à connaître3 :

  • par défaut, seul le nouvel enregistrement est envoyé ; pour recevoir l'ancien lors d'un UPDATE ou d'un DELETE, passez la table en replica identity full ;
  • les événements DELETE ne peuvent être filtrés que si la table est en replica identity full ;
  • les policies RLS ne s'appliquent pas aux DELETE, puisque Postgres ne peut plus vérifier l'accès à une ligne supprimée.
alter table public.messages replica identity full;

Comment diffuser les changements avec Broadcast ?

C'est la méthode recommandée par Supabase. Un trigger appelle realtime.broadcast_changes(), qui publie sur un canal privé ; seuls les utilisateurs autorisés par une policy sur realtime.messages reçoivent les messages2.

-- 1. Autoriser la réception (à restreindre selon votre modèle d'accès)
create policy "authenticated peut recevoir les broadcasts"
on realtime.messages for select to authenticated
using ( true );

-- 2. Fonction appelée par le trigger
create or replace function public.messages_changes()
returns trigger
security definer
language plpgsql
as $$
begin
  perform realtime.broadcast_changes(
    'room:' || coalesce(NEW.room_id, OLD.room_id)::text, -- topic
    TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD
  );
  return null;
end;
$$;

-- 3. Trigger
create trigger handle_messages_changes
after insert or update or delete on public.messages
for each row execute function public.messages_changes();
await supabase.realtime.setAuth() // nécessaire pour les canaux privés

const channel = supabase
  .channel(`room:${roomId}`, { config: { private: true } })
  .on('broadcast', { event: 'INSERT' }, ({ payload }) => console.log(payload))
  .on('broadcast', { event: 'UPDATE' }, ({ payload }) => console.log(payload))
  .on('broadcast', { event: 'DELETE' }, ({ payload }) => console.log(payload))
  .subscribe()

Le schéma realtime est verrouillé : vous pouvez y créer des policies sur realtime.messages (la RLS y est déjà active), mais pas de tables ni de fonctions. N'ajoutez pas alter table realtime.messages enable row level security à une migration : l'instruction échoue et annule la transaction4. Pour écrire des policies plus fines que using (true), voir notre article sur les policies Row Level Security.

Comment envoyer des messages éphémères entre clients ?

Pour un indicateur « en train d'écrire » ou un curseur partagé, rien ne passe par la base. Par défaut, l'émetteur ne reçoit pas son propre message ; l'option broadcast: { self: true } change ce comportement5.

const channel = supabase.channel(`room:${roomId}:typing`)

channel
  .on('broadcast', { event: 'typing' }, ({ payload }) => setTypingUser(payload.username))
  .subscribe()

async function notifyTyping(username: string) {
  await channel.send({ type: 'broadcast', event: 'typing', payload: { username } })
}

Correction : l'ancien exemple utilisait une variable channel jamais déclarée.

Comment savoir qui est en ligne avec Presence ?

const channel = supabase.channel('online-users', {
  config: { presence: { key: userId } },
})

channel
  .on('presence', { event: 'sync' }, () => {
    const state = channel.presenceState()
    setOnlineCount(Object.keys(state).length)
  })
  .on('presence', { event: 'join' }, ({ key, newPresences }) => console.log('arrivée', key, newPresences))
  .on('presence', { event: 'leave' }, ({ key, leftPresences }) => console.log('départ', key, leftPresences))
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({ user_id: userId, online_at: new Date().toISOString() })
    }
  })

Presence synchronise l'état via le serveur et notifie tous les abonnés à chaque changement : n'appelez pas track() à chaque mouvement de souris, utilisez Broadcast pour cela6.

Quelles limites selon le plan ?

LimiteFreeProPro sans plafond de dépenses / Team
Connexions simultanées20050010 000
Messages par seconde1005002 500
Canaux par connexion100100100
Taille d'un message Broadcast256 Ko3 000 Ko3 000 Ko
Taille d'un message Postgres Changes1 024 Ko1 024 Ko1 024 Ko

Valeurs de la documentation Supabase au 30/09/2026. Une limite dépassée se traduit par des erreurs too_many_connections, too_many_channels, too_many_joins ou tenant_events dans le WebSocket et dans les logs Realtime7.

Quelles bonnes pratiques retenir ?

  • Fermer chaque canal au démontage du composant (removeChannel dans le retour du useEffect).
  • Filtrer côté serveur (filter, topics précis) plutôt que de trier côté client.
  • Écrire des policies RLS simples et indexées : avec Postgres Changes, elles sont évaluées pour chaque abonné.
  • Utiliser Presence pour l'état de connexion, Broadcast pour tout ce qui change vite.

Pour intégrer ces abonnements dans des hooks React, voir les hooks React pour Supabase ; pour un projet temps réel complet, notre offre de développement temps réel sur mesure.

Sources

  1. Realtime (documentation Supabase)
  2. Subscribing to Database Changes (documentation Supabase)
  3. Postgres Changes (documentation Supabase)
  4. Realtime Authorization (documentation Supabase)
  5. Broadcast (documentation Supabase)
  6. Presence (documentation Supabase)
  7. Realtime Limits (documentation Supabase)

Écrit par

CTO & Chief Digital Strategist chez AdSim, Liège

Georges est CTO et Chief Digital Strategist d’AdSim.

  • Campaign Manager Brand Controls Basics
  • Bid Manager Brand Controls Basics
  • AdWords Video Brand Controls Basics

Questions fréquentes

Vos questions sur Supabase Realtime

Où activer Realtime sur une table dans le tableau de bord ?

Dans Database > Publications : sous la publication supabase_realtime, activez les tables à écouter. L'équivalent SQL est alter publication supabase_realtime add table nom_de_table.

Realtime stocke-t-il les messages Broadcast dans ma base ?

Non. Pour vérifier l'autorisation, Realtime insère un message dans realtime.messages puis annule la transaction ; aucun message n'y est conservé.

Comment écouter une table située dans un autre schéma que public ?

Accordez le droit SELECT sur la table au rôle présent dans le jeton (par exemple authenticated) et activez la RLS avec des policies adaptées, sinon ce rôle aura un accès en lecture illimité à la table.

Commentaires

Chaque commentaire est relu avant publication, en général sous 24 h ouvrées. Les liens promotionnels ne sont pas publiés.

Aucun commentaire pour l’instant. Une question sur l’article ? Posez-la ci-dessous.

Laisser un commentaire

Jamais publié. Sert à vous prévenir d’une réponse.

Votre commentaire sera publié après relecture. Un lien au plus, pas de message promotionnel.

Point de départ

On applique cette méthode à votre compte ?

L’audit gratuit part de vos données, pas d’un exemple. Vous recevez le diagnostic sous 48 h ouvrées.

« Chez AdSim, c’est un vrai expert du digital qui lit votre demande et vous répond sous 48 h ouvrées. »

Valérie Matrige, CEO & co-fondatrice

Réponse sous 48 h ouvrées · Diagnostic 100 % gratuit · Sans engagement · Zéro revente de vos données