Skip to content

Messaging

Messaging is a custom RPC channel between a gadget’s React frontend and its WASM backend. It runs parallel to the search pipeline and lets you call arbitrary backend logic from your views or settings panels: fetching data from a database, validating input, triggering actions, or anything else your UI needs that the search interface doesn’t cover.

Messaging is optional. Gadgets that only use the standard search result list don’t need it.

The sendMessage function is available through the useGadgetRuntime hook:

import { useGadgetRuntime } from "@torchsnap/gadget-sdk/hooks";
const { sendMessage } = useGadgetRuntime();

The function signature is:

type GadgetSendMessage = <TPayload = unknown, TResult = unknown>(
method: string,
payload: TPayload,
) => Promise<TResult>;

method identifies which operation to invoke on the backend. payload is serialized as JSON and passed to the backend’s handle_message. The returned promise resolves with the parsed JSON response, or rejects with an error string.

const { sendMessage } = useGadgetRuntime();
async function clearHistory() {
await sendMessage("clear_history", {});
}
async function loadPage(page: number) {
const data = await sendMessage<{ page: number }, HistoryPage>(
"get_history",
{ page },
);
setEntries(data.entries);
}

On the Rust side, sendMessage calls route to the gadget’s handle_message export. The method string maps directly to the first argument. Both the payload and response are JSON-encoded strings:

impl MessagingGuest for MyGadget {
fn handle_message(method: String, payload: String) -> Result<String, String> {
match method.as_str() {
"get_history" => {
let req: PageRequest = messaging::parse_payload(&payload)?;
let page = load_history_page(req.page)?;
messaging::to_response(&page)
}
"clear_history" => {
clear_history_db()?;
messaging::to_response(&true)
}
_ => Err(format!("unknown method: {method}")),
}
}
}

The SDK helpers messaging::parse_payload and messaging::to_response handle JSON serialization. See the Exports reference for the full WIT contract and SDK trait details.

Both directions are JSON-encoded strings at the WIT boundary:

  • Frontend → Backend: payload is serialized to JSON by the host before reaching handle_message.
  • Backend → Frontend: the Ok(String) return value is parsed as JSON by the host before resolving the sendMessage promise.

The TypeScript generic parameters on sendMessage<TPayload, TResult> provide type safety on the frontend side, but the actual wire format is always a JSON string. Make sure your Rust types and TypeScript types agree on the shape.

The sendMessage type signature includes an optional onMessage callback parameter, but this is only functional for native (non-WASM) gadgets. For WASM gadgets, the callback is never invoked. This limitation exists because the WIT messaging::handle-message interface is one-shot by design.