Skip to main content

PanelExtensionContext

type PanelExtensionContext = object;

The PanelExtensionContext exposes properties and methods for writing a custom panel. The context has methods to subscribe for messages, receive updates, configure your panel's settings, and render your panel to the UI.

The initPanel function used in registerPanel accepts a PanelExtensionContext argument. This argument contains properties and methods for accessing panel data and rendering UI updates. The initPanel function also returns an optional cleanup function to run when the extension panelElement unmounts.

See the Creating a custom panel guide for more details.

Properties

panelElement

readonly panelElement: HTMLDivElement;

The root element for the panel. Add your panel elements as children under this element.


initialState

readonly initialState: unknown;

Initial panel state


layout

readonly layout: LayoutActions;

Actions the panel may perform related to the user's current layout. See LayoutActions for details.


dataSourceProfile?

readonly optional dataSourceProfile: string;

Identifies the semantics of the data being played back, such as which topics or parameters are semantically meaningful or normalization conventions to use. This typically maps to a shorthand identifier for a robotics framework such as "ros1", "ros2", or "ulog". See the MCAP profiles concept at https://github.com/foxglove/mcap/blob/main/docs/specification/appendix.md#well-known-profiles.


onRender()?

optional onRender: (renderState, done) => void;

Set this property to a function during your panel's initialization.

Foxglove will run context.onRender whenever your panel needs to re-render during playback. The function accepts renderState and a done callback as its arguments. Render events occur frequently (60hz, 30hz, etc).

Note: Your onRender function must call done after rendering to indicate that the panel is ready to render the next set of data. The exact placement of this done invocation will vary between frameworks and different extensions' logic.

context.onRender = (renderState, done) => {
// Render your UI updates with fields from RenderState

// Call done when you've rendered all the UI for this renderState.
// If your UI framework delays rendering, call done when rendering has actually happened.
done();
};

Parameters

ParameterType
renderStateImmutable<RenderState>
done() => void

Returns

void


subscribeMessageRange()?

optional subscribeMessageRange: (args) => () => void;

Subscribe to receive the entire time range of messages for a given topic for the current data source.

See SubscribeMessageRangeArgs for more information on behavior.

Note: This will not read messages for live sources, like foxglove_bridge, rosbridge, or ROS 1 native connections. For those messages you will still need to use context.subscribe() and watch("currentFrame").

Parameters

ParameterType
argsSubscribeMessageRangeArgs

Returns

Function

A function that will unsubscribe from the topic, cancel the active async iterator, and prevent onNewRangeIterator from being called again.

Returns

void


UNSTABLE_subscribeMessageRange()?

optional UNSTABLE_subscribeMessageRange: (args) => () => void;

Parameters

ParameterType
argsSubscribeMessageRangeArgs

Returns

Function

Returns

void

Deprecated

Renamed to subscribeMessageRange. Please use that method instead.

Methods

watch()

Call Signature

watch(field): void

Subscribe to updates on this field within the render state. Render will only be invoked when this field changes.

Use context.watch to indicate which fields in RenderState (e.g. currentFrame, currentTime, previewTime, parameters, topics) should trigger panel re-renders when their contained values change.

context.watch("topics");
context.watch("currentFrame");
context.watch("parameters");
context.watch("currentTime");
Parameters
ParameterType
fieldkeyof RenderState
Returns

void

Call Signature

watch(field): void

Subscribe to updates on this field within the render state. Render will only be invoked when this field changes.

Parameters
ParameterType
field"allFrames"
Returns

void

Deprecated

Calling watch with allFrames is deprecated. Use PanelExtensionContext.subscribeMessageRange instead.


saveState()

saveState(state): void

Use context.saveState to save an arbitrary object as persisted panel state (also known as panel settings) in the current layout. You can view the current panel state using Import/export settings.

context.initialState = undefined; // your panel's initial state

context.saveState({ myNum: 2, myBool: false, myStr: "abc" });

Parameters

