Skip to main content

TypeScript SDK guide

Secure context required

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 install --save @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.

info

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.

info

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.

info

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:

FormatExampleMatches againstUse case
Physical key code"KeyW", "Digit1", "Space"event.codeSpatial/positional shortcuts (for example, WASD camera) that stay in the same physical position regardless of keyboard layout
Character key"p", "/", " "event.keyMnemonic shortcuts (for example, P for play) that follow the character printed on the keycap
Named key"Escape", "Enter", "F1"event.keyControl keys that are identical in both modes
info

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.

info

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
keycode
Letters
aKeyA
bKeyB
cKeyC
dKeyD
eKeyE
fKeyF
gKeyG
hKeyH
iKeyI
jKeyJ
kKeyK
lKeyL
mKeyM
nKeyN
oKeyO
pKeyP
qKeyQ
rKeyR
sKeyS
tKeyT
uKeyU
vKeyV
wKeyW
xKeyX
yKeyY
zKeyZ
Digits
0Digit0
1Digit1
2Digit2
3Digit3
4Digit4
5Digit5
6Digit6
7Digit7
8Digit8
9Digit9
Punctuation
-Minus
=Equal
[BracketLeft
]BracketRight
\Backslash
;Semicolon
'Quote
`Backquote
,Comma
.Period
/Slash
Editing & whitespace
EnterEnter
EscapeEscape
BackspaceBackspace
TabTab

(space)

Space
DeleteDelete
InsertInsert
Navigation
HomeHome
EndEnd
PageUpPageUp
PageDownPageDown
ArrowUpArrowUp
ArrowDownArrowDown
ArrowLeftArrowLeft
ArrowRightArrowRight
Lock keys
CapsLockCapsLock
Function keys
F1F1
F2F2
F3F3
F4F4
F5F5
F6F6
F7F7
F8F8
F9F9
F10F10
F11F11
F12F12
F13F13
F14F14
F15F15
F16F16
F17F17
F18F18
F19F19
F20F20
F21F21
F22F22
F23F23
F24F24

API reference

Refer to the @foxglove/embed documentation for the full API reference.