Slot Fills

Last Updated: October 2026

Slot fills let plugins inject React components into named extension points in the admin and player-facing UI. Each slot renders all registered fills in priority order (lower number = first).

How Slots Work

Host application renders:               Plugin registers a fill:

┌─────────────────────────┐             fills.register(
│  <MainLayout>           │               'layout.main.header.end',
│    <Header>             │               MyBadgeComponent,
│      ...                │               { priority: 10, providerType: 'my-plugin' }
│      <Slot              │             );
│        name="layout.main.header.end"
│      />                 │
│    </Header>            │             At render time:
│  </MainLayout>          │             <Slot> → MyBadgeComponent renders here
└─────────────────────────┘

Each fill is wrapped in a <PluginErrorBoundary>. A crash in a fill component is isolated — it will not crash the host application or other fills.

Registering a Fill

const { fills, React } = window.__pokerEngine;

function MyComponent(props) {
  // props contains whatever the slot passes through — varies per slot
  return React.createElement('div', { className: 'my-fill' }, 'Hello from plugin');
}

fills.register('slot-name', MyComponent, {
  priority: 10,           // Lower = renders earlier (default: 10)
  providerType: 'my-plugin',  // Required — used for cleanup on deactivation
});

All fills registered with your providerType are automatically removed when the plugin is deactivated.

Available Slots

layout.admin.sidebar.end

Location: Bottom of the admin panel sidebar, below all navigation items. Renders in: client/src/layouts/AdminLayout.tsx Props passed to fill: none

Use this slot to inject extra navigation items or widgets into the admin sidebar.

function AdminNavItem() {
  const { React } = window.__pokerEngine;
  return React.createElement(
    'a',
    { href: '/admin/plugins/my-plugin/settings', className: 'sidebar-link' },
    'My Plugin'
  );
}

fills.register('layout.admin.sidebar.end', AdminNavItem, {
  priority: 10,
  providerType: 'my-plugin',
});

Note: Pages registered with pages.register({ area: 'admin', nav: { ... } }) are automatically inserted as nav items via the AdminPluginNavItems core fill. You only need this slot for custom nav UI that differs from standard nav items.

layout.main.sidebar.end

Location: Bottom of the main player-facing sidebar, below all navigation items. Renders in: client/src/layouts/MainLayout.tsx Props passed to fill: none

Use this slot to inject extra navigation items into the player sidebar.

Note: Pages registered with pages.register({ area: 'user', nav: { ... } }) are automatically inserted via the UserPluginNavItems core fill.

layout.main.header.end

Location: Right side of the main player-facing header bar. Renders in: client/src/layouts/MainLayout.tsx Props passed to fill: none

Use this slot to inject badges, status indicators, or action buttons into the player header.

function HeaderBadge() {
  const { React } = window.__pokerEngine;
  return React.createElement(
    'span',
    { style: { background: '#7c3aed', color: '#fff', padding: '2px 8px', borderRadius: 4 } },
    'Beta'
  );
}

fills.register('layout.main.header.end', HeaderBadge, {
  priority: 10,
  providerType: 'my-plugin',
});

auth.login.below-form

Location: Below the login form on the player login page. Renders in: client/src/pages/auth/LoginPage.tsx Props passed to fill: none

Use this slot to inject additional login options, notices, or promotional content below the standard login form.

function SSOLoginButton() {
  const { React } = window.__pokerEngine;
  return React.createElement(
    'button',
    { onClick: () => redirectToSSO(), className: 'btn-sso' },
    'Log in with My Platform'
  );
}

fills.register('auth.login.below-form', SSOLoginButton, {
  priority: 10,
  providerType: 'my-plugin',
});

auth.register.below-form

Location: Below the registration form on the player registration page. Renders in: client/src/pages/auth/RegisterPage.tsx Props passed to fill: none

Use this slot to inject additional content below the standard registration form (e.g., terms links, promotional content, third-party registration options).

cashier.deposit.method-list

Location: Inside the cashier deposit tab, in the payment method selection area. Renders in: client/src/pages/cashier/CashierPage.tsx Props passed to fill: { selectedMethod, amount }

Use this slot to render custom deposit method UI. selectedMethod is the active deposit method ID and amount is the current deposit amount input. selectedMethod holds the method id from GET /api/v1/cashier/payment-methods. Every fill renders regardless of the selection, so return null unless it is your method’s ID. Typically used alongside registry.registerProvider() on the server side to add a custom payment provider.

function MyDepositMethod({ selectedMethod }) {
  const { React } = window.__pokerEngine;
  if (selectedMethod !== MY_METHOD_ID) return null;
  return React.createElement(
    'div',
    { className: 'payment-method' },
    React.createElement('img', { src: '/plugins/my-plugin/logo.png', alt: 'My Provider' }),
    React.createElement('span', null, 'Pay with My Provider')
  );
}

fills.register('cashier.deposit.method-list', MyDepositMethod, {
  priority: 20,
  providerType: 'my-plugin',
});

cashier.withdraw.method-list

Location: Inside the cashier withdrawal tab, in the payment method selection area. Renders in: client/src/pages/cashier/CashierPage.tsx Props passed to fill: { selectedMethod, onDestinationChange, onMetadataChange }

Mirrors cashier.deposit.method-list but for withdrawal methods. selectedMethod is the active withdrawal method ID. Call onDestinationChange(destination) when your UI collects the payout destination/account. Call onMetadataChange(metadata) with provider-specific withdrawal metadata that should be submitted with the request.