ParameterTypeDescription
statePartial<unknown>The state to save. This value should be JSON serializable.

Returns

void


setParameter()

setParameter(name, value): void

Use context.setParameter to set a parameter name to any valid value (i.e. primitives, dates, Uint8Arrays, and arrays or objects containing these values).

context.setParameter("/param1", "value1");

Parameters

ParameterTypeDescription
namestringThe name of the parameter to set.
valueParameterValueThe new value of the parameter.

Returns

void


setSharedPanelState()

setSharedPanelState(state): void

Set the transient state shared by panels of the same type as the caller of this function. This will not be persisted in the layout.

Parameters

ParameterType
stateundefined | Record<string, unknown>

Returns

void


setVariable()

setVariable(name, value): void

Use context.setVariable to set a variable name to any valid variable value.

context.setVariable("myVar", 55);

context.onRender = (renderState: RenderState, done) => {
// Read variable values from the renderState
const variableValues = renderState.variables;
const myVarValue = variableValues.myVar;

// Call done when you've rendered all the UI for this renderState. If your UI framework delays rendering, call done when rendering has actaully happened.
done();
};

Parameters

ParameterTypeDescription
namestringThe name of the variable to set.
valueVariableValueThe new value of the variable.

Returns

void


setPreviewTime()

setPreviewTime(time): void

Set the active preview time. Setting the preview time to undefined clears the preview time.

Parameters

ParameterType
timeundefined | number

Returns

void


seekPlayback()?

optional seekPlayback(time): void

Seek playback to the given time. Behaves as if the user had clicked the playback bar to seek.

Clients can pass a number or alternatively a Time object for greater precision.

This property may be undefined if the current data source does not support seeking.

Parameters

ParameterType
timenumber | Time

Returns

void


subscribe()

Call Signature

subscribe(subscriptions): void

Use context.subscribe to indicate the topics your panel wants to receive messages for. The messages are provided during render in RenderState.currentFrame.

Parameters
ParameterType
subscriptionsSubscription[]
Returns

void

Remarks

This method will update the current subscriptions to the new list of Subscriptions and unsubscribe from any previously subscribed topics no longer in the Subscription list. Passing an empty array will unsubscribe from all topics.

context.subscribe([{ topic: "/some/topic" }, { topic: "/another/topic" }]);

context.subscribe([]) will unsubscribe from all topics, and is equivalent to unsubscribeAll.

Range loading

Most panels display data from the current frame; examples of built-in panels that display the current frame are 3D, Image, and Raw Message, however some panels can display data for multiple messages or even the entire dataset duration (Plot, Map, State Transitions).

Subscriptions will provide only the messages for the current frame. If your panel would like to process all the available messages on a topic, use @PanelExtensionContext.subscribeMessageRange | subscribeMessageRange instead.

NOTE: Message range loading is done on a best-effort basis. If your range-loaded messages exceed available memory limits for the browser or desktop app, then the data may not represent the full dataset range. Range loading results in more data transfer and memory use and is recommended only for panels which require access to the entire dataset.

Message converters

Message converters can convert messages from one schema to another – for example, a user might convert custom GPS message into foxglove.LocationFix messages for visualization in the Map panel. Users may have one or more message converters registered.

If your panel expects messages with specific schema names, you can leverage registered message converters to convert from one schema to another.

Specify the convertTo option to enable message conversion on a topic. When conversion is enabled for a subscription, the MessageEvents will contain message entries with the converted message rather than the original message on the topic. The original message is available in the originalMessageEvent field in the message event.

context.subscribe([{ topic: "/some/topic", convertTo: "foxglove.LocationFix" }]);

The convertibleTo field within RenderState.topics will contain the names of schemas you can convert this topic into.

Call Signature

subscribe(topics): void
Parameters
ParameterType
topicsstring[]
Returns

void

Deprecated

Use subscribe with an array of Subscription objects instead.


unsubscribeAll()

unsubscribeAll(): void

Unsubscribe from all topics.

