npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@entityseven/fivem-rpc

v1.0.0

Published

Call FiveM server, client and NUI listeners like async functions: typed, with timeouts, no event ping-pong

Readme

FiveM RPC

Call FiveM server, client and NUI listeners like async functions: typed, with timeouts, no event ping-pong

Installation, quick start and package overview: main readme. Typed events: shared-types. Upgrading from 0.1: migration guide.

Exports

Besides createRPC the package exports:

  • RPCError, RPCErrors, RPCErrorDetails - see Errors
  • RPCConfig, RPCEnvironment and the instance types RPCInstanceServer, RPCInstanceClient, RPCInstanceWebview
  • native event types RPCNativeServerEvents, RPCNativeClientEvents, RPCNativeClientNetworkEvents, RPCNativeClientNetworkEventsNames and the lists NATIVE_SERVER_EVENTS, NATIVE_CLIENT_EVENTS, NATIVE_CLIENT_NETWORK_EVENTS accepted by the onNative* methods

RPCConfig

type RPCConfig = {
    env: 'server' | 'client' | 'webview'
    debug?: boolean // default false, logs every registration, call and incoming payload
    timeout?: number // default 5000, ms to wait for a response, 0 disables it
}

An unknown env makes createRPC throw RPCError with code RPCErrors.UNKNOWN_ENVIRONMENT

Errors

Every error from this library is an RPCError. code is one of RPCErrors, message says what to fix, and details names the call when the error comes from one

enum RPCErrors {
    EVENT_NOT_REGISTERED = 'Event not registered',
    UNKNOWN_NATIVE = 'Unknown native event',
    UNKNOWN_ENVIRONMENT = 'Unknown environment',
    TIMEOUT = 'Timed out waiting for response',
    HANDLER_ERROR = 'Listener threw an error',
}

Example error

The server has no onClient('buyItem', ...) listener, so the call from the client rejects:

import { RPCError, RPCErrors } from '@entityseven/fivem-rpc'

try {
    await rpc.emitServer('buyItem', 'water')
} catch (e) {
    if (e instanceof RPCError && e.code === RPCErrors.EVENT_NOT_REGISTERED) {
        e.message // 'No listener for "buyItem" on server. Register it with rpc.onClient("buyItem", ...) in server code.'
        e.details // { event: 'buyItem', uuid: '<uuid>', from: 'client', to: 'server' }
    }
}

How it works

Directions

Every call goes from an emit* method in one environment to the matching on* listener in another. The last column is the shared-types interface that types it

| From | Call | To | Listener | Typed by | | ------- | ------------------------------------- | ----------- | ------------------------------------- | ------------------------- | | server | emitClient(player, event, ...args) | client | onServer | RPCEvents_ServerClient | | server | emitClientEveryone(event, ...args) | all clients | onServer, no response | RPCEvents_ServerClient | | server | emitWebview(player, event, ...args) | webview | onServer, via client | RPCEvents_ServerWebview | | server | emitSelf(event, ...args) | server | onSelf | RPCEvents_Server | | client | emitServer(event, ...args) | server | onClient, player first | RPCEvents_ClientServer | | client | emitWebview(event, ...args) | webview | onClient | RPCEvents_ClientWebview | | client | emitSelf(event, ...args) | client | onSelf | RPCEvents_Client | | webview | emitServer(event, ...args) | server | onWebview, player first, via client | RPCEvents_WebviewServer | | webview | emitClient(event, ...args) | client | onWebview | RPCEvents_WebviewClient | | webview | emitSelf(event, ...args) | webview | onSelf | RPCEvents_Webview |

Commands registered with onCommand are typed by RPCCommands_Server and RPCCommands_Client

Routing

Server and client talk over FiveM network events, client and webview over NUI messages and NUI callbacks. Webview and server never talk directly: every call between them is relayed by the client of that player. So every client must run createRPC({ env: 'client' }), even with no listeners of its own, or those calls time out

One listener per event

Each on* method keeps one listener per event name. Registering the same name again replaces the previous listener, off* removes it. Directions are separate: onClient('x') and onWebview('x') on the server do not replace each other

Responses, errors and timeouts

  • emit* resolves with the value the listener returns (promises are awaited)
  • no listener on the target: the call rejects with RPCErrors.EVENT_NOT_REGISTERED
  • the listener throws: the target logs the error with console.error, the call rejects with RPCErrors.HANDLER_ERROR and the original message
  • no response within RPCConfig.timeout (default 5000 ms): the call rejects with RPCErrors.TIMEOUT and a late response is ignored. timeout: 0 waits forever
  • emitSelf calls the local listener directly, whatever it throws reaches the caller unchanged
  • emitClientEveryone does not wait for clients: it resolves once sent, failures stay on each client (console.error for a throwing listener, the rest with debug: true)