profile.edit.after

Location: Below the edit profile form on the player Profile page (Edit Profile tab). Renders in: client/src/pages/profile/ProfilePage.tsx Props passed to fill: none

Use this slot to inject additional settings or integrations below the profile edit form. Fills render inside the edit tab’s content area, after the save button.

function WalletConnect() {
  const { React } = window.__pokerEngine;
  return React.createElement(
    'div',
    { style: { marginTop: '16px', padding: '16px', background: '#1f2937', borderRadius: '8px' } },
    'Connect your external wallet here'
  );
}

fills.register('profile.edit.after', WalletConnect, {
  priority: 10,
  providerType: 'my-plugin',
});

admin.users.row.flags

Location: Inside the flags column of each user row in the Admin → Users management table (both desktop and mobile views). Renders in: client/src/pages/admin/UsersManagementPage.tsx Props passed to fill: { user } — the AdminUserData object for the row being rendered, containing id, username, email, isBot, isAgent, etc.

Use this slot to add flag badges for plugin-specific user attributes (e.g., linked wallets, external integrations). The fill should render a small inline badge consistent with the existing flag style (rounded pill, 12px icon, text-xs).

function MyPluginFlag({ user }) {
  const { React } = window.__pokerEngine;
  const [linked, setLinked] = React.useState(false);

  React.useEffect(() => {
    fetch(`/api/v1/my-plugin/status?userId=${user.id}`, { credentials: 'include' })
      .then(r => r.json())
      .then(d => { if (d?.data?.linked) setLinked(true); });
  }, [user.id]);

  if (!linked) return null;
  return React.createElement(
    'span',
    {
      className: 'inline-flex items-center gap-0.5 rounded-full px-2 py-0.5 text-xs font-medium',
      style: { background: 'rgba(100, 200, 100, 0.1)', color: '#4ade80' },
      title: 'My Plugin connected',
    },
    'Connected'
  );
}

fills.register('admin.users.row.flags', MyPluginFlag, {
  priority: 10,
  providerType: 'my-plugin',
});

admin.user-detail.integrations

Location: Inside the Activity section of the Admin -> Users detail drawer. Renders in: client/src/components/admin/UserDetailDrawer.tsx Props passed to fill: { user } — the selected AdminUserData object.

Use this slot to show compact integration-specific details for one user after an admin opens the detail drawer. Prefer this slot over admin.users.row.flags when your fill needs a network request or detailed account metadata.

lobby.top

Location: Above the table list on the Lobby page. Renders in: client/src/pages/lobby/LobbyPage.tsx Props passed to fill: none

Use this slot to add banners, announcements, or promotions.

plugin.settings.before-form

Location: Above the settings form on the Admin Panel plugin settings page. Renders in: client/src/pages/admin/PluginSettingsPage.tsx Props passed to fill: none

The slot renders on every plugin’s settings page. To target your own plugin only, use the plugin.settings.after-form.{providerType} variant below.

plugin.settings.after-form

Location: Below the settings form on the Admin Panel plugin settings page. Renders in: client/src/pages/admin/PluginSettingsPage.tsx Props passed to fill: none

Renders on every plugin’s settings page. A per-plugin variant, plugin.settings.after-form.{providerType}, renders directly after it on that plugin’s settings page only (for example plugin.settings.after-form.my-plugin).

Slots Summary

Slot NameLocationProps
layout.admin.sidebar.endAdmin sidebar — bottomnone
layout.main.sidebar.endPlayer sidebar — bottomnone
layout.main.header.endPlayer header — right sidenone
auth.login.below-formLogin page — below formnone
auth.register.below-formRegistration page — below formnone
cashier.deposit.method-listCashier — deposit methods area{ selectedMethod, amount }
cashier.withdraw.method-listCashier — withdrawal methods area{ selectedMethod, onDestinationChange, onMetadataChange }
profile.edit.afterProfile — below edit formnone
admin.users.row.flagsAdmin users table — flags column{ user }
admin.user-detail.integrationsAdmin users detail drawer — activity section{ user }
lobby.topLobby — above table listnone
plugin.settings.before-formPlugin settings — above formnone
plugin.settings.after-formPlugin settings — below formnone
plugin.settings.after-form.{providerType}One plugin’s settings — below formnone

Implementation Notes

  • Multiple fills in the same slot render as sibling elements in DOM order of priority.
  • If no fills are registered for a slot, the <Slot> renders null — no empty wrapper element.
  • The SlotFillRegistry is reactive; fills registered after initial render appear immediately.
  • Fill cleanup on deactivation is handled via fillRegistry.unregisterPlugin(providerType), called by usePluginSync when it receives a plugin:deactivated, plugin:auto-disabled or plugin:updated WebSocket event.
FilePurpose
client/src/core/slot-fill/registry.tsSlotFillRegistry implementation
client/src/core/slot-fill/components.tsx<Slot>, <Fill>, <SlotFillProvider>
client/src/core/slot-fill/plugin-error-boundary.tsx<PluginErrorBoundary>
client/src/main.tsxRegisters AdminPluginNavItems and UserPluginNavItems as core fills
client/src/hooks/usePluginSync.tsCalls fillRegistry.unregisterPlugin() on deactivation
Try the live demo Pricing

Have questions about Poker Engine? We reply fast.