Note: This is analagous to calling subscribe([]) with an empty array of topics.

Returns

void


subscribeAppSettings()

subscribeAppSettings(settings): void

Subscribe to any changes in application settings for an array of setting keys.

The keys and their corresponding values are not currently documented and are subject to change.

Parameters

ParameterType
settingsstring[]

Returns

void


optional advertise(
topic,
schemaName,
options?): void

Use context.advertise to indicate an intent to publish a specific datatype on a topic. A panel must call context.advertise before being able to publish on the topic (context.publish). Options are specific to the data source - some make use of options; others do not.

This property may be undefined if the current data source does not support publishing.

context.advertise("/my_image_topic", "sensor_msgs/Image");

options are specific to each data source - see documentation below for supported data sources.

Parameters

ParameterTypeDescription
topicstringThe topic on which the extension will publish messages.
schemaNamestringThe name of the schema that the published messages will conform to.
options?Record<string, unknown>Options passed to the current data source for additional configuration.

Returns

void

Remarks

Native (ROS 1)

fieldtypedescription
datatypesMap<string, T>JavaScript map of datatype names and MessageDefinition definitions for a given topic

Common datatype definitions are available in the @foxglove/rosmsg-msgs-common package.

import { ros1 } from "@foxglove/rosmsg-msgs-common";

context.advertise?.(currentTopic, "sensor_msgs/Joy", {
datatypes: new Map([
["std_msgs/Header", ros1["std_msgs/Header"]],
["std_msgs/Float32", ros1["std_msgs/Float32"]],
["std_msgs/Int32", ros1["std_msgs/Int32"]],
["sensor_msgs/Joy", ros1["sensor_msgs/Joy"]],
]),
});

Foxglove WebSocket

options depend on the server implementation.

Foxglove Bridge

When using the Foxglove Bridge with ROS data, use context.dataSourceProfile to determine the ROS version.

For ROS 1 data, pass datatypes the same way you would for a native ROS 1 connection.

For ROS 2 data, pass datatypes using the following fields:

fieldtypedescription
datatypesMap<string, T>JavaScript map of datatype names and MessageDefinition definitions for a given topic

Common datatype definitions are available in the @foxglove/rosmsg-msgs-common package.

import { ros2humble as ros2 } from "@foxglove/rosmsg-msgs-common";
// For Galactic and lower, use:
// import { ros2galactic as ros2 } from "@foxglove/rosmsg-msgs-common";
context.advertise?.(currentTopic, "sensor_msgs/Joy", {
datatypes: new Map([
["std_msgs/Header", ros2["std_msgs/Header"]],
["std_msgs/Float32", ros2["std_msgs/Float32"]],
["std_msgs/Int32", ros2["std_msgs/Int32"]],
["sensor_msgs/Joy", ros2["sensor_msgs/Joy"]],
]),
});

Rosbridge (ROS 1, ROS 2)

No options required. Simply publish a JSON message with fields conforming to the advertised datatype, and the bridge node will serialize it according to the datatype.


unadvertise()?

optional unadvertise(topic): void

Indicate that you no longer want to advertise on this topic.

context.unadvertise("/my_image_topic");

This property may be undefined if the current data source does not support publishing.

Parameters

ParameterType
topicstring

Returns

void


publish()?

optional publish(topic, message): void

Use context.publish to publish a message on a previously advertised topic. (You must first call advertise to advertise the topic before publishing.) If the topic is not advertised or otherwise malformed, the function will throw an error.

context.advertise("/my_color_topic", "std_msgs/ColorRGBA");
context.publish("/my_color_topic", { r: 0, g: 1, b: 0, a: 1 });

This property may be undefined if the current data source does not support publishing.

Parameters

ParameterTypeDescription
topicstringThe name of the topic to publish the message on
messageunknownThe message to publish

Returns

void


callService()?

optional callService(service, request): Promise<unknown>

Use context.callService to make a service call to the specified service with a request payload.

context.callService("my_service", { foo: "bar" });

