Chrome Extension Extension Messaging Protocols — Best Practices
3 min readExtension Messaging Protocols
Designing structured messaging protocols for Chrome extensions ensures type-safe, maintainable communication between content scripts, background scripts, and popup pages.
Typed Message Protocol with Discriminated Unions
Use discriminated unions for type-safe message routing:
// Shared types (in a common package or content script)
type MessageType =
| { type: 'GET_STATE'; payload: undefined }
| { type: 'UPDATE_STATE'; payload: { changes: StateUpdate } }
| { type: 'STREAM_DATA'; payload: { correlationId: string } };
type Response<T extends MessageType> =
| { success: true; data: T['payload'] }
| { success: false; error: ErrorCode };
Request-Response Pattern
Standardize all responses with success/error shape:
interface Response<T> {
success: true;
data: T;
} | {
success: false;
error: {
code: ErrorCode;
message: string;
};
}
Versioning for Forward Compatibility
Include protocol version in every message:
interface ProtocolMessage<T> {
version: number;
type: string;
payload: T;
timestamp: number;
}
Standardized Error Codes
Define a consistent error code enum:
enum ErrorCode {
VALIDATION_ERROR = 'VALIDATION_ERROR',
TIMEOUT = 'TIMEOUT',
NOT_FOUND = 'NOT_FOUND',
UNAUTHORIZED = 'UNAUTHORIZED',
INTERNAL_ERROR = 'INTERNAL_ERROR',
}
Message Router
Create a type-safe router for handling messages:
type MessageHandler<T extends MessageType> = (
payload: T['payload']
) => Promise<ResponsePayload<T>>;
const router: Record<string, MessageHandler<any>> = {
GET_STATE: async ({}) => ({ success: true, data: currentState }),
UPDATE_STATE: async ({ changes }) => {
applyChanges(changes);
return { success: true, data: { applied: true } };
},
};
Timeout Handling with AbortController
Implement reliable request-response with timeouts:
async function sendWithTimeout<T>(
message: MessageType,
timeoutMs = 5000
): Promise<T> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
return await browser.runtime.sendMessage(message);
} finally {
clearTimeout(timeout);
}
}
Message Logging Interceptor
Add debugging and audit capabilities:
const loggingInterceptor = (handler: Function) => async (msg: Message) => {
console.log(`[MSG] ${msg.type}:`, msg.payload);
const start = performance.now();
const result = await handler(msg);
console.log(`[MSG] ${msg.type} completed in ${performance.now() - start}ms`);
return result;
};
Cross-Reference
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.