Skip to main content
Real-time presence lets you display all users currently viewing a CoreShowPage and automatically restrict form editing to the first user in the queue. It uses Laravel Reverb and presence channels under the hood.

Enabling Presence

Call ->enablePresence() on CoreShowPage in your controller. The presence channel is derived automatically from the domain object URL — no frontend configuration needed.
return CoreShowPage::make('orders/order-show')
    ->fromDomainObject($order)
    ->enablePresence()
    ->with([
        'order' => fn () => OrderData::from($order),
    ]);
On the frontend, CoreShowPage picks up the enablePresence flag from pageData and activates the usePresence hook automatically.

Form Editing Control

Pass presenceState to CoreForm to restrict editing to the first user in the queue. All other users see a disabled form.
import CoreShowPage from '@core/components/core-show-page';
import CoreForm from '@core/components/core-form/core-form';
import { useForm } from '@inertiajs/react';

export default function OrderEdit({ order }: Props) {
  const form = useForm({ name: order.name });

  return (
    <CoreShowPage enablePresence={true}>
      <CoreForm
        form={form}
        onSubmit={() => form.post(route('orders.update', order))}
        presenceState={presenceState}
        disabledMessage={__('Another user is editing this. You are next in queue.')}
      >
        {/* form fields */}
      </CoreForm>
    </CoreShowPage>
  );
}

Using the Hook Directly

If you need presence outside of CoreShowPage, use usePresence directly:
import { usePresence } from '@core/hooks/use-presence';
import { canEditForm, getEditingUser } from '@core/lib/presence';

const presenceState = usePresence({ url: '/orders/123', enabled: true });
const canEdit = canEditForm(presenceState);
const editingUser = getEditingUser(presenceState);

Helper Functions

FunctionReturns
canEditForm(state)true if the current user has edit control
getEditingUser(state)The user currently holding edit control
getAllViewers(state)All users currently viewing
getUserQueuePosition(state, userId)The current user’s position in the queue

How It Works

  1. When a page loads with presence enabled, usePresence normalizes the domain object URL (e.g. /orders/123orders.123) and joins the presence channel core-page.{normalizedUrl} via Laravel Echo.
  2. The channel broadcasts each user’s ID, name, email, avatar, and join timestamp.
  3. The first user to join holds edit control. When they leave, the next user in the queue takes over.
  4. Avatars are displayed in the AppBar — the editing user has a primary ring, viewers have a standard ring.

Troubleshooting

  • Verify Reverb is running: php artisan reverb:start
  • Check environment variables (REVERB_APP_ID, REVERB_APP_KEY, etc.). REVERB_APP_KEY must not contain a dot (.) or colon (:) — those characters break the WebSocket URL and prevent the server connection.
  • Look for WebSocket errors in the browser console & network tab
  • Ensure broadcasting routes are registered in bootstrap/app.php
  • Look for /broadcasting/auth calls in the browser network tab with 200 response-
  • Look for an authorized socket call to App.Model.User in the browser network tab and make sure your authorized to it.
  • Ensure the user is authenticated
  • Verify presenceState is passed to CoreForm
  • Check presenceState.users contains the expected users
  • Confirm canEditForm() returns the correct value