Skip to main content
controller@Controller()
guard@Guard({ rank: 3 })
schemaz.coerce.number()
throttle@Throttle(5, 2000)
event@OnNet('bank:action')
injectconstructor(svc: Svc)
latest

The runtime forFiveM

TypeScript-first framework with dependency injection, Zod validation, and security primitives. Built for FiveM & RageMP — RedM coming next.

BankController.ts
const TransferSchema = z.object({
  targetId: z.coerce.number().int().positive(),
  amount: z.coerce.number().positive(),
})

@Controller()
export class BankController {
  constructor(private readonly bankService: BankService) {}

  @Command({ command: 'transfer', schema: TransferSchema })
  @Guard({ permission: 'bank.transfer' })
  @Throttle(1, 2000)
  async transfer(player: Player, targetId: number, amount: number) {
    await this.bankService.move(player.clientID, targetId, amount)
    player.send('Transfer completed', 'success')
  }
}

Framework vs. Raw Code

See what changes when you adopt OpenCore instead of writing everything by hand.

OpenCoreInventoryController.ts
const GiveItemSchema = z.object({
  targetId: z.coerce.number().int().positive(),
  item: z.string().min(1),
  amount: z.coerce.number().int().positive(),
})

@Controller()
export class InventoryController {
  constructor(private readonly inventory: InventoryService) {}

  @Command({ command: 'giveitem', schema: GiveItemSchema })
  @Guard({ permission: 'inventory.give' })
  @Throttle(5, 1000)
  async giveItem(player: Player, targetId: number, item: string, amount: number) {
    await this.inventory.addItem(targetId, item, amount)
    player.send('Item given!', 'success')
  }
}

Everything Built-in

Primitives for secure, scalable, adapter-first multiplayer servers.

⌨️

Commands

Declarative handlers with Zod validation and Player injection by default

⌨️Commands
HealthController.ts
@Command('heal', z.tuple([z.coerce.number().int().positive()]))
async heal(player: Player, targetId: number) {
  const target = this.players.getById(targetId)
  target.setHealth(200)
  player.send('Healed ' + target.name, 'success')
}
📡

Network Events

Typed event handlers with Player context and payload validation

📡Network Events
BankEventsController.ts
const BankActionSchema = z.object({
  action: z.enum(['deposit', 'withdraw']),
  amount: z.coerce.number().int().positive(),
})

@Controller()
export class BankEventsController {
  @OnNet('bank:action', BankActionSchema)
  async onAction(player: Player, payload: z.infer<typeof BankActionSchema>) {
    await this.bank.handle(player.clientID, payload)
  }
}
🧩

Library Events

Emit domain events between modules without coupling resources together

🧩Library Events
CharacterListeners.ts
const characters = Server.createServerLibrary('characters')

@Controller()
export class CharacterListeners {
  @OnLibraryEvent('characters', 'session:created')
  onSessionCreated(payload: { sessionId: string; playerId: number }) {
    this.audit.log('character session ready', payload)
  }
}

characters.emit('session:created', { sessionId: 's-42', playerId: 12 })
🛡️

Guards & Permissions

Role-based access control via decorators

🛡️Guards & Permissions
AdminController.ts
@Guard({ rank: 3 })
@Command('ban')
async ban(player: Player, targetId: number, reason: string) {
  await this.moderation.ban(targetId, reason)
}

@Guard({ permission: 'admin.teleport' })
@Command('tp')
async teleport(player: Player, x: number, y: number, z: number) {
  player.teleport({ x, y, z })
}
⏱️

Rate Limiting

Built-in throttling per player, per method

⏱️Rate Limiting
MarketController.ts
@Throttle(5, 2000)
@Command('search')
async search(player: Player, query: string) {
  return this.market.search(query)
}

@Throttle({ limit: 1, windowMs: 5000, message: 'Too fast!' })
@Command('buy')
async placeOrder(player: Player, itemId: string) {
  await this.market.purchase(player, itemId)
}
👤

Player Entity

Rich player API: state, communication, health

👤Player Entity
PlayerEntity.ts
player.emit('client:notify', { message: 'Hello!' })
player.send('Private message', 'info')

player.setMeta('job', 'police')
player.addState('on_duty')

player.teleport({ x: 100, y: 200, z: 30 })
player.setHealth(150)
player.kick('AFK timeout')
🔧

Binary Services

Use binaries easily from your favorite compiled languages

🔧Binary Services
BinaryService.ts
@BinaryService({
  name: 'image-processor',
  binary: 'img_worker',
  timeoutMs: 30000
})
export class ImageService {
  @BinaryCall()
  async resize(path: string, w: number, h: number): Promise<{ url: string }> {
    return null as any
  }
}
🔌

Adapters

Target FiveM and RageMP today, with RedM support on the way

🔌Adapters
opencore.config.ts
export default defineConfig({
  name: 'my-server',
  adapter: {
    server: FiveMServerAdapter(),
    client: FiveMClientAdapter(),
  },
})
🔍

Dev Mode

Runtime inspection with event history and virtual players

🔍Dev Mode
DevMode.ts
await init({
  mode: 'CORE',
  devMode: {
    enabled: true,
    interceptor: { enabled: true, recordHistory: true },
    simulator: { enabled: true, autoConnectPlayers: 2 },
  },
})

OpenCore CLI

Monorepo build, watcher, scaffolding, restart and adapter tooling

OpenCore CLI
terminal
$ opencore build
$ opencore dev
$ opencore create resource inventory --with-client
$ opencore doctor
🔒

Security by Default

Guards, throttles, validation out of the box

🔒Security by Default
SecurityExample.ts
const SpawnCarSchema = z.object({
  model: z.string().min(1).max(32),
})

@Guard({ permission: 'admin.spawn' })
@Throttle(2, 10000)
@RequiresState({ has: ['spawned'], missing: ['dead'] })
@Command({ command: 'spawncar', schema: SpawnCarSchema })
async spawnCar(player: Player, model: string) {
  await this.vehicles.spawn(player, model)
}

Performance

Internal benchmarks — February 2026

17.78M
EventInterceptor ops/s
~0.056 µs mean
10.49M
RuntimeConfig ops/s
~0.095 µs mean
80.14K
Command throughput
500 players, p95 0.226 ms
251.10K
RPC throughput
500 parallel, p95 1.83 ms

Ready to build?

OpenCore is free, open-source, and production-ready.

Read the docs