This property may be undefined if the current data source does not support services.

Parameters

ParameterTypeDescription
servicestringThe name of the service to call
requestunknownThe request payload for the service call

Returns

Promise<unknown>

A promise that resolves when the result is available or rejected with an error


updatePanelSettingsEditor()

updatePanelSettingsEditor(settings): void

Call the updatePanelSettingsEditor method on your panel's PanelExtensionContext instance to define or update its settings.

const panelSettings: SettingsTree = {
nodes: { ... },
actionHandler: (action: SettingsTreeAction) => { ... }
};

context.updatePanelSettingsEditor(panelSettings);

The settings argument must be a valid SettingsTree and include 2 mandatory properties – nodes and actionHandler:

  • nodes - Hierarchical structure where each node can contain input fields, display fields, or even other nodes
  • actionHandler - Function that is invoked when the user interacts with the settings UI; contains logic to process the interactions and update the panel or settings tree

It can also include the following optional properties:

  • enableFilter – Whether the settings should show the filter control
  • focusedPath – Node to scroll to (transient one-time effect)

The example tree below has a title text input field inside a General section along with an actionHandler to respond to updates for the title field.

const panelSettings: SettingsTree = {
nodes: {
general: {
label: "General",
fields: {
title: "{
label: "Title",
input: "string",
// `panelTitle` refers to a value in your extension panel's config
value: panelTitle,
},
},
},
},
actionHandler: (action: SettingsTreeAction) => {
switch (action.action) {
case "perform-node-action":
// Handle user-defined actions for nodes in the settings tree
break;
case "update":
if (action.payload.path[0] === "general" && action.payload.path[1] === "title") {
// Read action.payload.value for the new panel title value
panelTitle = action.payload.value;

// Update your panel's state accordingly
}
break;
}
},
}

context.updatePanelSettingsEditor(panelSettings);

SettingsTreeAction

A SettingsTreeAction describes how the settings UI should update when a user interacts with its fields.

Each SettingsTreeAction has a payload with a path to the settings field to update (e.g. ["general", "title"]).

The update action corresponds to a user setting a new value for a field (e.g. "My new title").

Special node properties

There are two special SettingsTreeNode properties, label and visibility. The value you specify for label will control the label displayed in the settings editor. If you set the renamable node property to true, the user can edit the node label – you will receive a SettingsTreeAction of update with a path ending in label.

In addition, if you specify a boolean value for visibility of the node then the settings editor will provide a button to toggle the visibility of the node and you will receive an update action with visibility as the final element in the path.

For an example of how to use these special properties, check out the panel settings example extension.

Input types

In addition to the string input type in the example above, the panel API provides a wide array of types for your extension panel input fields.

Each input type has different properties that you can configure:

  • autocomplete
  • boolean
  • rgb
  • rgba
  • gradient
  • messagepath
  • select
  • string
  • toggle
  • vec3
  • vec2

Parameters

ParameterTypeDescription
settings{ actionHandler: (action) => void; enableFilter: boolean; focusedPath: readonly string[]; nodes: {}; }-
settings.actionHandler(action) => voidHandler to process all actions on the settings tree initiated by the UI.
settings.enableFilter?booleanTrue if the settings editor should show the filter control.
settings.focusedPath?readonly string[]Setting this will have a one-time effect of scrolling the editor to the node at the path and highlighting it. This is a transient effect so it is not necessary to subsequently unset this.
settings.nodes{}The settings tree root nodes. Updates to these will automatically be reflected in the editor UI.

Returns

void


setDefaultPanelTitle()

setDefaultPanelTitle(defaultTitle): void

Use context.setDefaultPanelTitle to override a panel's default title. Users can always override the default title by editing it manually. If no override or default title is set, the panel will simply display its type (e.g. "Image").

// Override the default panel title
context.setDefaultPanelTitle(`Plot of ${config.topicName}`);

Parameters

ParameterType
defaultTitleundefined | string

Returns

void