React SDK guide
Foxglove must run in a secure context. Serve your application over HTTPS or from localhost.
The @foxglove/embed-react package provides the <FoxgloveViewer /> component, providing a React API for embedding Foxglove in your application.
Installation
Start by installing the @foxglove/embed-react package.
- npm
- yarn
npm install --save @foxglove/embed-react
yarn add @foxglove/embed-react
Basic setup
The @foxglove/embed-react package provides the <FoxgloveViewer /> component. This component is built on top of the @foxglove/embed package and provides the same capabilities, but with a more React-friendly API.
None of the props are required, but you should at least provide the orgSlug prop to ensure the user is signed into the correct organization. If you don't know your organization slug (or if you're using a fully self-hosted embedded viewer) you can omit this prop, allowing users to select the organization they want to sign into.
import { FoxgloveViewer } from "@foxglove/embed-react";
function BasicExample() {
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer orgSlug="my-org-slug" />
</div>
);
}
Events
You can pass callbacks to the FoxgloveViewer component to be called when the viewer is ready or when an error occurs.
import { FoxgloveViewer } from "@foxglove/embed-react";
import { useState } from "react";
function EventsExample() {
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState<string | undefined>(undefined);
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer
orgSlug="my-org-slug"
onReady={() => {
setIsReady(true);
}}
onError={(err) => {
setError(err);
console.error("Error", err);
}}
/>
</div>
);
}
Changing the data source
The embedded viewer can be used to visualize data from a variety of sources (including files, live data sources, and recordings). The data source is set using the data prop.
The following example shows how to open a file picker and set the selected file as data source.
import { FoxgloveViewer } from "@foxglove/embed-react";
import { useState } from "react";
function EventsExample() {
const [dataSource, setDataSource] = useState<DataSource | undefined>(undefined);
return (
<div style={{ height: "100vh" }}>
<button
style={{ padding: 16 }}
onClick={async () => {
try {
const handles = await window.showOpenFilePicker({
multiple: false,
types: [
{
description: SUPPORTED_FILE_TYPES.join(", "),
accept: {
"application/octet-stream": SUPPORTED_FILE_TYPES,
},
},
],
});
const file = await handles[0].getFile();
setDataSource({
type: "file",
file,
});
} catch (err: unknown) {
throw new Error("Error opening file");
}
}}
>
Open file data source ...
</button>
<FoxgloveViewer orgSlug="my-org-slug" data={dataSource} />
</div>
);
}
You can also connect to a live data source, such as a Foxglove WebSocket, or a ROS Bridge.
import { FoxgloveViewer } from "@foxglove/embed-react";
function LiveDataSourceExample() {
// Connect to a Foxglove WebSocket
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer
orgSlug="my-org-slug"
data={{
type: "live",
// The protocol defined the type of live data source to connect to.
// Supported protocols are:
// - "foxglove-websocket" => Foxglove WebSocket
// - "rosbridge-websocket" => ROS Bridge
protocol: "foxglove-websocket",
url: "ws://localhost:8765",
}}
/>
</div>
);
}
You can also visualize a recording stored in your Foxglove organization:
import { FoxgloveViewer } from "@foxglove/embed-react";
function RecordingDataSourceExample() {
// Connect to a recording
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer
orgSlug="my-org-slug"
data={{
type: "recording",
recordingId: "rec_0dHVqSWhIQ8HUUJU",
projectId: "prj_0dX15xqpCP0yPYNq",
}}
/>
</div>
);
}
You can also connect to one or multiple .bag or .mcap files hosted on a remote server.
import { FoxgloveViewer } from "@foxglove/embed-react";
function LiveDataSourceExample() {
// Connect to a remote file or multiple
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer
orgSlug="my-org-slug"
data={{
type: "remote-file",
urls: [
// ensure cors and range headers are enabled where these are hosted
"https://www.example.com/sample-0.mcap",
"https://www.example.com/sample-1.mcap",
],
}}
/>
</div>
);
}
Another way to visualize a recording is by connecting to a device. You can provide the device ID or device name, along with the start and end times. Both start and end are required strings compatible with ISO8601/RFC3339.
Here are two examples of how to connect to a device:
import { FoxgloveViewer } from "@foxglove/embed-react";
function RecordingDataSourceExample() {
// Connect to a device by ID
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer
orgSlug="my-org-slug"
data={{
type: "device",
deviceId: "dev_0dHVqSWhIQ8HUUJU",
start: "2025-01-01T00:00:00Z",
end: "2025-02-01T00:00:00Z",
}}
/>
</div>
);
}
import { FoxgloveViewer } from "@foxglove/embed-react";
function RecordingDataSourceExample() {
// Connect to a device by name
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer
orgSlug="my-org-slug"
data={{
type: "device",
deviceName: "my-device",
start: "2025-01-01T00:00:00Z",
end: "2025-02-01T00:00:00Z",
}}
/>
</div>
);
}
Seeking
You can programmatically seek to a specific time using the seekPlayback method on the viewer ref. This accepts either a Time object ({ sec, nsec }) or a number of seconds. The value must be an absolute timestamp (e.g. seconds since the UNIX epoch), not a relative offset or duration.
If the viewer is not ready or the data source doesn't support seeking (for example, a live WebSocket connection), the call is a no-op. Out-of-range values are clamped to the time range of the current data source.
import { FoxgloveViewer, type FoxgloveViewerInterface } from "@foxglove/embed-react";
import { useRef } from "react";
function SeekExample() {
const viewerRef = useRef<FoxgloveViewerInterface>(null);
return (
<div style={{ height: "100vh" }}>
<button onClick={() => viewerRef.current?.seekPlayback(1742000000.5)}>
Seek to timestamp
</button>
<FoxgloveViewer ref={viewerRef} orgSlug="my-org-slug" data={...} />
</div>
);
}
Foxglove Extensions
Like the Foxglove app, the Foxglove embedded viewer will automatically install all your organization's published extensions. For a fully self-hosted setup, you can install extensions manually using the extensions prop documented here.
Layout Management
Layouts are stored in the browser's local storage. The storageKey is used to identify the layout. Any changes made by the user will be restored when the layout is selected again. Setting the force parameter to true overrides the user's local changes.
Restore a layout with persisted changes
The following example shows how to restore a layout from local storage. If no layout with the same storage key is found, a default layout will be created and saved to the local storage under the provided storage key.
import { FoxgloveViewer } from "@foxglove/embed-react";
function RestoreDefaultLayoutExample() {
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer
orgSlug="my-org-slug"
layout={{
storageKey: "layout-a",
}}
/>
</div>
);
}
You could also customize the fallback layout using the opaqueLayout parameter.
import { FoxgloveViewer } from "@foxglove/embed-react";
function RestoreCustomLayoutExample() {
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer
orgSlug="my-org-slug"
layout={{
storageKey: "layout-a",
opaqueLayout: {
/* Your layout data. This is an opaque JavaScript object, which should be parsed from a JSON layout file that was exported from the Foxglove app. */
},
}}
/>
</div>
);
}
Override local changes to a layout
The following example loads a layout from a JSON file and overrides the existing layout.
import { FoxgloveViewer, type SelectLayoutParams } from "@foxglove/embed-react";
import { useState } from "react";
function OverrideLayoutExample() {
const [layout, setLayout] = useState<SelectLayoutParams | undefined>(undefined);
return (
<div style={{ height: "100vh" }}>
<button
onClick={async () => {
const handles = await window.showOpenFilePicker({
multiple: false,
types: [
{
description: ".json",
accept: {
"application/json": [".json"],
},
},
],
});
const file = await handles[0].getFile();
setLayout({
storageKey: "layout-a",
opaqueLayout: JSON.parse(await file.text()),
force: true,
});
}}
>
Select layout
</button>
<FoxgloveViewer orgSlug="my-org-slug" layout={layout} />
</div>
);
}
Retrieve the current layout
Use getLayout to retrieve the currently configured layout, including any user modifications. The layout data can later be passed in to the opaqueLayout parameter.
import { FoxgloveViewer, type FoxgloveViewerInterface } from "@foxglove/embed-react";
import { useRef } from "react";
function GetLayoutExample() {
const viewerRef = useRef<FoxgloveViewerInterface>(null);
return (
<div style={{ height: "100vh" }}>
<button
onClick={() => {
const layoutData = await viewerRef.current?.getLayout();
// Save the layoutData
}}
>
Save layout
</button>
<FoxgloveViewer ref={viewerRef} ... />
</div>
);
}
Keybinding management
The embedded viewer supports customizing keyboard shortcuts. You can register custom keybindings that trigger actions in your parent application, override default viewer shortcuts, or suppress keys entirely.
Keybindings are declarative and idempotent. Each time you set them, the full array replaces the previous configuration. Passing an empty array restores the viewer's defaults.
Registering custom keybindings
You can register custom keybindings that call a handler function when pressed inside the viewer. This is useful for keyboard-intensive workflows such as triggering a snapshot, annotating data, or navigating between views.
The viewer checks custom keybindings before built-in bindings. If a custom keybinding uses the same key as a built-in one, the custom keybinding takes priority and suppresses the built-in one.
import { FoxgloveViewer } from "@foxglove/embed-react";
import type { Keybinding } from "@foxglove/embed";
import { useMemo } from "react";
function CustomKeybindingsExample() {
const keybindings = useMemo<Keybinding[]>(
() => [
{
key: "s",
modifiers: ["Control", "Shift"],
handler: () => {
console.log("Custom keybinding triggered: take-snapshot");
},
},
],
[],
);
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer orgSlug="my-org-slug" keybindings={keybindings} />
</div>
);
}
Overriding a built-in command's keybinding
To set a keybinding for a built-in command, provide a keybinding with the desired key combination and the command ID.
When multiple keybindings share the same command, the viewer uses the first one in the list and ignores the rest.
import { FoxgloveViewer } from "@foxglove/embed-react";
import type { Keybinding } from "@foxglove/embed";
import { useMemo } from "react";
function OverrideKeybindingsExample() {
const keybindings = useMemo<Keybinding[]>(
() => [
// Configure the "p" key to trigger the "toggle playback" command
{ key: "p", command: "player.togglePlayback" },
// Suppress the default Space keybinding
{ key: " ", command: "noop" },
],
[],
);
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer orgSlug="my-org-slug" keybindings={keybindings} />
</div>
);
}
For a full list of built-in commands, see the shortcuts documentation.
Suppressing a keybinding
Use the "noop" command to disable a keybinding without routing it to any action.
The viewer processes "noop" keybindings last, so they have lower priority than built-in overrides and custom keybindings.
<FoxgloveViewer keybindings={[{ key: " ", command: "noop" }]} />
Key matching
The format of the key string determines how keyboard events are matched:
| Format | Example | Matches against | Use case |
|---|---|---|---|
| Physical key code | "KeyW", "Digit1", "Space" | event.code | Spatial/positional shortcuts (for example, WASD camera) that stay in the same physical position regardless of keyboard layout |
| Character key | "p", "/", " " | event.key | Mnemonic shortcuts (for example, P for play) that follow the character printed on the keycap |
| Named key | "Escape", "Enter", "F1" | event.key | Control keys that are identical in both modes |
When the viewer compares bindings to detect collisions, it resolves both character and physical-code bindings to the same underlying key using the browser's Keyboard API. Two bindings that point to the same physical key count as a collision regardless of format.
import { FoxgloveViewer } from "@foxglove/embed-react";
import type { Keybinding } from "@foxglove/embed";
import { useMemo } from "react";
function KeyMatchingExample() {
const keybindings = useMemo<Keybinding[]>(
() => [
// Physical key code — always the key to the left of Backspace
{
key: "Equal",
modifiers: ["PrimaryModifier"],
handler: () => console.log("Zoom in (physical key)"),
},
// Character key — matches when the user types "s"
{
key: "s",
modifiers: ["PrimaryModifier"],
handler: () => console.log("Save (logical key)"),
},
],
[],
);
return (
<div style={{ height: "100vh" }}>
<FoxgloveViewer orgSlug="my-org-slug" keybindings={keybindings} />
</div>
);
}
Supported key and code values
The following table lists all supported character key values and their corresponding physical code values. Character keys match by event.key, while physical key codes (such as "KeyW", "Digit1", "Space") match by event.code.
Punctuation character values correspond to US QWERTY unmodified keys. On other keyboard layouts the same physical key may produce a different character — use the physical key code for layout-independent bindings.
View all supported key and code values
key | code |
|---|---|
| Letters | |
a | KeyA |
b | KeyB |
c | KeyC |
d | KeyD |
e | KeyE |
f | KeyF |
g | KeyG |
h | KeyH |
i | KeyI |
j | KeyJ |
k | KeyK |
l | KeyL |
m | KeyM |
n | KeyN |
o | KeyO |
p | KeyP |
q | KeyQ |
r | KeyR |
s | KeyS |
t | KeyT |
u | KeyU |
v | KeyV |
w | KeyW |
x | KeyX |
y | KeyY |
z | KeyZ |
| Digits | |
0 | Digit0 |
1 | Digit1 |
2 | Digit2 |
3 | Digit3 |
4 | Digit4 |
5 | Digit5 |
6 | Digit6 |
7 | Digit7 |
8 | Digit8 |
9 | Digit9 |
| Punctuation | |
- | Minus |
= | Equal |
[ | BracketLeft |
] | BracketRight |
\ | Backslash |
; | Semicolon |
' | Quote |
` | Backquote |
, | Comma |
. | Period |
/ | Slash |
| Editing & whitespace | |
Enter | Enter |
Escape | Escape |
Backspace | Backspace |
Tab | Tab |
| Space |
Delete | Delete |
Insert | Insert |
| Navigation | |
Home | Home |
End | End |
PageUp | PageUp |
PageDown | PageDown |
ArrowUp | ArrowUp |
ArrowDown | ArrowDown |
ArrowLeft | ArrowLeft |
ArrowRight | ArrowRight |
| Lock keys | |
CapsLock | CapsLock |
| Function keys | |
F1 | F1 |
F2 | F2 |
F3 | F3 |
F4 | F4 |
F5 | F5 |
F6 | F6 |
F7 | F7 |
F8 | F8 |
F9 | F9 |
F10 | F10 |
F11 | F11 |
F12 | F12 |
F13 | F13 |
F14 | F14 |
F15 | F15 |
F16 | F16 |
F17 | F17 |
F18 | F18 |
F19 | F19 |
F20 | F20 |
F21 | F21 |
F22 | F22 |
F23 | F23 |
F24 | F24 |
API reference
Refer to the @foxglove/embed-react documentation for the full API reference.