Handlers
Handlers are content-context message endpoints. They receive actions routed from background and can subscribe to multicast events.
Generate a Handler
hexa generate handler tabs --namespace tabs
Then attach the handler to a specific content entry class:
hexa add handler TabsHandler MyContentEntry
@Handle must be unique by full route key. You cannot declare two handles that resolve to the same namespace:handle path anywhere in the content context.
Contents: [MyContentEntry] binds the handler to specific content entry classes. This is different from background context: background runs as a single script, while content can have multiple entries/scripts. Because of that, content handlers should declare Contents so routing and bundling stay scoped to the right content script(s).
Boundary policy decorators
Handlers follow the same route boundary model as controllers:
- Default: internal-only.
@AllowExternal(...)opts a class or method into external callers.@InternalOnly()can re-lock a method on an externally allowed class.
import { AllowExternal, InternalOnly } from '@hexajs-dev/common';
import { Handle, Handler } from '@hexajs-dev/core';
@AllowExternal({ ids: ['trusted.extension.id'] })
@Handler({ namespace: 'sync', Contents: [MyContentEntry] })
export class SyncHandler {
@Handle('public')
onPublic(payload: unknown): { ok: true } {
return { ok: true };
}
@InternalOnly()
@Handle('private')
onPrivate(payload: unknown): { ok: true } {
return { ok: true };
}
}
Method-level @AllowExternal() is also supported for handlers:
import { AllowExternal, InternalOnly } from '@hexajs-dev/common';
import { Handle, Handler } from '@hexajs-dev/core';
@InternalOnly()
@Handler({ namespace: 'sync', Contents: [MyContentEntry] })
export class SyncHandler {
@Handle('private')
onPrivate(payload: unknown): { ok: true } {
return { ok: true };
}
@AllowExternal()
@Handle('admin')
onAdmin(payload: unknown): { ok: true } {
return { ok: true };
}
}
Important limitation: runtime.onMessageExternal is not available in content scripts. External callers should target background first, then background can relay to content routes when needed.
Handler Example
import { Handler, Handle } from '@hexajs-dev/core';
import { HexaContentStore } from '@hexajs-dev/core';
import { LoggerService } from '../services/logger.service';
import { MyContentEntry } from './content';
import { ContentState } from './store/content.state';
import { backgroundCalled } from './store/content.actions';
@Handler({ namespace: 'tabs', Contents: [MyContentEntry] })
export class TabsHandler {
constructor(private logger: LoggerService, private store: HexaContentStore<ContentState>) {}
@Handle('active')
onActive(payload: { tabId: number }): { ok: boolean } {
this.logger.log('Background returned active tab id:', payload.tabId);
this.store.dispatch(backgroundCalled({ message: `tab ${payload.tabId}`, timestamp: Date.now() }));
return { ok: true };
}
}
Multicast Subscriptions
Use @Subscribe when one content handler listens to events pushed from background:
import { Handler, Subscribe } from '@hexajs-dev/core';
import { LoggerService } from '../services/logger.service';
import { MyContentEntry } from './content';
@Handler({ namespace: 'audit', Contents: [MyContentEntry] })
export class AuditHandler {
constructor(private logger: LoggerService) {}
@Subscribe('high-risk')
onHighRisk(payload: { message: string }): void {
this.logger.warn('Audit event received:', payload.message);
}
}
Notes
- Handlers are content-only classes and should inject content/general services.
@Handleis unicast. Only one handler can exist for a givennamespace:handleroute.- Prefer services and store abstractions in handlers, not direct browser APIs.
- Use
HexaContentStore<T>+dispatch(...)to reflect routed messages into local content state. - Namespace plus method decorator name forms the final route key:
namespace:name. - Pair handlers with Browser-Agnostic Messaging docs for request/response routing patterns.
@Handle
For Request/Response (Unary). CLI should enforce that handleName is UNIQUE per context.
import { Handle } from '@hexajs-dev/core';
@Handle(handleName: string)
@Handler
import { Handler } from '@hexajs-dev/core';
@Handler(options: HandlerOptions)
@Subscribe
For Fire-and-Forget (Multicast). Multiple methods can listen to the same eventName.
import { Subscribe } from '@hexajs-dev/core';
@Subscribe(eventName: string)
Supporting Types
HandlerOptions
interface HandlerOptions {
namespace: string;
Contents?: ContentClass[];
}