Skip to content

React Library

@webgui/react provides React 18 hooks and TypeScript types for SPAs running inside the WebGUI mod. All hooks use useSyncExternalStore — concurrent-mode safe, no Provider required.

Installation

bash
npm install @webgui/react
# or
pnpm add @webgui/react

Requires React 18+ as a peer dependency.

Quick example

tsx
import { useWebGUIClient, isInMod, isReady } from '@webgui/react'

export function PlayerInfo() {
  const client = useWebGUIClient()

  if (!isInMod())       return <p>Open this page inside Minecraft.</p>
  if (!isReady(client)) return <p>Connecting…</p>

  return <p>Hello, {client!.username}</p>
}

How it works

The mod injects window.webgui into every page and fires a webgui:client CustomEvent every time client data is updated. The library subscribes to this event at module load and feeds all hooks via a singleton store — no setup needed.

Available data

The mod pushes a snapshot at 20 TPS (once per client tick):

ts
interface WebGUIClient {
  playerUuid:  string                                      // player UUID in the world
  username:    string                                      // display name
  webviewMode: 'GUI_SCREEN' | 'HUD_OVERLAY' | 'NONE'
  dimension:   string                                      // e.g. "minecraft:overworld"
  pos:         { x: number; y: number; z: number }
  server?: {
    address?: string                                       // server address
    ping?:    number                                       // ping in ms
  }
}

When the GUI was opened by right-clicking an entity (via /webgui bind entity), the mod also sets window.webgui.entity:

ts
interface WebGUIEntity {
  uuid: string          // entity UUID
  type: string          // namespaced type, e.g. "minecraft:villager"
  name: string          // custom name if set, otherwise type display name
  pos:  { x: number; y: number; z: number }
}

window.webgui.entity is null when the GUI was opened via command rather than entity interaction.

Hooks

useWebGUIClient

ts
function useWebGUIClient(): WebGUIClient | null

Returns the latest snapshot. null before the first push or outside the mod. Re-renders on every update.

useWebGUIEntity

ts
function useWebGUIEntity(): WebGUIEntity | null

Returns the entity the player right-clicked to open this GUI, or null if the GUI was opened via command.

tsx
import { useWebGUIEntity } from '@webgui/react'

export function NpcHeader() {
  const entity = useWebGUIEntity()
  if (!entity) return null
  return <h2>Talking to: {entity.name}</h2>
}

useWebGUISelector

ts
function useWebGUISelector<T>(
  selector: (client: WebGUIClient) => T,
  equalFn?: (a: T, b: T) => boolean
): T | null

Derives a value and re-renders only when it changes.

tsx
const username = useWebGUISelector(c => c.username)

usePostToGame

ts
function usePostToGame(): (payload: PostToGamePayload) => void

Stable callback that sends a message to the game via the mod's CEF message router. No-op outside the mod.

tsx
const post = usePostToGame()

post({ channel: 'log', level: 'info', message: 'shop opened' })
post({ channel: 'shop:buy', itemId: 'minecraft:diamond', qty: 1 })

useCloseGui

ts
function useCloseGui(): () => void

Stable callback that closes the active HUD overlay or GUI screen from inside the SPA.

tsx
const close = useCloseGui()
<button onClick={close}>✕ Close</button>

useWebGUIToken

ts
function useWebGUIToken(paramName?: string): string | null

Returns the signed token the mod appended to the current page URL, or null when the token is absent (tokens disabled on the server, or normal browser tab). The token is static for the page lifetime.

ParameterDefaultDescription
paramName"webgui_token"Query parameter name configured in server.json
tsx
import { useWebGUIToken } from '@webgui/react'

export function App() {
  const token = useWebGUIToken()

  useEffect(() => {
    if (!token) return
    fetch('/api/data?webgui_token=' + token)
      .then(r => r.json())
      .then(setData)
  }, [token])
}

See Backend Token Verification for examples of how to verify the token in Python and Node.js.

useWebGUIEvent

ts
function useWebGUIEvent<T = unknown>(
  eventName: string,
  handler: (data: T) => void
): void

Subscribes to a named event pushed from the server via WebviewApi.emitToPage. The eventName is given without the webgui: prefix; handler receives the event's detail payload each time it fires. The listener is registered on mount and cleaned up automatically on unmount.

tsx
import { useState } from 'react'
import { useWebGUIEvent } from '@webgui/react'

export function Wallet() {
  const [balance, setBalance] = useState(0)

  useWebGUIEvent<{ balance: number }>('walletUpdate', ({ balance }) => {
    setBalance(balance)
  })

  return <span>Balance: {balance}</span>
}

TIP

Pass a stable handler (e.g. wrapped in useCallback) if its identity would otherwise change every render, since the subscription re-registers whenever eventName or handler changes.

See Events for how the server emits these events.

Utils

Plain functions — usable outside React, in conditions, in tests.

isInMod()

ts
function isInMod(): boolean

true when window.webgui is present.

isReady(client)

ts
function isReady(client: WebGUIClient | null): boolean

true when client !== null — at least one snapshot has been received.

TypeScript global types

The package ships global augmentations for window.webgui, the webgui:client event, and the webgui:entity event. Add to tsconfig.json:

json
{
  "compilerOptions": {
    "types": ["@webgui/react"]
  }
}

Then:

ts
window.webgui?.postToGame({ channel: 'log', message: 'hello' })

window.addEventListener('webgui:client', (e) => {
  console.log(e.detail.username) // fully typed
})

window.addEventListener('webgui:entity', (e) => {
  console.log(e.detail?.uuid) // WebGUIEntity | null, fully typed
})

Server-side rendering

All hooks return null on the server. isInMod() returns false. No typeof window guards needed in your components.