Structure

Messaging

Communicate between your extension's components.

Messaging API makes communication between different parts of your extension easy. To make it simple and scalable, we're leveraging @webext-core/messaging library.

It provides a declarative, type-safe, functional, promise-based API for sending, relaying, and receiving messages between your extension components.

Handling messages

Based on our convention, we implemented a little abstraction on top of @webext-core/messaging to make it easier to use. That's why all types and keys are stored inside lib/messaging directory:

lib/messaging/index.ts
import { defineExtensionMessaging } from "@webext-core/messaging";
 
export const Message = {
  HELLO: "hello",
} as const;
 
export type Message = (typeof Message)[keyof typeof Message];
 
interface Messages {
  [Message.HELLO]: (message: string) => string;
}
 
export const { onMessage, sendMessage } = defineExtensionMessaging<Messages>();

There you need to define what will be handled under each key. To make it more secure, only Message enum and onMessage and sendMessage functions are exported from the module.

All message handlers are located in src/app/background/messaging directory under respective subdirectories.

To create a message handler, create a TypeScript module in the background/messaging directory. Then, include your handlers for all keys related to the message:

app/background/messaging/example.ts
import { onMessage, Message } from "~/lib/messaging";
 
onMessage(Message.EXAMPLE, (req) => {
  const result = await querySomeApi(req.body.id);
 
  return result;
});

Don't forget to import!

To make your handlers available across your extension, you need to import them in the background/index.ts file. That way they could be interpreted by the build process facilitated by WXT.

Sending messages

Extension pages, content scripts, or tab pages can send messages to the handlers using the sendMessage function. Since we orchestrate your handlers behind the scenes, the message names are typed and will enable autocompletion in your editor:

app/popup/index.tsx
import { sendMessage, Message } from "~/lib/messaging";
 
...
 
const response = await sendMessage(Message.HELLO, "Hello, world!");
 
console.log(response);
 
...

As it's an asynchronous operation, it's advisable to use @tanstack/react-query integration to handle the response on the client side.

We're already doing it that way when fetching auth session in the User component:

hello.tsx
export const Hello = () => {
  const { data, isLoading } = useQuery({
    queryKey: [Message.HELLO],
    queryFn: () => sendMessage(Message.HELLO, "Hello, world!"),
  });
 
  if (isLoading) {
    return <p>Loading...</p>;
  }
 
  /* do something with the data... */
  return <p>{data?.message}</p>;
};

Last updated on

On this page

Ship your startup everywhere. In minutes.