Phoenix Presence with Phoenix LiveView

presence liveview

A lot of apps have some sort of notification on if users are online or not. Phoenix makes it easy to build that with the built-in Phoenix Presence. In this tutorial, I will combine the presence feature with Phoenix LiveView.

The realtime nature of LiveView makes it easy to display the current online users.

STEP 1 - Add a Presence Module

First I need to define a presence module and make sure it's started in the application.

# lib/tutorial/presence.ex

defmodule Tutorial.Presence do
  use Phoenix.Presence, otp_app: :tutorial, pubsub_server: Tutorial.PubSub

Add it in the start function:

# lib/tutorial/application.ex

def start(_type, _args) do
  children = [
    ...other processes
    # Start Presence process

STEP 2 - Add Presence to LiveView

The idea is that when a visitor visits a page, I need to add that visit in a specific channel. In this case, I have a route that points to a specific LiveView so I can add the logic in the mount function.

I have already set up so the currentuser is assigned in the user session in a plug. That means, I can get it as session["currentuser"]. In this tutorial, I use the user id as the unique identifier in the presence channel.

So, in the LiveView that is responsible for showing the online users, I need to add:

# lib/tutorial_web/live/page_live.ex

defmodule TutorialWeb.PageLive do
  use TutorialWeb, :live_view

  alias Tutorial.Presence
  alias Tutorial.PubSub

  @presence "tutorial:presence"

  @impl true
  def mount(_params, session, socket) do
    if connected?(socket) do
      user = session["current_user"]

      {:ok, _} = Presence.track(self(), @presence, user[:id], %{
        name: user[:name],
        joined_at: :os.system_time(:seconds)

      Phoenix.PubSub.subscribe(PubSub, @presence)

      |> assign(:current_user, session["current_user"])
      |> assign(:users, %{})
      |> handle_joins(Presence.list(@presence))

  @impl true
  def handle_info(%Phoenix.Socket.Broadcast{event: "presence_diff", payload: diff}, socket) do
      |> handle_leaves(diff.leaves)
      |> handle_joins(diff.joins)

  defp handle_joins(socket, joins) do
    Enum.reduce(joins, socket, fn {user, %{metas: [meta| _]}}, socket ->
      assign(socket, :users, Map.put(socket.assigns.users, user, meta))

  defp handle_leaves(socket, leaves) do
    Enum.reduce(leaves, socket, fn {user, _}, socket ->
      assign(socket, :users, Map.delete(socket.assigns.users, user))

Note that I use phoenix pubsub to notify all other PageLive-processes that are spawned for other page visitors.

I want to display the online users in a LiveView component called OnlineUsers. I mount the component in the template like:

# lib/tutorial_web/live/page_live.html.leex

<%= live_component @socket, TutorialWeb.Components.OnlineUsers, current_user: @current_user, users: @users %>

Note that I assign both current_user and all users.

In the component, I don't need to set up anything specific. Just enough to render the template.

# lib/tutorial_web/live/components/online_users.ex

defmodule TutorialWeb.Components.OnlineUsers do
  use TutorialWeb, :live_component

  def update(assigns, socket) do
      |> assign(assigns)

And lastly, in the template, rendering the online users is pretty straightforward.

  <h2>Users online:</h2>
  <%= for {user_id, user} <- @users do %>
    <%= if user_id == @current_user[:id] do %>
      <span class="text-xs bg-green-200 rounded px-2 py-1"><%= user[:name] %> (me)</span>
    <% else %>
      <span class="text-xs bg-blue-200 rounded px-2 py-1"><%= user[:name] %></span>
    <% end %>
  <% end %>

Related Tutorials

Published 25 Nov - 2022
Updated 29 Nov - 2022
Phoenix 1.7

Sortable lists with Phoenix LiveView and SortableJS

A very common user interface pattern on the web is to have sortable elements. In this tutorial I will go through how to accomplish sortable lists wi..

Published 31 May - 2023
Phoenix 1.7

CSV Import file upload with preview in LiveView

In this tutorial, I will go through how to upload and import a CSV file with Phoenix LiveView and, show how easy it is to preview the imports before..