Skip to main content

Documentation Index

Fetch the complete documentation index at: https://zenbulabs.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

When you define a service class in the main process, every public method becomes callable from the renderer process. Zenbu.js handles all the wiring between processes for you, so you just call methods like normal functions. Under the hood, communication happens over a WebSocket, which means the same RPC system works whether your app runs in Electron or in the browser. The transport is pluggable, so you can swap it for Electron IPC, WebRTC, or anything else. Services are scoped under their owning plugin’s name. For a plugin named app, the call site is rpc.app.<service>.<method>(...). Core’s own services live under rpc.core.<service>.
// Main process — declared inside the `app` plugin
export class MathService extends Service.create({
  key: "math",
}) {
  add(args: { a: number; b: number }) {
    return args.a + args.b
  }
}

// Renderer
const rpc = useRpc()
const result = await rpc.app.math.add({ a: 1, b: 2 }) // 3

Type inference

TypeScript types flow from the service definition to the renderer call site. Parameters, return types, and method names are all inferred. Types are generated by zen link (runs automatically during zen dev) and stored in .zenbu/types/, so if you rename a method or change its signature, the renderer code shows a type error immediately.

Async

All RPC calls are async from the renderer’s perspective, even if the service method is synchronous. This is because calls cross a process boundary.
// This service method is synchronous
getCount() {
  return this.count
}

// But from React, it returns a Promise
const count = await rpc.app.counter.getCount()

Error handling

Errors thrown in service methods are serialized and re-thrown in the renderer.
// Main process
export class AuthService extends Service.create({ key: "auth" }) {
  login(args: { email: string; password: string }) {
    if (!args.email) throw new Error("Email is required")
    // ...
  }
}

// Renderer
try {
  await rpc.app.auth.login({ email: "", password: "pass" })
} catch (e) {
  console.log(e.message) // "Email is required"
}

Method conventions

  • Take a single object argument. This keeps argument signatures stable as the API grows.
  • Return JSON-serializable values. Anything that round-trips through JSON.stringify works.