TypeScript SDK guide
Foxglove must run in a secure context. Serve your application over HTTPS or from localhost.
You can embed Foxglove in your application or website to enable multi-modal data visualization and debugging within your application.
Installation
Start by installing the @foxglove/embed package.
- npm
- yarn
npm install --save @foxglove/embed
yarn add @foxglove/embed
Basic setup
The @foxglove/embed package provides the FoxgloveViewer class. This class creates and manages an iframe to embed Foxglove in your application.
To create a new FoxgloveViewer, you need to provide the parent element to which the iframe will be appended and the organization slug for signing in. Passing a slug ensures users sign into the correct organization before they can visualize anything. If you don't know your organization slug (or if you're using a fully self-hosted embedded viewer) you can pass undefined, allowing users to select the organization they want to sign into.
import { FoxgloveViewer } from "@foxglove/embed";
const viewer = new FoxgloveViewer({
// The parent element to append the iframe to.
parent: document.getElementById("container")!,
// The Foxglove organization the user should be signed into.
orgSlug: "my-org",
});
Events
You can listen to events from the embedded viewer using the addEventListener method.
viewer.addEventListener("ready", (e) => {
console.log("FoxgloveViewer is ready");
});
viewer.addEventListener("error", (e) => {
console.log("FoxgloveViewer error:", e.detail);
});
The following events are supported:
ready- The embedded viewer has been initialized and is ready to receive commands.error- An error occurred while executing a command.
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 setDataSource method.
The following example shows how to open a file picker and set the selected file as data source.
// Open a file picker and set the selected file as data source
window
.showOpenFilePicker({
multiple: false,
types: [
{
description: ".bag, .db3, .ulg, .ulog, .mcap",
accept: {
"application/octet-stream": [".bag", ".db3", ".ulg", ".ulog", ".mcap"],
},
},
],
})
.then(async (handles) => {
const file = await handles[0]!.getFile();
viewer.setDataSource({
type: "file",
file,
});
});
You can also connect to a live data source, such as a Foxglove WebSocket, a ROS Bridge, or a file hosted on a remote server.
// Connect to a Foxglove WebSocket
viewer.setDataSource({
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",
});
You can also visualize a recording stored in your Foxglove organization:
// Connect to a recording
viewer.setDataSource({
type: "recording",
recordingId: "rec_0dHVqSWhIQ8HUUJU",
projectId: "prj_0dX15xqpCP0yPYNq",
});
You can also connect to one or multiple .bag or .mcap files hosted on a remote server.
// Connect to a remote file or multiple
viewer.setDataSource({
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",
],
});
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:
// Connect to a device by ID
viewer.setDataSource({
type: "device",
deviceId: "dev_0dHVqSWhIQ8HUUJU",
start: "2025-01-01T00:00:00Z",
end: "2025-02-01T00:00:00Z",
});
// Connect to a device by name
viewer.setDataSource({
type: "device",
deviceName: "my-device",
start: "2025-01-01T00:00:00Z",
end: "2025-02-01T00:00:00Z",
});
Seeking
You can programmatically seek to a specific time using the seekPlayback method. 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.
// Seek using absolute seconds since epoch
viewer.seekPlayback(1742000000.5);
// Seek using an absolute Time object
viewer.seekPlayback({ sec: 1742000000, nsec: 500000000 });
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 installExtensions method 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.
viewer.selectLayout({
storageKey: "layout-a",
});
You could also customize the fallback layout using the opaqueLayout parameter.
// Load a layout from a JSON file
window
.showOpenFilePicker({
multiple: false,
types: [
{
description: ".json",
accept: {
"application/json": [".json"],
},
},
],
})
.then(async (handles) => {
const file = await handles[0]!.getFile();
const layoutData = JSON.parse(await file.text());
viewer.selectLayout({
storageKey: "layout-a",
opaqueLayout: layoutData,
});
});
Override local changes to a layout
The following example loads a layout from a JSON file and overrides the existing layout.
// Load a layout from a JSON file
window
.showOpenFilePicker({
multiple: false,
types: [
{
description: ".json",
accept: {
"application/json": [".json"],
},
},
],
})
.then(async (handles) => {
const file = await handles[0]!.getFile();
const layoutData = JSON.parse(await file.text());
viewer.selectLayout({
storageKey: "layout-a",
opaqueLayout: layoutData,
force: true,
});
});
Retrieve the current layout
Use getLayout to retrieve the currently configured layout, including any user modifications:
const layoutData = await viewer.getLayout();
// Save the layoutData
The layout data can later be passed in to the opaqueLayout parameter:
viewer.selectLayout({
storageKey: "layout-a",
opaqueLayout: layoutData,
force: true,
});
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 call to setKeybindings fully replaces the previous configuration. Calling with 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 combination as a built-in one, the custom keybinding takes priority and suppresses the built-in one.
const viewer = new FoxgloveViewer({
parent: document.getElementById("container")!,
orgSlug: "my-org",
keybindings: [
{
key: "s",
modifiers: ["Control", "Shift"],
handler: () => {
console.log("Custom keybinding triggered: take-snapshot");
},
},
],
});
You can also update keybindings after initialization using setKeybindings. Calling with an empty array restores the viewer's default keybindings.
viewer.setKeybindings([
{
key: "s",
modifiers: ["Control", "Shift"],
handler: () => {
console.log("Custom keybinding triggered: take-snapshot");
},
},
]);
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.
const viewer = new FoxgloveViewer({
parent: document.getElementById("container")!,
orgSlug: "my-org",
keybindings: [
// Configure the "p" key to trigger the "toggle playback" command
{ key: "p", command: "player.togglePlayback" },
// Suppress the default Space keybinding
{ key: " ", command: "noop" },
],
});
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.
viewer.setKeybindings([{ 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.
viewer.setKeybindings([
// 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)"),
},
]);
Combining overrides and custom keybindings
viewer.setKeybindings([
// Configure the "p" key to trigger the "toggle playback" command
{ key: "p", command: "player.togglePlayback" },
// Suppress the default Space keybinding
{ key: " ", command: "noop" },
// Custom: host-owned handler
{ key: "s", modifiers: ["Control", "Shift"], handler: () => console.log("Screenshot!") },
{ key: "h", handler: () => console.log("Go home") },
]);
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 documentation for the full API reference.