Player identity

Server listeners (onClient, onWebview) get the calling player's server id as the first argument. It comes from FiveM's source, never from the payload, so a client cannot pose as another player. Use it instead of player ids passed as arguments. A response to emitClient or emitWebview is only accepted from the player it was sent to

Server (source)

onClient

Listens to client event

rpc.onClient('clientServerEvent', (player, arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offClient

Stops listening to client event

rpc.offClient('clientServerEvent')

emitClient

Sends event to specified client

const response = await rpc.emitClient(playerServerId, 'serverClientEvent', someData)
// response will come from client listener with returned data

emitClientEveryone

Sends event to all clients. One-way: clients run their listener but do not answer

await rpc.emitClientEveryone('serverClientEvent', someData)

onWebview

Listens to webview event

rpc.onWebview('webviewServerEvent', (player, arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offWebview

Stops listening to webview event

rpc.offWebview('webviewServerEvent')

emitWebview

Sends event to the webview of specified player

const response = await rpc.emitWebview(playerServerId, 'serverWebviewEvent', someData)
// response will come from webview listener with returned data

onSelf

Listens to server event

rpc.onSelf('serverEvent', (arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offSelf

Stops listening to server event

rpc.offSelf('serverEvent')

emitSelf

Sends event to server

const response = await rpc.emitSelf('serverEvent', someData)
// response will come from server listener with returned data

onCommand

Registers chat command. args are the raw strings typed after the command, validate them yourself. With restricted set to true only players with the ACE permission command.<name> can use it (defaults to false)

rpc.onCommand('serverCommand', (player, args, rawCommand) => {
    // logic
}, true /* restricted */)

onNativeEvent

Listens to native server event (reference)

rpc.onNativeEvent('playerJoining', (source, oldId) => {
    // logic
})

Client (source)

onServer

Listens to server event

rpc.onServer('serverClientEvent', (arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offServer

Stops listening to server event

rpc.offServer('serverClientEvent')

emitServer

Sends event to server

const response = await rpc.emitServer('clientServerEvent', someData)
// response will come from server listener with returned data

onWebview

Listens to webview event

rpc.onWebview('webviewClientEvent', (arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offWebview

Stops listening to webview event

rpc.offWebview('webviewClientEvent')

emitWebview

Sends event to own webview

const response = await rpc.emitWebview('clientWebviewEvent', someData)
// response will come from webview listener with returned data

onSelf

Listens to client event

rpc.onSelf('clientEvent', (arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offSelf

Stops listening to client event

rpc.offSelf('clientEvent')

emitSelf

Sends event to client

const response = await rpc.emitSelf('clientEvent', someData)
// response will come from client listener with returned data

onCommand

Registers chat command. args are the raw strings typed after the command, validate them yourself

rpc.onCommand('clientCommand', (player, args, rawCommand) => {
    // logic
})

onNativeEvent

Listens to native client event (reference)

rpc.onNativeEvent('entityDamaged', (victim, culprit, weapon, baseDamage) => {
    // logic
})

onNativeNetworkEvent

Listens to native client network event (reference)

rpc.onNativeNetworkEvent('CEventShockingCarCrash', (entities, eventEntity, data) => {
    // logic
})

setWebviewFocus

Sets or removes focus and cursor from own webview

rpc.setWebviewFocus(true /* focus */, true /* show cursor */)

Webview (source)

onClient

Listens to client event

rpc.onClient('clientWebviewEvent', (arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offClient

Stops listening to client event

rpc.offClient('clientWebviewEvent')

emitClient

Sends event to own client

const response = await rpc.emitClient('webviewClientEvent', someData)
// response will come from client listener with returned data

onServer

Listens to server event

rpc.onServer('serverWebviewEvent', (arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offServer

Stops listening to server event

rpc.offServer('serverWebviewEvent')

emitServer

Sends event to server

const response = await rpc.emitServer('webviewServerEvent', someData)
// response will come from server listener with returned data

onSelf

Listens to webview event

rpc.onSelf('webviewEvent', (arg1, arg2, ...rest) => {
    // logic
    return someData // this will be forwarded back to caller
})

offSelf

Stops listening to webview event

rpc.offSelf('webviewEvent')

emitSelf

Sends event to webview

const response = await rpc.emitSelf('webviewEvent', someData)
// response will come from webview listener with returned data

Using with AI agents

The package ships an Agent Skill with the directions, rules, typing and error fixes above. Copy it into your agent's skills folder, e.g. for Claude Code:

cp -r node_modules/@entityseven/fivem-rpc/skills/fivem-rpc .claude/skills/

Copy it again after upgrading. Every method also carries TSDoc with its direction and matching listener

License

Licensed under the Custom Attribution-NoDerivs Software License