# Foxglove Documentation (Full) > This file contains the complete Foxglove product documentation for LLM consumption. > For a concise overview, see llms.txt > For REST API reference, see /api/llms.txt and /api/llms-full.txt Base URL: https://docs.foxglove.dev --- # Overview > Introduction and getting started ## Foxglove Documentation **URL**: /docs **Description**: Foxglove is a purpose-built platform that empowers robotics teams to visually debug robots, build reliable autonomy, and scale their development. Foxglove is a platform to record, upload, organize, and visualize multimodal log data such as time series, text logs, video, 3D, maps, and more. It is most often used in hardware, robotics, and physical AI. ![Foxglove Visualization](/img/docs/introduction/hero@2x.webp) ## Foxglove components Foxglove provides a collection of tools that together support the entire data lifecycle for robotics teams. | Component | What it is | Where it runs | How to access it | | --- | --- | --- | --- | | **Foxglove app** | The main app through which Foxglove functionality can be accessed: visualization and debugging (panels, layouts, and playback), data organization, search, settings, and more. | In a desktop app or in the browser | | | **[Data Platform](/docs/data)** | Hosted storage, indexing, and search for your robotics data — recordings, devices, and events. This is the backbone of all collaboration, organization, and search functionality in Foxglove. |

Runs on [Primary Sites](/docs/data/primary-sites), available in multiple flavors:

| | | **The software that runs on robots** | | Your robots, though SDK code can also run on your local machine | | ## Workflows Foxglove supports all aspects of the multimodal data lifecycle: **Record** - Record logs in the open source [MCAP](https://mcap.dev/) file format using the [SDK](/docs/sdk) (C++, Python, and Rust) **Ingest** - Automatically upload recorded files (ROS 1 bag and MCAP) through [Foxlet](/docs/fleet/foxlet) running on your robot - [Import](/docs/data/importing-data) recordings into the Data Platform using the Foxglove app **Organize** - Index imported [data recordings](/docs/data/recordings) by device, time, and topic - Annotate logs with [metadata and events](/docs/data/events) for easier search and discovery - [Search](/docs/data/search) for interesting moments across all your recordings - Share recordings with team members to triage incidents and analyze the root cause of an issue **Visualize** - Connect to [live](/docs/visualization/connecting/live) robot data to visualize or [teleoperate](/docs/visualization/panels/teleop) using the Foxglove app - Open a variety of [supported formats](/docs/visualization/connecting/local-data#supported-formats) (MCAP, ROS Bag, ULog, etc) locally using the Foxglove app - Visualize [recordings in the cloud](/docs/visualization/connecting/cloud-data), from your organization's Data Platform or remote HTTP servers - Configure [panels](/docs/visualization/panels) to understand how your robots sense, think, and act - Create shared [layouts](/docs/visualization/layouts) to tackle repeated visualization and debugging tasks **Integrate** - Configure [webhooks](/docs/webhooks) to react to data updates such as a new recording, event creation, or device update - Chart telemetry from your recordings on [Grafana](/docs/grafana) dashboards - Automate aspects of your data pipeline using the [API](/api) and [CLI](/docs/cli) ## Command palette The command palette provides quick access to actions and navigation across the app. Open it with `Ctrl` + `K` (Windows and Linux) or `Cmd` + `K` (macOS) to search for commands by name or keyword. Use the command palette to: - **Open data sources** — Open local files, start a live connection, or open a recording - **Navigate** — Jump to pages like the dashboard, settings, devices, recordings, events, or timeline - **Switch layouts** — Browse and switch between recent, personal, and organization layouts - **Access help** — Open the documentation, view version info, or launch the getting started guide Start typing to filter the list. Some commands have sub-menus that expand when selected. For example, selecting "Change layout" drills down into a searchable list of available layouts. Use the mouse or arrow keys to navigate, click a list entry or press `Enter` to select, and `Esc` or click outside the list to close the palette. Available commands are context-aware, meaning some only appear on certain pages. ## Links and resources - [Create a free account](https://app.foxglove.dev/signup) - [Download the desktop app](https://foxglove.dev/download) (for Linux, Windows, and macOS) - [Browse tutorials](https://foxglove.dev/blog?topic=tutorial) --- ## Settings **URL**: /docs/settings **Description**: Use the Settings page to configure preferred time zone, timestamp format, and more. Additionally, admins can modify the subscription plan, invite and manage organization members, and configure organization-wide settings like sites and custom device properties. ## User settings Use the [Settings page](https://app.foxglove.dev/~/settings/user/general) page to configure your preferred time zone, timestamp format, and more. ### General | field | description | | --- | --- | | **Color scheme** | Choose between light or dark mode, or follow your OS settings | | **Timestamp format** | Formatting used to display timestamps (12-hour, 24-hour, RFC 3339, or seconds) | | **Language** | App language | | **Open links in** (Web app only) | Choose whether [shareable links](/docs/visualization/shareable-links) are opened in the web app or desktop app | ### Time zone Visit the [Dashboard](https://app.foxglove.dev/~/dashboard) and use the dropdown menu in the top bar to change the time zone used for displaying timestamps. ![time zone menu](/img/docs/visualization/timezone-menu@2x.webp) If your organization admin has set an organization [default time zone](#org-general), it appears at the top of the menu as **Organization default: ``** and applies to anyone who hasn't picked their own. Selecting **Detected** or any other zone overrides the org default for your account; selecting **Organization default** at any time switches you back to following it. ### Extensions Visit the [Extensions](https://app.foxglove.dev/~/settings/user/extensions) settings page to select the [Foxglove extensions](/docs/extensions) you want to install locally. :::note Administrators can also [publish extensions](#extensions-1) to all users of their organization. ::: Each row has an **Enabled** toggle that turns the extension off for you on this device, hiding its contributions from your session. The setting is saved in your browser and does not sync across devices. The toggle is only available on developer seats. If you are switched to a basic seat, your disabled selections are preserved but ignored — org extensions activate normally — and they re-engage if you are upgraded back to developer. ### Connected accounts Visit the [Connected accounts](https://app.foxglove.dev/~/settings/user/connected-accounts) settings page to link or disconnect external accounts. Link your Slack account to use the [Slack integration](/docs/agents/slack). ### Desktop app In the desktop app, visit _Settings > Desktop_ to configure the following settings: | field | description | | --- | --- | | **Automatically install updates** | Whether the app should automatically check for and install updates (macOS and Windows only) | | [**ROS_PACKAGE_PATH**](https://wiki.ros.org/ROS/EnvironmentVariables#ROS_PACKAGE_PATH) | Paths to search for ROS packages (local file paths or `package://` URLs); separate paths with standard OS path separator (e.g. ':' on Unix).
_Example_: `/path/to/workspace/src:/opt/ros/noetic/share`
_Note_: The `ROS_PACKAGE_PATH` environment variable will also be used. | ### Standalone license If your desktop installation uses standalone mode, see [Standalone license activation](/docs/standalone-license) for setup and activation instructions. ## Organization settings Admins can use the [Organization settings](https://app.foxglove.dev/~/settings/general) to modify the subscription plan, invite and manage organization members, and configure organization-wide settings like sites and custom device properties. ### General {#org-general} On the [General settings page](https://app.foxglove.dev/~/settings/general), admins can: - **Organization name** – Edit the organization name - **Default time zone** – Set the time zone used to render timestamps for organization members who haven't picked their own. Members can still override it from their personal [Time zone](#time-zone) menu; the override persists across sessions. - **Subscription** – View and modify plan details - **Approved domains** – New users signing in with email addresses from these domains are automatically added to the organization - **Usage** – Shows data storage usage - **Danger zone** – Delete the organization ### Members On the [Members settings page](https://app.foxglove.dev/~/settings/members), admins can: - View a list of all organization members (name, email address, last seen, [role](/docs/security/roles), [seat type](/docs/security/seat-types)) - Invite or remove organization members - Modify organization members' roles ("admin", "user") - Change a member's [seat type](/docs/security/seat-types) ("developer", "basic") :::note All organization members of Free plan organizations have the admin role. ::: ### Projects On the [Projects settings page](https://app.foxglove.dev/~/settings/projects), admins can: - Create new [Projects](/docs/projects) - Manage Project access - Rename and delete Projects ### Sites On the [Sites settings page](https://app.foxglove.dev/~/settings/sites), admins can: - Look up the status, name, and ID of their Foxglove- and self-hosted [Primary Sites](/docs/data/primary-sites) - Look up the status, name, and ID of their on-premises [Edge Sites](/docs/data/edge-sites) - Add Foxglove-hosted Primary Sites - Add Edge Sites ### Devices On the [Devices settings page](https://app.foxglove.dev/~/settings/devices), admins can set predefined metadata fields (i.e. [properties](/docs/data/devices#properties)) for the whole organization to associate with their devices. ### API keys On the [API keys settings page](https://app.foxglove.dev/~/settings/apikeys), admins can: - Generate API keys for accessing the [Foxglove API](/api) - Enable, disable, or permanently delete generated API keys - Configure the API key's capabilities per data resource (e.g. read-only for devices, read and write for recordings, etc.) ### Audit logs On the [Audit logs settings page](https://app.foxglove.dev/~/settings/audit-logs), Enterprise organization admins can review organization activity. The feed shows who performed an action, what they did, and when. See [Audit logs](/docs/audit-logs) for details. ### SSO On the [SSO settings page](https://app.foxglove.dev/~/settings/sso), admins can configure Okta SSO OIDC settings like the Okta domain, client ID, and more. Foxglove currently supports both [Microsoft](https://foxglove.dev/blog/announcing-microsoft-sign-in) and [Okta SSO](/docs/security/sso). ### Search On the [Search settings page](https://app.foxglove.dev/~/settings/search), admins can manage how [visual search](/docs/data/search#visual-search) indexing is configured. They can: - Manage which projects have visual search indexing enabled - Configure which topics are indexed for newly uploaded recordings - See the time range of recording data that has been indexed ### Webhooks On the [Webhooks settings page](https://app.foxglove.dev/~/settings/webhooks), admins can manage [Webhooks](/docs/webhooks) to subscribe to notifications about events that occur within Foxglove. ### Slack On the [Slack settings page](https://app.foxglove.dev/~/settings/slack), admins can add [Foxglove to their Slack workspace](/docs/agents/slack). Once it's installed, anyone in the workspace can ask Foxglove about their organization's data. ### Extensions On the [Extensions settings page](https://app.foxglove.dev/~/settings/extensions), admins can install Foxglove [extensions](/docs/extensions) across the organization to better support the team's unique workflows. Enabled extensions are automatically installed for all signed-in organization members. Use the `foxglove` CLI to [write](/docs/extensions#writing-an-extension) and [publish](/docs/extensions/publish) Foxglove extensions. ### Insights On the [Insights settings page](https://app.foxglove.dev/~/settings/insights), admins can understand how their team members are leveraging the platform. #### Import volume Track the net and cumulative volume of data your Foxglove organization has imported week-over-week: ![imports](/img/docs/organization-setup/insights/imports@2x.webp) Understand your team's data storage bill, whether you are nearing your plan's data limit, and the time periods during which the team imported the most data. --- ## Standalone License Activation **URL**: /docs/standalone-license **Description**: Activate Foxglove desktop in standalone mode for offline and air-gapped environments. Use standalone mode to run Foxglove desktop with a standalone license instead of signing in with an account. ## Before you begin Standalone mode is intended for offline and air-gapped workflows. In standalone mode: - Cloud-based pages and features are unavailable - Users do not sign in with an account - Users cannot sign out to switch accounts - [Preloaded layouts and extensions](#preload-layouts-and-extensions-optional) can be used but cannot be modified or removed To show the **Activate License** link on the welcome page, create a `standalone` folder in your Foxglove app data directory. After you create the folder, start Foxglove desktop and select **Activate License** on the welcome page. ## Activate online Use this flow when the machine can reach the internet. 1. Open **Activate License** from the welcome page. 2. Enter your license key. 3. Select **Activate**. ## Activate offline Use this flow for offline or air-gapped machines. 1. On the offline machine, open **Activate License**. 2. Select **use offline activation**. 3. Enter your license key and select **Generate .req file**. 4. Transfer the generated `.req` file to an online machine. 5. On the online machine, open [licensing.foxglove.dev](https://licensing.foxglove.dev/). 6. Upload the `.req` file and download the generated `.lic` file. 7. Transfer the `.lic` file back to the offline machine. 8. Using Foxglove on the offline machine, select **Import .lic file** and choose the `.lic` file. ## Preload layouts and extensions (optional) You can pre-configure standalone installations by placing files in subdirectories under `standalone`: ```text .../Foxglove/ └── standalone/ ├── layouts/ │ └── My Layout.json └── extensions/ └── mycompany.example-extension-1.0.0.foxe ``` - Put exported layout `.json` files in `standalone/layouts/` - Put extension `.foxe` files in `standalone/extensions/` Foxglove loads these files when a valid standalone license is active. ## Need help? Contact [support@foxglove.dev](mailto:support@foxglove.dev). --- ## Pricing **URL**: /docs/pricing **Description**: Foxglove offers Free, Pro, and Enterprise plans to fit teams of every size. Compare features, usage limits, and find the right plan for your workflow. Foxglove offers pricing plans for teams of any size, from individual developers to large organizations. Visit our [Pricing](https://foxglove.dev/pricing) page for an overview of the available plans. ## Managing your plan Organization admins can view and modify your plan from the [Billing settings](https://app.foxglove.dev/~/settings/billing) page. ## Tiered pricing Each plan includes a set amount of managed infrastructure, users, and devices. On Pro and Enterprise plans, additional usage is billed according to the marginal pricing tiers below. On Free and Academic plans, included usage is a hard limit. Prepaid committed-use credits are available on the Enterprise plan. To learn more, please [contact sales](https://foxglove.dev/contact?reason=sales). ## Data platform ### Included usage | Dimension | **Free** | **Pro / Enterprise** | **Academic** | | ------------- | --------- | -------------------- | ------------ | | **Storage** | 10 GB | 1 TB | 1 TB | | **Query** | 1 hour/mo | 1 hour/mo | 10 hours/mo | | **Indexing** | 100 GB/mo | 1 TB/mo | 1 TB/mo | | **Bandwidth** | 100 GB/mo | 100 GB/mo | 1 TB/mo | ### Storage Storage is priced per TB-month, calculated based on your average hourly data during the billing period. | Range | Rate | | ------------- | --------- | | First 1 TB | Included | | 1 - 10 TB | $50/TB/mo | | 10 - 100 TB | $40/TB/mo | | 100 TB - 1 PB | $30/TB/mo | | 1+ PB | $26/TB/mo | ### Query Query is calculated as the sum of execution time for all search, stream, and visualization data requests. | Range | Rate | | ---------------- | ---------- | | First 1 hour | Included | | 1 - 10 hours | $3.65/hour | | 10 - 100 hours | $2.75/hour | | 100 - 500 hours | $2.35/hour | | 500 - 1000 hours | $2.15/hour | | 1000+ hours | $2.05/hour | ### Indexing Indexing includes any data uploaded to Foxglove, or data indexed with [BYO Storage](/docs/data/primary-sites#foxglove-cloud-bring-your-own-storage). | Range | Rate | | ------------- | -------- | | First 1 TB | Included | | 1 - 10 TB | $35/TB | | 10 - 100 TB | $28/TB | | 100 TB - 1 PB | $24/TB | | 1+ PB | $22/TB | ### Bandwidth Bandwidth (egress) is charged for all data requests. | Range | Rate | | -------------- | -------- | | First 100 GB | Included | | 100 GB - 10 TB | $150/TB | | 10 - 100 TB | $135/TB | | 100 TB - 1 PB | $125/TB | | 1+ PB | $115/TB | ## Fleet ### Included usage | Dimension | Free | Pro / Enterprise | Academic | | ----------------- | ---- | ----------------- | -------- | | **Devices** | 5 | 5 | 10 | | **Remote access** | — | 300 min/device/mo | — | ### Devices Devices are only charged if they were active during the billing period. Additional devices are charged at $20/device/mo. ### Remote access Each device includes 300 remote access minutes per month (minutes are not pooled across devices). Additional minutes are charged at $0.05/min. ## AI agents ### Included usage Included usage is granted per user and is not pooled across users. | Dimension | **Free** | **Pro / Enterprise** | **Academic** | | ------------ | ---------- | -------------------- | ------------ | | **AI usage** | $5/user/mo | $5/user/mo | $5/user/mo | ### AI usage AI agent usage is measured by the tokens consumed by Large Language Model (LLM) requests, across both the agent sidebar and the Slack integration. When a user reaches their limit, behavior depends on the plan: - **Free and Academic**: included usage is a hard limit. The user is blocked from further AI agent usage until the monthly cycle renews, with no overage billing. - **Pro**: the user keeps working, and the organization is billed in arrears for that user's overage at the rate below. Overage is charged per user, so users still within their allowance are never billed. - **Enterprise**: AI overage is credit-gated rather than billed in arrears. It draws from the organization's prepaid credits, and once those run out, users are blocked until more are added. Usage includes input tokens (prompts and context), output tokens (AI responses), and prompt caching operations. | Range | Rate | | ---------------- | -------------------------- | | First $5/user/mo | Included | | Additional usage | Provider cost + $1.00/MTok | Provider costs vary by model and are passed through at cost. The Foxglove fee is a flat $1.00 per million tokens across all token types. --- ## Projects **URL**: /docs/projects **Description**: Projects are collections of resources within an organization. Projects provide data isolation and access control to collections of resources within an organization. Every [recording](/docs/data/recordings), [device](/docs/data/devices), and [event](/docs/data/events) belongs to one Project. ## What can you do with Projects? Projects enable you to organize data and control access within your organization. - Separate production and development workflows - Organize data by team or customer - Share subsets of data with specific members - Restrict third parties or external contractors to specific data within your Foxglove organization ## Creating a Project Every organization starts with a single shared Project that all members can access. Admins of enterprise organizations can create additional Projects from the [Projects page](https://app.foxglove.dev/~/settings/projects). :::warning Before creating a second Project, ensure all programmatic uploads specify a Project or existing device. See below. ::: ## Access control A Project can either be invite-only or shared. Invite-only Projects require explicit assignments for non-admin members, while shared Projects are available to all members of the organization. Admins can assign members to Projects via the [Members page](https://app.foxglove.dev/~/settings/members) and change the invite-only status of a Project via the [Projects page](https://app.foxglove.dev/~/settings/projects). - Members with the **user** role can only see and access shared Projects and Projects they are assigned to. - Members with the **admin** role can access all Projects. - API Keys can access all Projects. Projects are isolated. Data in one Project cannot be accessed from or moved to another Project. :::note [Pending imports](/docs/data/importing-data#track-pending-imports) not yet associated with a Project are only visible to admins via the [Pending Imports page](https://app.foxglove.party/~/settings/pending-imports). This can happen when uploads don't specify a device or Project ID — for example, at self-hosted sites where metadata isn't included during the upload. To ensure non-admin members can track their imports, always include a device or Project ID. ::: ## Associating data We recommend always providing a Project ID when interacting with Foxglove programmatically. For organizations with multiple Projects, a Project ID is required when creating devices or uploading recordings without an existing device. Project IDs can be found on the [Projects page](https://app.foxglove.dev/~/settings/projects). ### API requests Endpoints that create or list Project scoped resources accept a `projectId` field to specify the Project. See [API docs](/api) for more information. ### Self-hosted Primary Sites Providing a `foxglove_project_id` record in object metadata or a `projectId` in MCAP metadata associates the upload with a Project. See [Site docs](/docs/data/primary-sites/manage-data#adding-metadata-to-imports) for more information. Self-hosted Primary Sites support Projects as of [version 0.0.69](/docs/data/primary-sites/upgrading). ### Web and desktop apps Uploading files and creating devices from the Foxglove dashboard automatically associates them with the current Project. ## Project vs Organization scoped resources Every device, event, and recording belongs to a single Project. Data in one Project cannot be accessed from another Project. Resources at the organization level work across all Projects. | Resource | Project | Organization | | ----------------- | ------- | ------------ | | Devices | ✓ | | | Events | ✓ | | | Recordings | ✓ | | | API Keys | | ✓ | | Custom Properties | | ✓ | | Extensions | | ✓ | | Layouts | | ✓ | | Sites | | ✓ | | Webhooks | | ✓ | --- ## CLI **URL**: /docs/cli **Description**: The foxglove CLI tool helps you import, export, and interact with your data from the command line. The [Foxglove CLI tool](https://github.com/foxglove/foxglove-cli) helps you import, export, and interact with your data from the command line. ### Installation Download the latest release from your command line. | OS | architecture | command | | --- | --- | --- | | linux | | | amd64 | `curl -L https://github.com/foxglove/foxglove-cli/releases/latest/download/foxglove-linux-amd64 -o foxglove && chmod +x foxglove` | | | arm64 | `curl -L https://github.com/foxglove/foxglove-cli/releases/latest/download/foxglove-linux-arm64 -o foxglove && chmod +x foxglove` | | macos | | | any | Using [homebrew](https://brew.sh/): `brew install foxglove-cli` | | | amd64 | `curl -L https://github.com/foxglove/foxglove-cli/releases/latest/download/foxglove-macos-amd64 -o foxglove && chmod +x foxglove` | | | arm64 | `curl -L https://github.com/foxglove/foxglove-cli/releases/latest/download/foxglove-macos-arm64 -o foxglove && chmod +x foxglove` | | windows | | | amd64 | `curl -L https://github.com/foxglove/foxglove-cli/releases/latest/download/foxglove-windows-amd64.exe -o foxglove.exe` | | | arm64 | `curl -L https://github.com/foxglove/foxglove-cli/releases/latest/download/foxglove-windows-arm64.exe -o foxglove.exe` | After downloading the file, you may want to move it to a location in your system's `PATH` to make it more easily executable. To install a specific release, see the [releases page](https://github.com/foxglove/foxglove-cli/releases). ### Getting started Before interacting with your data, you must first authenticate with your [Foxglove account](https://app.foxglove.dev/signup): ```bash foxglove auth login ``` For a list of all available CLI commands: ```bash foxglove -h ``` Enable shell autocompletion for subcommands and parameters (`bash`, `zsh`, `fish`, and `PowerShell` are supported): ```bash foxglove completion -h ``` ### Actions | | Create | List | Delete | | --- | --- | --- | --- | | **Projects** | | `foxglove projects list` | | | **Devices** | `foxglove devices add --name "my device" --project-id prj_abcd1234` | `foxglove devices list` | `foxglove devices delete --name "my device"` | | **Recordings** | `foxglove data import ~/data/bags/gps.bag --device-id dev_drpLqjBZYUzus3gv` | `foxglove recordings list` | | | **Events** | `foxglove events add --device-id dev_flm75pLkfzUBX2DH --timestamp 2022-01-01T12:00:00Z --duration-nanos 0 --metadata 'someKey:someValue'` | `foxglove events list` | | | **Extensions** | `foxglove extensions publish ./my-extension.1.0.0.foxe` | `foxglove extensions list` | `foxglove extensions unpublish ext_BsGXKGsZ9c4WQF1` | ### Visualizing data The Foxglove CLI tool cannot directly open data in the Foxglove app, but the app binary can [accept data source parameters when opened via the command line](/docs/visualization/shareable-links). --- ## Audit logs **URL**: /docs/audit-logs **Description**: Review organization activity from the Audit logs page. Available to org admins on Enterprise plans. Audit logs let organization [admins](/docs/security/roles) review who did what in your Foxglove organization, and when. Use them to investigate unexpected layout changes, track membership and access updates, or confirm whether an action was intentional. :::note Audit logs are available only to organization admins on Enterprise plans. If you don't see **Audit logs** under organization settings, check your [plan](/docs/pricing) or ask an admin. ::: ## Open audit logs 1. Sign in as an organization admin on an Enterprise plan. 2. Open [Settings → Audit logs](https://app.foxglove.dev/~/settings/audit-logs). The page lists recent organization events in reverse chronological order. ## What each event shows Each row includes: | Field | Description | | --------- | ------------------------------------------------------------------------------ | | **Time** | When the event occurred | | **Event** | The action name (for example `layout.delete` or `device.update_remote_access`) | | **Actor** | Who performed the action — an organization member, an API key, or the system | Select a row to open details, including event metadata such as the name of a deleted layout or other action-specific fields. ## Filter events Use the filters at the top of the page to narrow the feed: - **Event** — limit results to a specific action name - **From** / **To** — limit results to a date range ## Examples of recorded activity Audit logs cover a range of organization actions, including: - Layout deletions and restores - Remote Access enable and disable on devices - Member invitations, removals, and seat-type changes - API key create, edit, and delete - Project create, delete, and membership changes - Device edits and device token changes - Recording and import deletions ## Links and resources - [Settings](/docs/settings) - [Roles](/docs/security/roles) - [Pricing](/docs/pricing) --- # Getting Started > Quick start guides by framework ## Python **URL**: /docs/getting-started/python **Description**: Log data from Python into Foxglove Connect directly to Foxglove or play back local data recorded with the Python SDK. ## Install the SDK The SDK is distributed through [PyPI](https://pypi.org/project/foxglove-sdk/). You can get started with a new project or use Foxglove from a [notebook](/docs/notebook). The exact installation will depend on your package manager; here's an example using [uv](https://docs.astral.sh/uv/getting-started/installation/). ```bash uv init example cd example uv add foxglove-sdk ``` ## Live data ### Log messages from Python Edit the generated `main.py`: ```python import foxglove import time foxglove.start_server() while True: foxglove.log("/hello", {"time": time.time()}) time.sleep(0.03) ``` #### Run the script Run the script to start logging. Following the uv example above, you can run the following command from the "example" directory: ```bash uv run python main.py ``` ### Connect In Foxglove, select "Open connection" from the dashboard or left-hand menu. ![Select open connection](/img/docs/getting-started/open-connection@2x.webp) Select "Foxglove WebSocket" in the "Open a new connection" dialog, then click "Open" to accept the default connection string: ![Foxglove WebSocket dialog](/img/docs/getting-started/foxglove-websocket@2x.webp) ## Local data ### Recording data to a file The SDK can also log data to disk. Let's augment the example above to also produce an [MCAP](https://mcap.dev/) file which can later be opened in Foxglove: ```python import foxglove import time foxglove.start_server() # highlight-start # Keep a reference to the mcap. It will automatically close the file when the program exits, but # we could also close it manually with `mcap.close()`. with foxglove.open_mcap("example.mcap"): # highlight-end while True: foxglove.log("/hello", {"time": time.time()}) time.sleep(0.03) ``` ### Viewing data from a file ## Links and resources - [SDK documentation](/docs/sdk?lang=python) with a more detailed [example](/docs/sdk/example?lang=python) - [Jupyter notebook integration](/docs/notebook) - [Python reference](https://foxglove-sdk-api-docs.pages.dev/python) - [Source code](https://github.com/foxglove/foxglove-sdk) with additional examples --- ## Rust **URL**: /docs/getting-started/rust **Description**: Log data from Rust into Foxglove Connect directly to Foxglove or play back local data recorded with the Rust SDK. ## Install the SDK If you want to try Foxglove with a new project, you can use these steps: ```bash cargo new example --bin cd example cargo add foxglove ``` ## Live data ### Log messages from Rust Add the following to `main.rs`: ```rust use std::{thread, time}; #[derive(foxglove::Encode)] struct Message { elapsed: f64, } fn main() { foxglove::WebSocketServer::new() .start_blocking() .expect("Server failed to start"); let start = time::SystemTime::now(); loop { foxglove::log!( "/hello", Message { elapsed: start .elapsed() .expect("clock failed to elapse") .as_secs_f64() } ); thread::sleep(time::Duration::from_millis(30)); } } ``` #### Run the example From the project directory: ```bash cargo run ``` ### Connect In Foxglove, select "Open connection" from the dashboard or left-hand menu. ![Select open connection](/img/docs/getting-started/open-connection@2x.webp) Select "Foxglove WebSocket" in the "Open a new connection" dialog, then click "Open" to accept the default connection string: ![Foxglove WebSocket dialog](/img/docs/getting-started/foxglove-websocket@2x.webp) ## Local data ### Recording data to a file The SDK can also log data to disk. Let's augment the example above to also produce an [MCAP](https://mcap.dev/) file which can later be opened in Foxglove: ```rust //highlight-start use std::{ sync::{ atomic::{AtomicBool, Ordering}, Arc, }, thread, time, }; //highlight-end #[derive(foxglove::Encode)] struct Message { elapsed: f64, } fn main() { foxglove::WebSocketServer::new() .start_blocking() .expect("Server failed to start"); //highlight-start // Keep a reference to the writer. It'll automatically flush and close when it's dropped, // or we could call `.close()` to close it manually. // We use a named variable here to ensure it's dropped only at the end of the scope. let _writer = foxglove::McapWriter::new() .create_new_buffered_file("example.mcap") .expect("Failed to create writer"); // Log until interrupted. We need a ctrlc handler here to ensure // that main() exits cleanly, dropping _writer before ending the process. let done = Arc::new(AtomicBool::default()); ctrlc::set_handler({ let done = done.clone(); move || { done.store(true, Ordering::Relaxed); } }) .expect("Failed to set SIGINT handler"); let start = time::SystemTime::now(); while !done.load(Ordering::Relaxed) { //highlight-end foxglove::log!( "/hello", Message { elapsed: start .elapsed() .expect("clock failed to elapse") .as_secs_f64() } ); thread::sleep(time::Duration::from_millis(30)); } } ``` ### Viewing data from a file ## Links and resources - [SDK documentation](/docs/sdk?lang=rust) with a more detailed [example](/docs/sdk/example?lang=rust) - [Rust reference](https://docs.rs/foxglove/latest/foxglove) - [Source code](https://github.com/foxglove/foxglove-sdk) --- ## C++ **URL**: /docs/getting-started/cpp **Description**: Log data from C++ into Foxglove Connect directly to Foxglove or play back local data recorded with the C++ SDK. ## Install the SDK ## Live data ### Log messages from C++ ```cpp #include #include #include #include #include #include using namespace std::chrono_literals; int main(int argc, const char *argv[]) { foxglove::WebSocketServerOptions options; auto serverResult = foxglove::WebSocketServer::create(std::move(options)); if (!serverResult.has_value()) { std::cerr << foxglove::strerror(serverResult.error()) << '\n'; return 1; } auto server = std::move(serverResult.value()); auto channel = foxglove::RawChannel::create("/hello", "json").value(); auto start = std::chrono::steady_clock::now(); // Log until interrupted static std::function sigint_handler; std::atomic_bool done = false; sigint_handler = [&] { done = true; }; std::signal(SIGINT, [](int) { if (sigint_handler) { sigint_handler(); } }); while (!done) { auto dur = std::chrono::steady_clock::now() - start; float elapsed_seconds = std::chrono::duration(dur).count(); std::string msg = "{\"elapsed\": " + std::to_string(elapsed_seconds) + "}"; channel.log(reinterpret_cast(msg.data()), msg.size()); std::this_thread::sleep_for(33ms); } return 0; } ``` #### Run the example If you're using something like the CMake configuration above, you can create a `build` directory and run the following. ```bash cd build cmake .. cmake --build . ./my_program ``` ### Connect In Foxglove, select "Open connection" from the dashboard or left-hand menu. ![Select open connection](/img/docs/getting-started/open-connection@2x.webp) Select "Foxglove WebSocket" in the "Open a new connection" dialog, then click "Open" to accept the default connection string: ![Foxglove WebSocket dialog](/img/docs/getting-started/foxglove-websocket@2x.webp) ## Local data ### Recording data to a file The SDK can also log data to disk. Let's augment the example above to also produce an [MCAP](https://mcap.dev/) file which can later be opened in Foxglove: ```cpp #include // highlight-start #include // highlight-end #include #include #include #include #include using namespace std::chrono_literals; int main(int argc, const char *argv[]) { foxglove::WebSocketServerOptions options; auto serverResult = foxglove::WebSocketServer::create(std::move(options)); if (!serverResult.has_value()) { std::cerr << foxglove::strerror(serverResult.error()) << '\n'; return 1; } auto server = std::move(serverResult.value()); auto channel = foxglove::RawChannel::create("/hello", "json").value(); auto start = std::chrono::steady_clock::now(); // highlight-start foxglove::McapWriterOptions mcap_options; mcap_options.path = "example.mcap"; auto writerResult = foxglove::McapWriter::create(mcap_options); if (!writerResult.has_value()) { std::cerr << foxglove::strerror(writerResult.error()) << '\n'; return 1; } // Keep a reference to the writer. It will be closed when the writer is // destroyed, or we could call `.close()` to close it manually. auto writer = std::move(writerResult.value()); // highlight-end // Log until interrupted static std::function sigint_handler; std::atomic_bool done = false; sigint_handler = [&] { done = true; }; std::signal(SIGINT, [](int) { if (sigint_handler) { sigint_handler(); } }); while (!done) { auto dur = std::chrono::steady_clock::now() - start; float elapsed_seconds = std::chrono::duration(dur).count(); std::string msg = "{\"elapsed\": " + std::to_string(elapsed_seconds) + "}"; channel.log(reinterpret_cast(msg.data()), msg.size()); std::this_thread::sleep_for(33ms); } return 0; } ``` ### Viewing data from a file ## Links and resources - [SDK documentation](/docs/sdk?lang=cpp) with a more detailed [example](/docs/sdk/example?lang=cpp) - [C++ reference](https://foxglove-sdk-api-docs.pages.dev/cpp) - [Source code](https://github.com/foxglove/foxglove-sdk) --- ## Explore examples **URL**: /docs/getting-started/explore-examples **Description**: Curated example datasets with prebuilt layouts — no setup or code required. Explore Foxglove with curated example datasets — real multimodal robotics data with prebuilt layouts and 3D scenes, ready to browse without writing any code. ## In the app Open the command palette (`Cmd/Ctrl` + `K`), search for "Getting started", then select _Explore examples_. Each example opens with a layout already configured for its data. ## On the web Browse the [Foxglove example gallery](https://foxglove.dev/examples) for datasets spanning autonomous vehicles, drones, robot arms, and more. ## Next steps - Rearrange the [panels](/docs/visualization/panels) in an example and save your own [layout](/docs/visualization/layouts) - Ready to see your own data instead? Follow the [getting started guide](/docs/getting-started-guide) or [open a file](/docs/getting-started/open-a-file) --- ## Getting started guide **URL**: /docs/getting-started/getting-started-guide **Description**: Stream a sample drone recording live into Foxglove with one command using the Foxglove wizard — no hardware, robot, or code needed. The Foxglove wizard is a command line tool that streams a sample drone recording live into Foxglove, uploads that same recording to your [project](/docs/projects), and tags the moment the drone takes off so you can search for it. One command covers all three, so you can see how Foxglove works before you connect a robot or write any code. The app walks you through these same steps on its _Getting started_ page, which opens automatically the first time you open Foxglove. To open it again later, use the command palette (`Cmd/Ctrl` + `K`) and search for "Getting started". :::tip Want to use your own robot or code instead? Log from your code with the [SDK](/docs/sdk), or connect [ROS 1](/docs/getting-started/frameworks/ros1), [ROS 2](/docs/getting-started/frameworks/ros2), [PX4](/docs/getting-started/frameworks/px4), or [LeRobot](/docs/getting-started/frameworks/lerobot). To browse real robot data without running anything, [explore examples](/docs/getting-started/explore-examples). ::: ## Before you start - [Create a free Foxglove account](https://app.foxglove.dev/signup) - [Download the desktop app](https://foxglove.dev/download) or use [app.foxglove.dev](https://app.foxglove.dev) in Chrome - Install [uv](https://docs.astral.sh/uv/), or use Python 3.9 or newer with `pip` ## Run the wizard The wizard uploads its recording to a Foxglove project, so it needs a project ID. The _Getting started_ page fills yours in for you and offers the same package manager choice, which makes copying the command from there the shortest path. To write the command yourself, replace `` with the [project](/docs/projects) you want the recording to land in. The wizard opens your browser so you can sign in and confirm the request. If the browser doesn't open, use the verification link and code it prints in your terminal. It signs you in for that session only — it creates no [API key](/docs/settings#api-keys) and writes no credentials to disk. Once you confirm, the wizard serves the recording at `ws://localhost:8765` and waits for Foxglove to connect. Leave it running. Running the wizard is the only step in your terminal — from here you work in Foxglove. You'll connect to the stream you just started, watch the drone fly live in the viewer, then search the recording the wizard saved. ## See it live Once connected, Foxglove opens the [viewer](/docs/visualization), its visualization workspace, on a prebuilt [layout](/docs/visualization/layouts) built for the drone's flight. The wizard replays the recording at the cadence it was captured, so the data updates in real time rather than arriving all at once. A _You're live in Foxglove_ hint confirms the stream — leave the wizard running to keep the data flowing. ![The wizard's drone layout playing live in the viewer, with the "You're live in Foxglove" hint](/img/docs/getting-started/getting-started-guide-viewer@2x.webp) The layout arranges several [panels](/docs/visualization/panels), each showing the same flight a different way: - A [**3D**](/docs/visualization/panels/3d) panel plots the drone in space over the `world` grid, tracing its pose and flight path as the recording plays. - Four [**Gauge**](/docs/visualization/panels/gauge) panels read the drone's live telemetry: **Speed** (0 to 10) and its orientation as **Pitch**, **Roll**, and **Yaw** (each −π to π radians). Every needle tracks the current value as the recording plays. - A [**Plot**](/docs/visualization/panels/plot) panel traces those values over time — speed, pitch, roll, and position — so you can see how the flight develops, not just where the drone is right now. Together they show one recording from several angles at once, which is the point of a layout. The viewer is where you'll spend most of your time in Foxglove — drag the panels around, add your own, or save the arrangement as a [layout](/docs/visualization/layouts) you can reuse. ## Search the uploaded recording While it streams, the wizard also uploads the same recording to your project and tags an [event](/docs/data/events) three seconds in, where the drone takes off — so the flight is saved and searchable, not just shown once. When the _Your recording has been uploaded_ hint appears in the viewer: 1. Select _Search your data_. 2. Foxglove opens the [Search](/docs/data/search) page with the takeoff query already filled in and run: ```text event.properties.event like takeoff ``` 3. You land on the matching event, and an _Explore your data_ hint points out the takeoff events the wizard recorded. Edit the query to explore the rest of the flight, or open a result to [play the recording back](/docs/data/recordings) without the live connection. ## Recap The wizard exercised every core Foxglove concept. It streamed messages on **topics** through [**channels**](/docs/sdk/concepts), which Foxglove rendered in [**panels**](/docs/visualization/panels) arranged into a [**layout**](/docs/visualization/layouts) you can rearrange, save, and share. The same messages were recorded to an [MCAP file](/docs/visualization/connecting/local-data#supported-formats) and uploaded as a searchable [**recording**](/docs/data/recordings) carrying an [**event**](/docs/data/events). Everything else you do in Foxglove builds on those pieces. ## Troubleshooting If Foxglove doesn't connect: - Confirm the wizard is still running in your terminal - Check the connection URL is `ws://localhost:8765` - If the wizard runs on another machine, use that machine's IP address instead of `localhost` If sign-in doesn't finish, open the verification link the wizard printed in your terminal and enter the code shown beside it. If a _Still waiting for your recording_ hint appears, the upload hasn't registered yet. Leave the wizard running until it reports the upload finished, then reload the page. ## Next steps - [Add more panels](/docs/visualization/panels) — try a [Plot](/docs/visualization/panels/plot) or [Raw Messages](/docs/visualization/panels/raw-messages) panel, then save your arrangement as a [layout](/docs/visualization/layouts) - Log from your own code or robot — add the [SDK](/docs/sdk) to your Python, C++, or Rust program, or connect [ROS](/docs/getting-started/frameworks/ros1), [PX4](/docs/getting-started/frameworks/px4), or [LeRobot](/docs/getting-started/frameworks/lerobot) - [Upload your own recordings](/docs/data/importing-data) so your team can search and share them - Learn the [SDK concepts](/docs/sdk/concepts) behind channels, schemas, and servers --- ## Open a file **URL**: /docs/getting-started/open-a-file **Description**: Drop an MCAP file or ROS bag into Foxglove and explore it instantly. Have a recording already? Foxglove opens local files directly — no upload or conversion required. Foxglove supports MCAP files, ROS 1 bags, and [other common formats](/docs/visualization/connecting/local-data#supported-formats). ## Open your recording :::note Don't have a recording handy? [Explore examples](/docs/getting-started/explore-examples) instead, or follow the [getting started guide](/docs/getting-started-guide) — it runs the Foxglove wizard to stream a sample recording live and upload it to your project. ::: ## Next steps - [Import recordings](/docs/data/importing-data) to organize, search, and share them with your team - [Create a layout](/docs/visualization/layouts) so your panel setup is ready every time you open a file - Ready to log your own data? The [Foxglove SDK](/docs/sdk) walks you through it --- # Framework Guides > Integration guides for ROS, PX4, and more ## ROS 1 **URL**: /docs/getting-started/frameworks/ros1 **Description**: Use Foxglove to stream multimodal robotics data using ROS 1 or replay it from `.bag` files. ## Learn more - Set up [remote access](/docs/fleet/remote-access) to visualize and teleoperate your robot through the Foxglove platform without direct network access - Explore [all of the panels](/docs/visualization/panels) Foxglove has to offer for viewing and exploring data - Create and save sets of panels and their setting as [layouts](/docs/visualization/layouts) to reuse them or share with teammates - Use [Foxglove Data Platform](/docs/data) to store, stream, and share your robotics data - Learn about all the [configuration options available for Foxglove Bridge](https://github.com/foxglove/foxglove-bridge-ros1/blob/main/README.md#parameters) --- ## ROS 2 **URL**: /docs/getting-started/frameworks/ros2 **Description**: Use Foxglove to stream multimodal robotics data using ROS 2 or replay it from MCAP files. Foxglove offers two ways to visualize ROS 2 data: live from a running stack via the Foxglove Bridge, or from recorded `.mcap` files. ## Before you start - [Create a free Foxglove account](https://app.foxglove.dev/signup) - [Download the desktop app](https://foxglove.dev/download) or use [the web app](https://app.foxglove.dev) in Chrome ## Learn more - Set up [remote access](/docs/fleet/remote-access) to visualize and teleoperate your robot through the Foxglove platform without direct network access - Explore [all of the panels](/docs/visualization/panels) Foxglove has to offer for viewing and exploring data - Create and save sets of panels and their setting as [layouts](/docs/visualization/layouts) to reuse them or share with teammates - Use [Foxglove Data Platform](/docs/data) to store, stream, and share your robotics data - Learn about all the [configuration options available for Foxglove Bridge](https://github.com/foxglove/foxglove-sdk/blob/main/ros/src/foxglove_bridge/README.md#configuration) --- ## PX4 **URL**: /docs/getting-started/frameworks/px4 **Description**: Load local PX4 ULog (`.ulg`) files for visualization. ## Visualize PX4 flight logs Foxglove can open [PX4 ULog](https://docs.px4.io/main/en/dev_log/ulog_file_format.html) (`.ulg`) files natively for visualization. ULog files contain flight data recorded by PX4 autopilots, including sensor readings, actuator outputs, and system state information. ## Record flight data PX4 automatically logs flight data to ULog files when the autopilot is armed. The logs are typically stored on the SD card or in the `log` directory on your flight controller. To manually start logging or configure logging parameters, use the PX4 command line interface or QGroundControl. For more details on recording and managing ULog files, see the [PX4 logging documentation](https://docs.px4.io/main/en/dev_log/logging). ## Open your recording ## PX4 Converter extension The [PX4 Converter extension](https://github.com/foxglove/px4_converter) translates selected UORB messages and creates Foxglove messages from them so that the PX4 data can be visualized in additional panels such as [map](../../visualization/panels/map.mdx) and [3D](../../visualization/panels/3d.mdx). To install the extension, navigate to Settings->Extensions, select PX4 Converter Extension, and click install. Once installed, the messages will be automatically converted when you load the panels utilizing them. ![PX4 Converter Extension](/img/docs/getting-started/px4-extension@2x.webp) The converter will publish the following frame transforms: - `local_origin` - an ENU frame matching the EKF2 initialization frame - `base_intermediate` - an ENU intermediate frame containing the local position of the aircraft and yaw - `base_link` - same as base_intermediate but with applied Roll and Pitch angles ## Learn more - [PX4 Converter extension](https://github.com/foxglove/px4_converter) - Explore [all of the panels](/docs/visualization/panels) Foxglove has to offer for viewing and exploring data - Create and save sets of panels and their settings as [layouts](/docs/visualization/layouts) to reuse them or share with teammates --- ## LeRobot **URL**: /docs/getting-started/frameworks/lerobot **Description**: Stream LeRobot teleop, recording, and dataset replay to Foxglove with native WebSocket support. ## Visualize LeRobot with Foxglove As of [LeRobot 0.6.0](https://github.com/huggingface/lerobot), Foxglove is a built-in visualization backend. Pass `--display_mode=foxglove` to `lerobot-teleoperate`, `lerobot-record`, or `lerobot-rollout` to stream live camera feeds and joint plots over WebSocket. Replay any recorded dataset as a seekable timeline with `lerobot-dataset-viz --display-mode foxglove`. No custom integration code is required. :::tip The [Native Foxglove Visualization in LeRobot](https://foxglove.dev/blog/native-foxglove-visualization-in-lerobot) blog post walks through teleoperation, recording, and dataset playback with videos. ::: :::note This example uses an SO-101 arm. If you are using a different robot, adjust `--robot.type`, `--teleop.type`, and the related configuration for your hardware. Data will stream to Foxglove as expected for any LeRobot-supported robot as long as you pass `--display_data=true` and `--display_mode=foxglove`. ::: ### 1. Install LeRobot LeRobot requires Python 3.12+. Use [uv](https://docs.astral.sh/uv/) to create a virtual environment and install from PyPI: ```bash uv venv --python 3.12 source .venv/bin/activate uv pip install 'lerobot[feetech,dataset_viz]' ``` The `dataset_viz` extra includes everything needed for Foxglove visualization. See the [LeRobot installation guide](https://huggingface.co/docs/lerobot/v0.6.0/en/installation) for system dependencies and hardware setup. ### 2. Connect Foxglove Start any LeRobot command with `--display_mode=foxglove` (live) or `--display-mode foxglove` (dataset replay). Data appears on `ws://localhost:8765` as soon as the server starts. Import a starter layout from the [Foxglove SDK examples](https://github.com/foxglove/foxglove-sdk/tree/main/python/foxglove-sdk-examples/so101-visualization/foxglove) or [create your own](/docs/visualization/layouts). ### 3. Live visualization :::tip Make sure your SO-101 hardware is set up and calibrated. When in doubt, follow the [LeRobot SO-101 guide](https://huggingface.co/docs/lerobot/en/so101#calibrate). ::: Pass `--display_data=true --display_mode=foxglove` to stream observations, actions, and camera feeds in real time: ```bash uv run lerobot-teleoperate \ --robot.type=so101_follower \ --robot.port=/dev/tty.usbmodemFOLLOWER \ --robot.id=my_follower \ --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \ --teleop.type=so101_leader \ --teleop.port=/dev/tty.usbmodemLEADER \ --teleop.id=my_leader \ --display_data=true \ --display_mode=foxglove ``` Replace the USB port paths with your device paths (find them with `lerobot-find-port`). Camera IDs come from `lerobot-find-cameras opencv`. Live data is published on these topics: | Topic | Contents | | ------------------------------ | ----------------------------------------------------------- | | `/observation/state` | Per-joint observation values (named series for Plot panels) | | `/action/state` | Teleoperator or policy action targets | | `/observation/images/` | Camera frames (`RawImage` or compressed JPEG) | The same flags work for `lerobot-record` and `lerobot-rollout`. During recording, live visualization helps catch bad camera framing or joint limits before you finish an episode. ### 4. Dataset replay `lerobot-dataset-viz` serves any LeRobot dataset — from the Hugging Face Hub or recorded locally — as a seekable timeline in Foxglove: ```bash # A public dataset from the Hub uv run lerobot-dataset-viz \ --repo-id lerobot/svla_so101_pickplace \ --episode-index 0 \ --display-mode foxglove # A dataset you recorded locally uv run lerobot-dataset-viz \ --repo-id ${HF_USER}/my-dataset \ --root ~/.cache/huggingface/lerobot/${HF_USER}/my-dataset \ --episode-index 0 \ --display-mode foxglove ``` The Foxglove playback bar drives play, pause, seek, and speed. Dataset replay uses the same topics as live streaming, plus `/episode/state` for episode metadata. :::note Live commands use underscores (`--display_mode`, `--display_data`) while `lerobot-dataset-viz` uses hyphens (`--display-mode`). This matches each tool's CLI convention. ::: ### 5. Common options | Goal | Live commands | Dataset replay | | -------------------------- | ----------------------------- | ----------------------------- | | JPEG-compress images | `--display_compressed_images` | `--display-compressed-images` | | Custom port | `--display_port=8766` | `--web-port 8766` | | Stream to another machine | `--display_ip=0.0.0.0` | `--host 0.0.0.0` | | Don't auto-play on connect | — | `--no-autoplay` | When streaming to another machine, connect Foxglove to `ws://:8765` instead of `localhost`. ## Learn more - [Blog Post - Native Foxglove Visualization in LeRobot](https://foxglove.dev/blog/native-foxglove-visualization-in-lerobot) - [LeRobot dataset visualization docs](https://huggingface.co/docs/lerobot/en/using_dataset_tools) - [Foxglove SO-101 Documentation](/docs/getting-started/robots/so-100) - [Foxglove Python SDK examples](https://github.com/foxglove/foxglove-sdk/tree/main/python/foxglove-sdk-examples) --- # Hardware Guides > Connecting cameras, lidar, and sensors ## Luxonis OAK Cameras **URL**: /docs/getting-started/hardware/oak-depth-cameras **Description**: Stream data from Luxonis OAK depth cameras to Foxglove for real-time visualization and recording. # Integrate Luxonis OAK Cameras with Foxglove You can stream data from Luxonis OAK cameras to Foxglove using [DepthAI](https://docs.luxonis.com/software-v3/) and the [Foxglove SDK](https://github.com/foxglove/foxglove-sdk). Stream RGB images, colored point clouds, camera calibration, IMU, and transforms, with no ROS required. The camera calculates depth on-device (stereo or neural, chosen with `--depth-source`), and the SDK streams results over a WebSocket that Foxglove connects to directly. :::note If you are using ROS, launch your OAK ROS 2 node and follow the [ROS 2 documentation](../frameworks/ros2.mdx) to run Foxglove Bridge. ::: This approach works with all DepthAI-enabled devices. The examples require a stereo camera pair and an IMU — tested with the [OAK 4 D](https://docs.luxonis.com/hardware/products/OAK%204%20D). `--depth-source neural` needs an OAK 4 (RVC4) device; use `--depth-source stereo` (the default) on older OAK cameras. Use the [Python](https://github.com/foxglove/foxglove-sdk/tree/main/python/foxglove-sdk-examples/oak-camera-streaming) and [C++](https://github.com/foxglove/foxglove-sdk/tree/main/cpp/examples/oak-camera-streaming) SDK examples as the reference implementation. ## Prerequisites - A DepthAI-enabled device with a stereo camera pair and an IMU (for example a Luxonis [OAK 4 D](https://docs.luxonis.com/hardware/products/OAK%204%20D)), connected over USB 3 or Ethernet - For Python: [uv](https://docs.astral.sh/uv/) (installs `foxglove-sdk` and DepthAI v3 from `pyproject.toml`) - For C++: the [DepthAI C++ library](https://github.com/luxonis/depthai-core) (`depthai-core`). If it's in a non-standard prefix, pass `CMAKE_PREFIX_PATH` or `depthai_DIR` when building - On Linux, install the Luxonis [udev rules](https://docs.luxonis.com/hardware/platform/deploy/usb-deployment-guide/) for USB devices ## Published Topics The examples publish these topics: | Topic | Schema | Contents | | --- | --- | --- | | `/oak/points` | `foxglove.PointCloud` | Colored point cloud in meters | | `/oak/rgb/image` | `foxglove.RawImage` | Color video (`bgr8`) | | `/oak/rgb/calibration` | `foxglove.CameraCalibration` | Color-camera intrinsics and distortion | | `/oak/imu` | JSON (ROS `sensor_msgs/Imu`-like) | Accelerometer (m/s²) and gyroscope (rad/s) | | `/tf` | `foxglove.FrameTransforms` | Transform that orients the camera frame upright | ## Installation ## Usage ### Command Line Options | Option | Description | Default | | --- | --- | --- | | `--depth-source` | Depth source: `stereo` or `neural` (`neural` requires OAK 4 / RVC4) | `stereo` | | `--port` | WebSocket server port | `8765` | | `--record ` | Also record to an MCAP file at this path | Disabled | | `--point-unit` | Unit of the DepthAI point coordinates: `auto`, `meters`, or `millimeters` | `auto` | | `--help`, `-h` | Show help message | - | `--point-unit auto` detects whether DepthAI point coordinates are meter-scale or millimeter-scale before publishing Foxglove point clouds in meters. If the point cloud renders ~1000× too large or too small in the 3D panel, override the detection with `--point-unit meters` or `--point-unit millimeters`. ## How It Works DepthAI v3 describes on-device processing as a graph of nodes. Color and depth feed an `RGBD` node that produces a colored point cloud; the IMU publishes on a separate path. The snippet below is abridged from `main.py`. The examples create typed Foxglove channels, start a WebSocket server, then poll RGBD and IMU queues and log Foxglove messages. The C++ example mirrors this structure; see the [Python example](https://github.com/foxglove/foxglove-sdk/tree/main/python/foxglove-sdk-examples/oak-camera-streaming) and [C++ example](https://github.com/foxglove/foxglove-sdk/tree/main/cpp/examples/oak-camera-streaming) for the full convert-and-log loop. ### Build the DepthAI Pipeline ```python color = pipeline.create(dai.node.Camera).build(sensorFps=fps) # stereo or neural depth from CAM_B / CAM_C ... rgbd = pipeline.create(dai.node.RGBD).build(color, depth, size, fps) rgbd.setDepthUnits(dai.LengthUnit.METER) imu = pipeline.create(dai.node.IMU) imu.enableIMUSensor(dai.IMUSensor.ACCELEROMETER_UNCALIBRATED, IMU_HZ) imu.enableIMUSensor(dai.IMUSensor.GYROSCOPE_UNCALIBRATED, IMU_HZ) ``` ## Visualize OAK Camera Data in Foxglove Run the example, then connect Foxglove to the WebSocket server. Optionally import the example layout at `foxglove/oak_layout.json` (in the Python example directory) via _Import from file…_ in the layout menu. ### Visualize Camera Feed ### Visualize Point Cloud ### Visualize IMU Data The `/oak/imu` payload is shaped like ROS `sensor_msgs/Imu`. Add a Plot panel and set the expression to `/oak/imu.linear_acceleration.x` or `/oak/imu.angular_velocity.x` (and `.y` / `.z`) to chart accelerometer or gyroscope readings over time. ## Learn More - Explore the [Python OAK camera streaming example](https://github.com/foxglove/foxglove-sdk/tree/main/python/foxglove-sdk-examples/oak-camera-streaming) - Explore the [C++ OAK camera streaming example](https://github.com/foxglove/foxglove-sdk/tree/main/cpp/examples/oak-camera-streaming) - Read the [Luxonis documentation](https://docs.luxonis.com/) - Learn more about using the [Foxglove SDK](/docs/sdk/) --- ## RGB Camera **URL**: /docs/getting-started/hardware/rgb-camera **Description**: Stream data from RGB cameras to Foxglove for real-time visualization # Integrate RGB Cameras with Foxglove With Foxglove, you can stream data from any RGB camera (USB webcams, built-in laptop cameras, or IP cameras) for real-time visualization. The [Foxglove SDK](https://github.com/foxglove/foxglove-sdk) contains example code that shows how to stream data that you can modify to fit your application. :::note If you are using ROS, you can launch your camera node, and follow [ROS 2 Documentation](../frameworks/ros2.mdx) to start Foxglove Bridge. ::: ## Prerequisites ### OpenCV Installation ## Installation and Setup ## Usage ### Command Line Options | Option | Description | Default | | ------------------- | -------------------------------------- | ------- | | `--camera-id`, `-c` | Camera ID (integer) or video file path | `0` | | `--help`, `-h` | Show help message | - | ## Visualize RGB Camera Data in Foxglove To visualize the RGB camera data, open Foxglove and connect to the websocket server. ### Visualize Camera Feed ## Learn More - Explore the [Foxglove SDK documentation](/docs/sdk/) - Check out the [Python RGB camera stream example](https://github.com/foxglove/foxglove-sdk/tree/main/python/foxglove-sdk-examples/rgb-camera-visualization) - Check out the [Rust RGB camera stream example](https://github.com/foxglove/foxglove-sdk/tree/main/rust/examples/rgb_camera_visualization) - Check out the [C++ RGB camera stream example](https://github.com/foxglove/foxglove-sdk/tree/main/cpp/examples/rgb-camera-visualization) - Learn more about [OpenCV camera capture](https://docs.opencv.org/4.x/d8/dfe/classcv_1_1VideoCapture.html) --- ## Stereolabs ZED Cameras **URL**: /docs/getting-started/hardware/stereolabs-depth-cameras **Description**: Stream data from Stereolabs ZED depth cameras to Foxglove for real-time visualization and recording. # Integrate Stereolabs ZED Cameras with Foxglove With Foxglove, you can stream data from any of the Stereolabs ZED cameras: - ZED Mini - ZED 2 - ZED 2i - ZED X series The [ZED Foxglove Integration](https://github.com/stereolabs/zed-foxglove) utilizes Stereolabs' ZED SDK and Foxglove SDK to stream real-time image and point cloud data from ZED cameras to Foxglove. Both Python and C++ implementations are available. ## Installation :::note If you are using ROS, launch [ZED ROS 2 wrapper](https://github.com/stereolabs/zed-ros2-wrapper), and run Foxglove Bridge, as described in [ROS 2 Documentation](../frameworks/ros2.mdx). ::: ### ZED SDK Installation :::tip For up-to-date instructions on ZED SDK and CUDA installation, refer to the [Stereolabs documentation](https://www.stereolabs.com/docs/development/zed-sdk/). ::: ### ZED Foxglove Integration Setup ## Usage ### Command Line Options | Option | Description | Default | | --- | --- | --- | | `--ws` | Enable WebSocket server for live streaming | Disabled | | `--mcap ` | Output MCAP file name | `output.mcap` (C++), disabled (Python) | | `--help`, `-h` | Show help message | - | ## Visualize Depth Camera Data in Foxglove To visualize the ZED camera data in Foxglove, run the application with the `--ws` option, as described in the previous section, and follow these steps. ### Visualize Camera Feed ### Visualize Point Cloud ## Learn More - Explore [Stereolabs Documentation](https://www.stereolabs.com/docs/) - Check out the [ZED-Foxglove Repository](https://github.com/stereolabs/zed-foxglove/) - Learn more about using [Foxglove SDK](/docs/sdk/) --- # Visualization > Panel-based data visualization and debugging ## Foxglove Visualization **URL**: /docs/visualization **Description**: After connecting to a data source, leverage Foxglove's visualization and debugging tools to start exploring your data. Download Foxglove as a [desktop app](https://foxglove.dev/download) (available on Linux, Windows, or macOS), or navigate to the [web app](https://app.foxglove.dev) in a Chrome browser window. ### Extensions - Installing [extensions](/docs/extensions) via the [registry](https://github.com/foxglove/extension-registry) ### Other - [Creating shareable links](/docs/visualization/shareable-links) prefixed with `foxglove://` ## Interface [Connect to a data source](/docs/visualization/connecting) to explore it with Foxglove's visualization and debugging tools. ![top nav](/img/docs/visualization/navigation.png) | component | description | | --- | --- | | **App menu** | Connect to a [data source](/docs/visualization/connecting#data-sources), toggle the sidebars, or view resources | | **Add tab** | Add a tab to the current window | | **Add panel** | Add a [panel](/docs/visualization/panels) to the current layout | | **Layout menu** | Save your workspace view as a [layout](/docs/visualization/layouts) and share it with teammates | | **Left sidebar** | Edit panel settings (Panel), view data source's topics (Topics), and troubleshoot issues with your connection (Problems) | | **Right sidebar** | Set layout-wide [variables](/docs/visualization/variables) (Variables), write layout-scoped [user scripts](/docs/visualization/user-scripts) (User Scripts), and view playback metrics (Performance). Must enable debugging features in the [General settings](https://app.foxglove.dev/~/settings/user/general) to see the Performance tab | | **User menu** | Sign in to your Foxglove account, configure preferences, manage [extensions](/docs/extensions), and more | ## Sidebars ### Panel [Edit panel settings](/docs/visualization/panels#edit-settings) for any selected [panel](/docs/visualization/panels). ### Topics View all topics available in the data source, along with their data types, message encodings, and message rates. :::note Message frequency and count are not available for data streamed from Foxglove Data Platform. ::: ### Problems See a list of playback errors to troubleshoot. ### Variables Set layout-wide [variables](/docs/visualization/variables) that can be used in different panels with [FoxQL](/docs/visualization/foxql). ### User Scripts Write layout-scoped data transformations in [User Scripts](/docs/visualization/user-scripts). Open the **User Scripts** tab in the right sidebar or use the command palette to create and switch scripts. ### Performance Display playback and data-streaming performance statistics. The following statistics are displayed for a given playback session. "Instantaneous" statistics are reported based on the most recent frame, and also are averaged over the last 5 seconds. #### Memory usage Details the `jsHeapSizeLimit`, `usedJSHeapSize`, and `totalJSHeapSize`. #### Main thread ##### Frame rate The number of frames played per second. Though the player can play back at up to 60fps, this statistic will be lower if frames take longer than 16ms to render. ##### Latency Measures time for information to travel from the data source to the visualization panels. #### User scripts Measures time spent executing user scripts, both individually and in total. #### Data pipeline ##### Playback speed The player tries to play at the speed specified by the user, but may not be able to keep up, given heavy layouts and large amounts of data. This chart displays the actual playback speed as a ratio of bag time to playback time. ##### Frame rate The number of frames played per second. Though the player can play back at up to 60fps, this statistic will be lower if frames take longer than 16ms to render. ##### Bag frame time The duration in bag-time for the rendered frames in milliseconds. To "keep up" with playback, Foxglove will often emit "larger" frames. ##### Data throughput The amount of data received by the player in megabits per second. For remote bags, this includes topics that the player is not subscribed to. This statistic does not account for Content-Encoding compression, so it may be larger than the actual network bandwidth. ## Desktop-only features Some features are only available via the [desktop app](https://foxglove.dev/download). Specifically: - Tabs — Open multiple data sources in a single window using browser-style tabs. Create new tabs, drag to reorder, and use [keyboard shortcuts](/docs/visualization/shortcuts#desktop-tabs) to switch between them. Each tab maintains its own navigation history. - Opening a [native ROS 1 connection](/docs/getting-started/frameworks/ros1?modality=live) - Loading local URDF and mesh resources in the [3D panel](/docs/visualization/panels/3d) using URLs prefixed with `package://` - Exporting the current panel layout to a [video file](/docs/visualization/video-export) ## System requirements Foxglove visualization requires Chrome v119+ on Linux, Windows, or macOS. Make sure you are using the [latest version of Chrome](https://www.google.com/chrome/), or download [Foxglove Desktop](https://foxglove.dev/download) instead. Graphics acceleration is strongly preferred when using the Image panel for video replay or the 3D panel. See [checking video acceleration](/docs/visualization/panels/image#checking-video-acceleration) for more. Actual performance will depend significantly on a number of factors including the data you are visualizing, network connectivity, and whether you're looking at live, local, or cloud data - among other factors. Use the [performance sidebar](/docs/visualization#performance) to profile your workloads. ## Links and resources - [Getting Started with Foxglove](https://www.youtube.com/watch?v=ySVzQ3iFw90) --- ## Playback **URL**: /docs/visualization/playback **Description**: When visualizing a local or remote data file, you can navigate its contents using the playback bar controls. When visualizing a local or remote data file, you can navigate its contents using the playback bar controls. ![playback bar](/img/docs/visualization/playback/bar@2x.webp) If you're playing back a data file using [the Foxglove SDK's WebSocket server](/docs/sdk/websocket-server), you can also expose the playback bar controls like play/pause, seek, and playback speed, by enabling the [`PlaybackControl` capability](/docs/sdk/websocket-server#playback-control). This lets you control playback on your remote server directly from the Foxglove app. ### Message ordering The Foxglove app processes and delivers messages in **log time** order. Log time is meant to be a monotonic increasing value from some epoch, but its specific meaning varies by context: - **For recorded data files** (MCAP, ROS bags, ULog): Log time typically refers to when the message was originally recorded by the robot - **For live connections** (WebSocket, Foxglove Bridge): Log time refers to when the message was received by the WebSocket client Foxglove has some interpretation for log time in every supported data format, including [MCAP](https://mcap.dev/spec#message-op0x05), [WebSocket](/docs/sdk/websocket-server), [ROS 1](https://docs.ros.org/en/noetic/api/roscpp_traits/html/classros_1_1MessageEvent.html#a21d12c15255967761a0078ef29902414), [ROS 2](https://docs.ros.org/en/rolling/p/message_filters/generated/classmessage__filters_1_1MessageEvent.html#_CPPv4NK15message_filters12MessageEvent14getReceiptTimeEv), and [ULog](https://docs.px4.io/main/en/dev_log/ulog_file_format.html). Messages are always delivered in log time order to panels, scripts, and range subscriptions. #### Other timestamp fields Messages may contain other timestamp fields beyond log time. Depending on your robot logic, these timestamps might come before or after the log time: - **Header stamp**: Common in ROS messages, typically represents when sensor data was published - **Publish time**: The time a message was created, usually closer to the actual event - **Custom timestamp fields**: Any field in your message containing time data Some panels can use these alternative timestamps: - The [Plot](/docs/visualization/panels/plot) and [State Transitions](/docs/visualization/panels/state-transitions) panels support plotting data by custom timestamp fields - 3D and Image panels interpret and visualize [transform frames](/docs/visualization/panels/3d) based on timestamps within those messages Note that when mixing time sources, they still must all use a common reference time (epoch). You should normalize the times using offsets in order to ensure that time 0 refers to the same time for all sources. :::tip For most accurate playback synchronization, use log time as your primary timestamp. ::: #### Choosing the right timestamp When working with multiple timestamp options, consider these guidelines: - **Log time** (recommended default): - Best for overall playback synchronization - Ensures consistent message ordering across all panels - Use this unless you have a specific reason to use another timestamp - **Publish time**: - Time at which the message was published - Use to view information base on when it happened rather than wen it was logged - Possible to have message appear out of order - When writing an MCAP, if not available, must be set to the log time - **Header stamp**: - Use if you have ROS specific needs that rely on header time stamp - Possible to have messages appear out of order - **Custom timestamp fields**: - Use for specialized analysis of specific timing aspects in your system - Useful when your messages contain multiple relevant timestamps ### Message loading Foxglove optimizes how it loads your complex robotics data, for more streamlined seeking and playback. #### Message buffering To enable smoother playback, Foxglove buffers messages ahead of time into memory. The darker gray sections in the playback bar show which portions of the timeline have already been buffered. Playing from these buffered areas is typically faster, as the messages are already loaded into memory. Depending on the data being played back, the buffer may fill up quickly — especially when dealing with high-bandwidth topics. #### Message "lookback" When seeking to an arbitrary point in your loaded data, it's unlikely that every topic you are visualizing has a message at exactly the time you jumped to. The vast majority of your topics will have messages sometime before or after that exact seek point. To ensure that your layout still displays something reasonable, Foxglove performs a “lookback” on your data. Essentially, it looks for the most recent message on each subscribed topic. This ensures that even when seeking to an arbitrary point in your data, Foxglove will still display reasonable data for all the panels in your layout – even if their messages occurred at different times. #### Latched topics By default, ROS 1 `.bag` files, MCAP files, and Foxglove data streams will play back using message latching. When seeking within your data, Foxglove fetches the last message on all subscribed topics – even if they occurred multiple minutes before your seek location. Every panel in the layout will then automatically display the last data it saw for that topic – even if that data is infrequently published or was not published at that exact moment in time. For Foxglove data streams, Foxglove will load the last seen messages for each subscribed topic – even if those messages are from before your current data segment's start time. Message latching allows panels to accurately display data from infrequently published topics (like a map, for example), even while seeking around to multiple points in your data at random. #### Range loading While most Foxglove panels – like the [Raw Messages](/docs/visualization/panels/raw-messages) and [Image](/docs/visualization/panels/image) panels – display just the most recent message for a given topic, others like the [Plot](/docs/visualization/panels/plot) and [Map](/docs/visualization/panels/map) panels benefit from visualizing messages across the data's entire time range. Seeing a complete view of one's data makes spotting anomalies, summarizing robot behavior, and recognizing trends and patterns much easier. Range loading data allows these panels to access all their historical data throughout playback. Some panels that visualize only their most recently seen data also leverage range loading. For example, the [3D panel](/docs/visualization/panels/3d) can preload transform messages from the full data range to accurately position its markers. Robots often have many coordinate frames (e.g. joints of a robot arm, cameras on a self-driving car), each with their own markers. To render markers from different frames in a single 3D scene, the panel needs to use transforms to calculate the position of these visual elements in a common coordinate space. Since transforms accumulate and update over time, looking at just the latest message would result in an incompletely or incorrectly rendered scene. By loading all transforms, the 3D panel can accurately position its markers, regardless of where and how often you decide to seek in your data. ## Controls and shortcuts - Create, edit, view, and search [events](/docs/data/events) - Trim or expand the range of the active data - Format the current timestamp display - Seek back and forth at increments of 100ms - Loop playback - Configure playback speed ### Adjusting the playback range You can adjust the playback start and end time when viewing a local file, streaming a recording, or streaming data for a device + time range. Drag the handles on the playback bar to make quick adjustments or edit the input fields for precise adjustments. The time format will match your current timestamp display. Each input also has a button that fills it with the current playback time, and `Cmd`/`Ctrl` + `Shift` + `←`/`→` do the same from the keyboard while the dialog is open, so you can seek to the moment you care about and snap the range to it instead of copying the timestamp by hand. Once you have adjusted the range, a **Reset to full recording** link appears beneath the inputs to restore the recording's full extent. Changes take effect when you select **Update**. The command palette offers the same adjustments without opening the dialog: **Trim start to here** and **Trim end to here** move the corresponding end of the range to the playback time, and **Reset playback range** restores the full recording. These apply immediately. ![playback bar](/img/docs/visualization/playback/bar-range@2x.webp) :::tip If you make an adjustment and the resulting start/end time do not match what you expected, it's likely one of two scenarios occurred: - You're viewing a fixed-length recording and tried to extend beyond the start/end. In this case, we will automatically adjust the range to match the full recording. - You're viewing device data and the new playback range did not include any additional data. In this case, we will automatically trim the playback range around available data. ::: ### Keyboard shortcuts | Shortcut | Description | | --- | --- | | `Space` | Toggle playback | | `←` | Seek backward 100ms | | `→` | Seek forward 100ms | | `Shift` + `←` | Seek backward 10ms | | `Shift` + `→` | Seek forward 10ms | | `Alt` + `←` (Windows and Linux)
`Option` + `←` (macOS) | Seek backward 500ms | | `Alt` + `→` (Windows and Linux)
`Option` + `→` (macOS) | Seek forward 500ms | | `Home` (Windows and Linux)
`Fn` + `←` (macOS) | Seek to start | | `End` (Windows and Linux)
`Fn` + `→` (macOS) | Seek to end | | `Shift` + `↑` | Increase playback speed | | `Shift` + `↓` | Decrease playback speed | | `Ctrl` + `Shift` + `←` (Windows and Linux)
`Cmd` + `Shift` + `←` (macOS) | Set the range start to the current playback time (while adjusting the playback range) | | `Ctrl` + `Shift` + `→` (Windows and Linux)
`Cmd` + `Shift` + `→` (macOS) | Set the range end to the current playback time (while adjusting the playback range) | --- ## Comparison mode **URL**: /docs/visualization/comparison-mode **Description**: Open multiple data sources on a shared playback timeline and view their data together, either overlaid or side-by-side. You can open two or more data sources on a shared playback timeline while keeping each source distinct using **comparison mode**. This is useful when the sources share topics and you want to view the data together, either overlaid or side-by-side. For example, comparing a run before and after a code change, or aligning a simulation against a real-world log. Because comparison mode can pull data from different devices, it is an excellent fit for multi-device workflows. Select sessions and events from separate robots to unlock comparing like topics, overlaid or side-by-side. ## Starting a comparison On the Recordings, Sessions, or Events pages, select two or more items and click **Compare**. ![Compare button on the Recordings page](/img/docs/visualization/comparison-mode/recordings-compare@2x.webp) To open local files straight into comparison mode, click **Compare local files** on the dashboard, or **Compare local files…** in the nav menu under **Open data sources**, then select two or more files of the same type. Opening local files any other way — the command palette, `Ctrl` + `O` / `Cmd` + `O`, **Open local file(s)**, or dragging files into Foxglove — merges them by default. To split a merged set into slots, open the Sources sidebar and set **Source mode** to **Compare** at the top. ## Reading the comparison Each item you compare (such as a recording, session, or event) is a **source**. Each source occupies a **slot** labeled A, B, C, and so on, assigned in the order you select them. Slot badges appear next to the source name in the top bar, in Plot and State Transitions legends and series settings, in the Sources sidebar, and in the scrubber tooltip. Map represents each slot with a distinct color instead of a badge. ![Slot badges across the top bar and Plot legend](/img/docs/visualization/comparison-mode/plot-legend-slots@2x.webp) Some panels, such as [Plot](/docs/visualization/panels/plot#comparison-mode), [State Transitions](/docs/visualization/panels/state-transitions#comparison-mode), and [Map](/docs/visualization/panels/map#comparison-mode), are **multi-source**: they render every source at once. Others are **single-source**: they display one source at a time. Both types have a data source picker in the panel toolbar. Use it to focus a multi-source panel on one slot, or to switch a single-source panel between slots. Hovering a slot accents the matching slot and dims the other sources' data in multi-source panels, so you can pick out one source's data. The slot badges themselves stay visible. ## Adjusting a comparison Every source begins at time zero on the shared timeline, regardless of when it was originally recorded. Shift a source's start time to align two sources recorded at different real-world times. Open the Sources sidebar to: - **Add local files.** Click **Add** in the footer. Local-file comparisons only. - **Remove a source.** Click **×** next to its heading and confirm. - **Reorder slots and change start times.** Click **Manage** in the footer (or the compare icon on the playback bar) to open the editor. Drag rows in the editor to reorder slots, type new offsets in each row's field, or drag each source's bar on the playback timeline to shift its start time. ![Sources sidebar comparison editor](/img/docs/visualization/comparison-mode/source-info-editor@2x.webp) :::note Setting a source's start time before time zero re-anchors the whole set. The earliest source moves to zero, and every other source shifts by the same amount. ::: Hovering the playback scrubber shows the **Elapsed** time followed by each source's local timestamp. ![Scrubber tooltip showing Elapsed time and per-source timestamps](/img/docs/visualization/comparison-mode/scrubber-tooltip@2x.webp) --- ## Layouts **URL**: /docs/visualization/layouts **Description**: Arrange Foxglove panels into custom layouts to tackle your visualization and debugging workflows. :::note Authoring and editing layouts requires a [developer seat](/docs/security/seat-types). Members with a basic seat can view shared layouts. ::: With layouts, you can reuse a workspace for a recurring task or share it with a teammate solving a similar problem. A perception engineer may develop layouts for calibrating various sensors, a planning engineer may have a few for visualizing different routing algorithm outputs, and a controls engineer may build one for plotting robot kinematics. Use the "Layouts" menu to create, edit, and share layouts. ![Layouts menu](/img/docs/visualization/layouts/layout-menu@2x.webp) ### Personal layouts Personal layouts are accessible to only you – they cannot be viewed, loaded, or edited by anyone else. Saved changes to your personal layouts are synced across all your devices, and can be shared with your organization. #### Create Use the "Layouts" menu's "Create new layout" option to start building a custom workspace. Add and position [panels](/docs/visualization/panels), configure their settings, adjust [playback settings](/docs/visualization/playback), and set [variable](/docs/visualization/variables) values. ![New layout](/img/docs/visualization/layouts/new-layout@2x.webp) :::tip To organize multiple panels in your layout, use the [Tab panel](/docs/visualization/panels/tab) feature. Select multiple panels and choose "Group in tab" to instantly organize them without manual dragging. ::: #### Unsaved changes Changes you make to layouts are automatically tracked both in the desktop app and per-browser on web, but layouts must be saved to sync across different environments. When you have unsaved local changes to your layout you'll see a purple 'unsaved' indicator appear on the layout menu. From your layout's context menu you can: - **"Save changes"** – Save your locally stored changes. - **"Revert"** – Discard your unsaved changes and revert to the last explicitly saved layout version ![Unsaved personal layout](/img/docs/visualization/layouts/unsaved-personal@2x.webp) #### Import and export Use a layout's context menu to "Export..." it as a JSON file. Use the "Layouts" menu's "Import from file..." option to load an exported layout JSON file. :::tip You can also copy individual tabs between layouts using the right-click context menu. See [Tab panel - Copying tabs between layouts](/docs/visualization/panels/tab#copying-tabs-between-layouts) for details. ::: #### Share Use a layout's context menu to "Share with team..." – this will make a personal layout accessible to your entire organization. #### Other Use each layout's context menu to view a history of changes, rename, duplicate, or delete any personal layout. To perform a batch action on multiple layouts: - Use `Cmd` to multi-select individual layouts - Use `Shift` to select a range of adjacent layouts - Right-click any selected layout's context menu to select a batch action ### Organization layouts Organization layouts allow teams to curate a set of canonical layouts to accomplish common tasks – e.g. for calibrating radar sensors, visualizing planning algorithm outputs, or viewing logs. Instead of maintaining marginally different setups for different tasks, organization members can use layouts pre-configured by workflow experts to avoid redundant work and accelerate development. Organization layouts work very similarly to personal layouts – i.e. you can view their history, rename, copy, export, and delete them – but operate more as templates than evolving snapshots of a workspace. ![Organization layout](/img/docs/visualization/layouts/org-layout@2x.webp) #### Unsaved changes Changes you make to layouts are automatically tracked both in the desktop app and per-browser on web, but layouts must be saved to sync across different environments and to update the shared layout for your organization. When you have unsaved local changes to your layout you'll see an 'unsaved' indicator appear on the layout menu. From your layout's context menu you can: - **"Save changes"** – Overwrite the organization layout with your changes - **"Revert"** – Discard your local changes and revert to the last explicitly saved layout version - **"Make a personal copy"** – Save your changes as a separate personal layout (reverts changes to organization layout) #### Share Use a personal layout's context menu to "Share with team..." – this will make it accessible to your entire organization. Organization layouts can be edited, renamed, or deleted by any team member. ### Layout History In the layout history sidebar you can compare your current draft to the most recently saved version of a layout and see who last saved the layout when. On Pro, Enterprise, and Academic plans, you can also browse a history of changes to your layouts and restore an older version if needed. ![Layout history sidebar](/img/docs/visualization/layouts/layout-history@2x.webp) ### Layout Management The [layout manager](https://app.foxglove.dev/~/layouts) allows for individual or batch changes to layouts: ![Layout Manager](/img/docs/visualization/layout-manager@2x.webp) #### Add Use the **"Add"** button to either create a new layout or import an existing one. ![Layout Manager](/img/docs/visualization/add-new-layout@2x.webp) - **"New Layout"** will prompt for a name, personal or organizational, and an optional folder to store the layout under: ![Layout Manager](/img/docs/visualization/create-new-layout@2x.webp) - **"Import Personal Layout"** will prompt to select a layout JSON file from the computer's file system. #### Navigation Browsing layout folders can be performed directly from the left panel. They are categorized at the top level by: all, personal, and organizational. Additionally, navigation can be performed from the right panel by clicking a folder row to move into it, or by clicking the breadcrumb at the top of the right panel to move out of it. #### Searching & Filtering Searching and filtering can be performed from the top of the right panel. Providing values to these controls will limit the shown layouts in the currently selected folder or top level category to those that match. #### Individual Actions To perform similar actions from the layout menu, clicking the **"ellipsis"** button will provide a dropdown revealing them. The actions listed will depend on the layout type and status. Clicking **"Open"** will navigate you to visualization with the opened layout preselected. ![Layout Manager](/img/docs/visualization/layout-manager-layout-actions@2x.webp) #### Batch Actions Batch actions are performed by selecting at least one row using the checkboxes in the left most column. Once selected the actions will appear above the table. ![Layout Manager](/img/docs/visualization/layout-manager-batch-actions@2x.webp) ##### Move to Folder Only layouts of the same type, personal or organizational, can be selected to move to a new folder. Additionally, only a folder that exists under that same layout type can be chosen as a destination. ![Layout Manager](/img/docs/visualization/layout-manager-move-layouts@2x.webp) ### Layout data Saving a layout stores its full configuration in Foxglove's control plane, including panel settings such as topic and field names, so it can sync across your devices and be shared with your organization. Until you explicitly save a layout, your changes stay local to your browser or desktop app and aren't sent to Foxglove. If you don't want layout data stored in Foxglove's control plane, embed Foxglove in your own application and serve layouts from your own storage using [`selectLayout` and `getLayout`](/docs/embed/foxglove-embed#selectlayoutparams-selectlayoutparams-void). ### Layouts in Python notebooks You can create and use layouts programmatically in your [Jupyter-like notebooks](/docs/notebook). See the [Layouts API documentation](/docs/notebook/layouts) for details. --- ## Variables **URL**: /docs/visualization/variables **Description**: Variables are values that can be set globally for a Foxglove layout. Variables are values that can be set globally for a Foxglove layout and quickly update multiple panels at once. They can be set to a string, number, or boolean value. They can also be set to an array containing any of those primitive values (e.g. `["x", 2, false]`), or a map of strings to any of those primitive values (e.g. `{"x": 2, "y": false}`). Open the sidebar's "Variables" tab to view, add, and update variables. ![variables tab](/img/docs/visualization/variables@2x.webp) ## Using variables Reference variables in Foxglove using a `$` prefix, for example `$my_global_var`. ### In expressions Panels that support [FoxQL](/docs/visualization/foxql) – like [Raw Messages](/docs/visualization/panels/raw-messages), [Plot](/docs/visualization/panels/plot), and [State Transitions](/docs/visualization/panels/state-transitions) – can reference variables to [slice](/docs/visualization/foxql#using-variables-to-slice) or [filter](/docs/visualization/foxql#using-variables-to-filter) data and dynamically decide what to visualize. You can leverage variables to quickly switch between subsets of your data – for example: - Create a `$my_ID` variable in the _Variables_ tab, and set its value to `101` - Type `/my_objects.objects[:]{id==$my_ID}` in a [Raw Messages panel](/docs/visualization/panels/raw-messages) to inspect the object whose `id` field equals `101` - Add `/my_objects.objects[:]{id==$my_ID}.velocity` as a y-axis value in a [Plot panel](/docs/visualization/panels/plot) to plot that same object's `velocity` ### In User Scripts Variables can be [referenced](/docs/visualization/user-scripts#using-global-variables), but not modified, in User Scripts. The user script receives all variables as an object every time it is called. ### In Foxglove Extensions Variables can be referenced in custom [extension panels](/docs/extensions) via the extension API [`RenderState`](/docs/extensions/extension-api/type-aliases/RenderState). ## Updating variables Variables can be updated directly or via user interactions in the [3D](/docs/visualization/panels/3d) panel or [Variable Slider](/docs/visualization/panels/variable-slider) panel. Foxglove extensions can also create or update variables via the extension API [`PanelExtensionContext`](/docs/extensions/extension-api/type-aliases/PanelExtensionContext). ## Controls and shortcuts - Type `]` to quickly open and close the right sidebar - Click input + `↑` – Increment numeric variable values - Click input + `↓` – Decrement numeric variable values --- ## User Scripts **URL**: /docs/visualization/user-scripts **Description**: Publish pseudo topics internally to Foxglove using custom scripts. Manipulate, reduce, and filter existing messages and output them for useful visualization. User scripts are custom in-app scripts (written in TypeScript) that transform messages. A user script can transform both playback and range-loaded. The result is output to a new topic. - **Playback data** – Messages streaming frame-by-frame into Foxglove; e.g. data for the [Raw Messages](/docs/visualization/panels/raw-messages) or [3D](/docs/visualization/panels/3d) panel - **[Range-loaded data](/docs/visualization/playback#range-loading)** – Messages for the entire data range being played back; e.g. data for the [Plot](/docs/visualization/panels/plot) or [State Transitions](/docs/visualization/panels/state-transitions) panels When transforming range-loaded data, Foxglove creates two instances of the running user script – one handles the full data range, while the other handles just the current playback frame of messages. Each instance of the user script receives the messages in [log time order](/docs/visualization/playback#message-ordering). :::tip User scripts are local to a layout. Use a **[message converter](/docs/extensions#message-converters)** to transform messages in a way that will apply to all layouts. ::: ## Getting started User Scripts are written in TypeScript. :::info TypeScript is a superset of JavaScript, so you can Google syntactic questions (e.g. how to manipulate arrays, or access object properties) using JavaScript terms, and semantic questions (e.g. how to make an object property optional) using TypeScript terms. ::: ### Writing your first script The user script editor can be found in the **User Scripts** tab in the right sidebar. Open the sidebar by pressing ] or use the command palette. Every script must declare 3 exports: - `inputs` – An array of input topics to transform - `output` – Name of the transformed output topic - `script` – A function that takes messages from input topics, transforms them, and then publishes messages on the output topic; must be the [default export](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#description) Here is a basic script that echoes its input on a new output topic, `/foxglove_script/echo`: ```typescript export const inputs = ["/rosout"]; export const output = "/foxglove_script/echo"; export default function script(event: Input<"/rosout">): Message<"rosgraph_msgs/Log"> { return event.message; } ``` If you open a recording with a `/rosout` topic, you can now inspect the `/foxglove_script/echo` topic in the [Raw Messages panel](/docs/visualization/panels/raw-messages). When you create a new script, you’ll be presented with some boilerplate: ```typescript type Output = { hello: string }; export const inputs = ["/input/topic"]; export const output = "/foxglove_script/output_topic"; export default function script(event: Input<"/input/topic">): Output { return { hello: "world!" }; } ``` You’ll notice a few things: - The `Input` and `Message` types are imported from the `./types.ts` module, which provides helper types for your `Input` events and messages - The `Output` type has some default properties that the `script` function's output must adhere to `Input` is a generic type, meaning that it takes a parameter in order to be used. It is left empty on purpose as you'll need to populate it with the name of your input topic, e.g. `Input<"/rosout">`. :::note The input `event` is read-only. Do not modify the `event` object. ::: As for the `Output` type, you can either manually type out your output with the properties you care about (i.e. what is available in the boilerplate) or use one of the dynamically generated types from the `Message` type imported above. For instance, if you want to publish an array of markers, you can return the type `Message<"visualization_msgs/MarkerArray">`. It's not always obvious how message properties affect the visualized output – strictly typing your scripts helps you debug issues at compile time rather than at runtime. With that said, you can disable TypeScript checks when working on a rough draft of your script by adding `// @ts-expect-error` on the line above the one you want to ignore. ### Using multiple input topics In some cases, you will want to define multiple input topics: ```typescript export const inputs = ["/rosout", "/tf"]; export const output = "/foxglove_script/echo"; export default function script(event: Input<"/rosout"> | Input<"/tf">): { data: number[] } { if (event.topic === "/rosout") { // read event.message fields expected for /rosout messages } else { // read event.message fields expected for /tf messages } return { data: [] }; } ``` This snippet uses union types to assert that the message in the `script` function can take either a /rosout or /tf topic. Use an if/else clause to differentiate between incoming topics' schema names when manipulating messages. To combine messages from multiple topics, create a variable in your script's global scope to reference every time your `script` function is invoked. Check timestamps to make sure you are not publishing out-of-sync data. ```typescript export const inputs = ["/rosout", "/tf"]; export const output = "/foxglove_script/echo"; let lastReceiveTime: Time = { sec: 0, nsec: 0 }; const myScope: { tf?: Message<"tf2_msgs/TFMessage">; rosout?: Message<"rosgraph_msgs/Log"> } = {}; export default function script( event: Input<"/rosout"> | Input<"/tf">, ): { data: number[] } | undefined { const { receiveTime } = message; let inSync = true; if (receiveTime.sec !== lastReceiveTime.sec || receiveTime.nsec !== lastReceiveTime.nsec) { lastReceiveTime = receiveTime; inSync = false; } if (message.topic === "/rosout") { myScope.rosout = event.message; } else { myScope.tf = event.message; } if (!inSync) { return { data: [] }; } } ``` ### Using global variables The `script` function will receive all of the [variables](/docs/visualization/variables) as an object every time it is called. Each time a new message is received, the `script` function will be re-run with the latest variable values: :::note Global variables are read-only on user-scripts. Do not modify the `globalVars` parameter. ::: ```typescript type Output = {}; type GlobalVariables = { someNumericaVar: number }; export const inputs = []; export const output = "/foxglove_script/"; export default function script(event: Input<"/foo_marker">, globalVars: GlobalVariables): Output { if (event.message.id === globalVars.someNumericaVar) { // Message's id matches $someNumericaVar } return { data: [] }; } ``` ### Debugging User scripts are not executed unless the output topic is being used somewhere within your layout. To debug your script, first add a Raw Messages panel subscribing to the output topic to your layout. From there, you can either inspect the incoming topic directly, or invoke `log(someValue)` throughout the user script to print values to the _Logs_ section at the bottom of the panel. The only value you cannot `log()` is one that is, or contains, a function definition. You can also log multiple values at once, e.g. `log(someValue, anotherValue, yetAnotherValue)`. The following log statements will not produce any errors: ```typescript const addNums = (a: number, b: number): number => a + b; log(50, "ABC", null, undefined, { abc: 2, def: false }); log(1 + 2, addNums(1, 2)); ``` But these containing function definitions will: ```typescript log(() => {}); log(addNums); log({ subtractNums: (a: number, b: number): number => a - b }); ``` Invoking `log()` outside your `script` function will invoke it once, when your script is registered. Invoking `log()` inside your `script` function will log that value every time your `script` function is called. :::tip For topics publishing at a high rate, using `log()` can slow down the user script. Because a [Plot panel](/docs/visualization/panels/plot) will invoke the user script across all messages in the rendered time-range, `log()` output is not shown when plotting the output of a user script. In this case, use a [Raw Messages panel](/docs/visualization/panels/raw-messages) to view the output message instead. ::: ### Skipping output Do an early (or late) `return` in your function body when you don't want to publish. For example, let's say you only wanted to publish messages when a constant in the input is not 3: ```typescript export const inputs = ["/state"]; export const output = "/foxglove_script/manual_metrics"; export default function script(event: Input<"/state">): { metrics: number } | undefined { if (event.message.constant === 3) { // Do not publish any message return; } return { // Your data here }; } ``` In TypeScript, if you return without a value, it will implicitly return `undefined`. Note the union return type for the `script` function – we've indicated to TypeScript that this function can return `undefined`. ### Using Foxglove message types Import and use types from the [`@foxglove/schemas`](https://github.com/foxglove/foxglove-sdk) package in user scripts: ```typescript export const inputs = ["/imu"]; export const output = "/s_script/json_data"; type Output = { color: Color; pose: Pose }; export default function script(event: Input<"/imu">): Output { return { color: { r: 1, g: 1, b: 1, a: 1 }, pose: { position: { x: 1, y: 1, z: 1 }, orientation: { x: 1, y: 1, z: 1, w: 1 } }, }; } ``` ## Utilities The script selector includes a **Utilities** section with functions that can be imported into any script (e.g. `import { compare } from "./time.ts"`). The `types.ts` utility file is generated from the currently loaded data source, and contains type definitions for all found schemas. :::info Add custom utilities by creating an extension. See [Guide: Register a user-script utility](/docs/extensions/guides/register-user-script-utility) for more details. Extension utilities appear under **Extension Utilities** in the script selector. ::: ## Templates The script selector's **From template** section includes boilerplate for writing common scripts, like one that publishes a `MarkerArray`. ## Settings | General | | | ----------------------- | ------------------------------------------- | | **Auto-format on save** | Auto-format the code in your script on save | ## Controls and shortcuts Press `Cmd` + `s` to save script changes. ## TypeScript Resources - [Basic Types](https://www.typescriptlang.org/docs/handbook/2/basic-types.html) - [Gitbook](https://basarat.gitbook.io/typescript/getting-started/why-typescript) ## When to use a user script or a topic converter extension User scripts and [topic converter extensions](/docs/extensions#message-converters) have similar capabilities, but there are key differences in how they are authored, shared, and how they support third-party packages. | | **User scripts** | **Topic converters** | | ----------------------------- | ---------------- | -------------------- | | **Data transformation** | ✅ | ✅ | | **Create new topics** | ✅ | ✅ | | **Edit directly in Foxglove** | ✅ | ❌ | | **Scoped to a layout** | ✅ | ❌ | | **Reusable across layouts** | ❌ | ✅ | | **Shareable with your team** | ❌ | ✅ | | **Written in your IDE** | ❌ | ✅ | | **Part of your codebase** | ❌ | ✅ | | **Use third-party packages** | ❌ | ✅ | --- ## FoxQL **URL**: /docs/visualization/foxql **Description**: Use FoxQL throughout Foxglove to select and transform values from topics. FoxQL is an expression language for selecting and transforming data in Foxglove. FoxQL expressions can name a topic, navigate into its fields, and transform output. Use FoxQL wherever Foxglove asks for an expression — for example in the Plot, Raw Messages, State Transitions, Gauge, and Indicator panels, and in search conditions. :::note FoxQL was previously referred to as "message path syntax". :::
## Topics and fields Reference this sample message for the `/my_models` topic: ```json { "total": 4, "objects": [ { "width": 10, "height": 20 }, { "width": 15, "height": 30 }, { "width": 20, "height": 40 }, { "width": 25, "height": 50 } ] } ``` Specify the topic name to display all messages for that topic: ```json /my_models => { total: 4, objects: [ { width: 10, height: 20 }, { width: 15, height: 30 }, { width: 20, height: 40 }, { width: 25, height: 50 } ] } ``` To access nested fields, first specify the topic, then use dot notation `.` to drill down into a nested field: ```json /my_models.total => 4 ``` Typing in an expression input will display a list of matching autocomplete options. Any topics or nested fields that contain the input text will be included in this list. ![autocomplete options](/img/docs/visualization/foxql/autocomplete@2x.webp) ### Indexing into an array Index into an array with bracket notation: ```json /my_models.objects[1].width => 15 /my_models.objects[-1].width => 25 ``` Nested arrays (arrays of arrays) are not supported by FoxQL. ## Slices Reference this sample message for the `/my_options` topic: ```json { "colors": [ { "r": 10, "g": 20, "b": 100 }, { "r": 15, "g": 30, "b": 50 }, { "r": 20, "g": 40, "b": 20 }, { "r": 25, "g": 50, "b": 70 }, { "r": 30, "g": 60, "b": 90 } ], "numbers": [3, 5, 7, 9, 10] } ``` Slices will return an array of values: ```json /my_options.colors[1:2] => [{ r: 15, g: 30, b: 50 }, { r: 20, g: 40, b: 20 }] /my_options.numbers[-2:-1] => [9, 10] ``` Using dot notation after an array of objects will map through to access each element's nested field: ```json /my_options.colors[1:3].r => [15, 20, 25] /my_options.colors[:].g => [20, 30, 40, 50, 60] ``` ### Using variables to slice Slice on a [variable](/docs/visualization/variables) by prepending each variable name with a `$`. For example, setting `my_start_idx` to `3` and `my_end_idx` to `4`: ```json /my_options.colors[$my_start_idx:$my_end_idx] => [{ r: 25, g: 50, b: 70 }, { r: 30, g: 60, b: 90 }] /my_options.colors[$my_start_idx:$my_end_idx].b => [70, 90] /my_options.numbers[$my_start_idx:$my_end_idx] => [9, 10] ``` ## Filters Reference these sample messages for the `/my_books` topic: ```json // message 1 { stats: { pages: 100, author: "Beatrice Potter" }, readers: [ { id: 1, name: "Ashley", isCurrentlyReading: true }, { id: 2, name: "Baron", isCurrentlyReading: false }, { id: 3, name: "Charlie", isCurrentlyReading: true } ] } // message 2 { stats: { pages: 210, author: 'Tommy "Two Gun" Simon' }, readers: [ { id: 4, name: "Dana", isCurrentlyReading: true }, { id: 5, name: "Ethan", isCurrentlyReading: false }, { id: 6, name: "Frank", isCurrentlyReading: false } ] } ``` Filter messages based on their fields’ boolean, number, or string values. The following comparison operators are supported: `==`, `!=`, `<`, `<=`, `>`, `>=`. Create filters in FoxQL using curly brackets. Messages that don't match the filter will be skipped entirely: ```json /my_books{stats.pages>200} => { stats: { pages: 210, author: 'Tommy "Two Gun" Simon' } } /my_books{stats.pages==100}.author => "Beatrice Potter" /my_books{stats.pages==500} => // no value returned ``` ### Filtering on nested fields You can also filter on a message’s nested field values using a combination of slices and filters: ```json /my_books.readers[:]{isCurrentlyReading==true}.name => ["Ashley", "Charlie"] // message 1 ["Dana"] // message 2 ``` ### Using variables to filter In addition to filtering on primitive values, you can filter on [variables](/docs/visualization/variables). Setting variable `my_id` to `1`: ```json /my_books.readers[:]{id==$my_id} => { id: 1, name: "Ashley", isCurrentlyReading: true } // message 1 // No value returned for message 2 ``` ### Using multiple filters When you apply multiple filters, only messages that satisfy all filters will be returned (in other words, an `AND` expression): ```json /my_books.readers[:]{id==1}{isCurrentlyReading==true}.name => "Ashley" // message 1 // No value returned for message 2 /my_books.readers[:]{id==1}{isCurrentlyReading==false}.name => // No value returned for message 1 // No value returned for message 2 /my_books.readers[:]{id==5}{isCurrentlyReading==false}.name => // No value returned for message 1 "Ethan" // message 2 ``` ### Other considerations Escaping quotation marks in strings is not supported, but you can use single or double quotes, which allows you to express most strings: ```json /my_books{stats.author=='Tommy "Two Gun" Simon'}.readers[:].name => // No value returned for message 1 ["Dana", "Ethan", "Frank"] // message 2 ``` ### Enum names in filters For fields with enum types, you can filter using the enum name instead of the numeric value: ```json /my_topic{status==MOVING} // filters where status field equals the MOVING enum value /my_topic{status==2} // equivalent numeric filter ``` Enum names are unquoted bare words. They are case-sensitive and must match the enum definition exactly. If the name doesn't match any defined enum value, the filter matches nothing. For boolean fields, the parser recognizes `true` and `false` as boolean values: ```json /my_topic.readers[:]{isActive==true}.name ``` The parser does not treat quoted strings as enum names. Use unquoted identifiers for enums and quoted strings for string comparisons: ```json /my_topic{status==OK} // enum name, resolved to numeric value /my_topic{name=="OK"} // string comparison ``` Variables cannot be used outside of slicing and filtering in expressions. ## Functions {#functions} The [Plot](/docs/visualization/panels/plot#functions), [Raw Messages](/docs/visualization/panels/raw-messages), [State Transitions](/docs/visualization/panels/state-transitions), [Gauge](/docs/visualization/panels/gauge), and [Indicator](/docs/visualization/panels/indicator) panels support appending a function suffix to transform the final value returned by an expression. Support varies by panel: | Function category | Plot | Raw Messages | State Transitions | Gauge | Indicator | | :--- | :---: | :---: | :---: | :---: | :---: | | [Scalar](#functions-category-scalar) ([`@abs`](#functions-abs), [`@degrees`](#functions-degrees), [`@radians`](#functions-radians), and similar functions) | ✓ | ✓ | ✓ | ✓ | ✓ | | [Operand](#functions-category-operand) ([`@add(number)`](#functions-add), [`@sub(number)`](#functions-sub), [`@mul(number)`](#functions-mul), [`@div(number)`](#functions-div)) | ✓ | ✓ | ✓ | ✓ | ✓ | | [Array](#functions-category-array) ([`@length`](#functions-length)) | ✓ | ✓ | ✓ | ✓ | ✓ | | [Vector](#functions-category-vector) ([`@norm`](#functions-norm)) | ✓ | ✓ | ✓ | ✓ | ✓ | | [Struct](#functions-category-struct) ([`@rpy`](#functions-rpy), [`@ypr`](#functions-ypr), [`@yrp`](#functions-yrp), [`@quat`](#functions-quat)) | ✓* | ✓ | ✓* | ✓* | ✓* | | [Time-series](#functions-category-time-series) ([`@derivative`](#functions-derivative), [`@delta`](#functions-delta), [`@timedelta`](#functions-timedelta)) | ✓** | — | — | — | — | * Field access required (for example, @rpy.yaw) for scalar value; ** Timestamp x-axis only. Use `.@functionname` syntax, with an optional operand for functions that accept one: ```json /imu.linear_acceleration.x.@abs /wheel.speed.@mul(3.6) /joint_states.position.@length /imu.linear_acceleration.@norm ``` The sections below define each function (including vector magnitude, quaternion/RPY, and time-series behavior), plus [chaining functions](#functions-chaining) for how suffixes combine. Panel docs summarize what each panel supports and link here for full definitions. ### Scalar {#functions-category-scalar} Scalar functions align with JavaScript's Math functions where applicable. Angles use radians unless you convert with [`@degrees`](#functions-degrees) or [`@radians`](#functions-radians). ### Operand {#functions-category-operand} Operand functions accept one numeric argument in parentheses. Values can also come from [variable references](/docs/visualization/variables). ### Array {#functions-category-array} Use [`@length`](#functions-length) to return the JavaScript `.length` of an array or typed array: ```json /joint_states.position.@length /markers.markers.@length ``` The function returns `0` for an empty array. Use the function suffix syntax (`.@length`) for arrays; field access like `.length` is reserved for message objects that actually define a field named `length`. Previously, paths ending in `.length`, `.byteLength`, or `.buffer` on an array or typed array value resolved to the underlying JavaScript property. Those paths no longer resolve and should be migrated to `.@length` for length lookups; the other properties have no FoxQL equivalent. ### Vector {#functions-category-vector} Computes the Euclidean norm (magnitude) of an object with `x`, `y` (and optionally `z`) numeric properties, or of a numeric array: ```json /imu.linear_acceleration.@norm /gps.velocity.@norm /joint_states.velocity.@norm ``` ### Struct {#functions-category-struct} [`@rpy`](#functions-rpy), [`@ypr`](#functions-ypr), and [`@yrp`](#functions-yrp) convert a quaternion — the standard `(x, y, z, w)` form, with `x`, `y`, `z` the vector part and `w` the scalar part, describing a rotation in right-handed 3D space — into `roll`, `pitch`, and `yaw` Euler angles in radians. They differ only in the order the rotations are composed. In `@ypr` and `@yrp`, `yaw` is the rotation about the fixed Z axis (world-frame heading); in `@rpy`, `yaw` is measured in the body frame after roll and pitch. Append field access (for example, `.yaw` or `.roll`) for a scalar result, and chain [`@degrees`](#functions-degrees) to convert to degrees. XYZ Euler order — intrinsic rotations about X (roll), then Y (pitch), then Z (yaw). ```json /odom.orientation.@rpy /odom.orientation.@rpy.yaw ``` ZYX Euler order — intrinsic rotations about Z (yaw), then Y (pitch), then X (roll). ```json /odom.orientation.@ypr /odom.orientation.@ypr.yaw ``` ZXY Euler order — intrinsic rotations about Z (yaw), then X (roll), then Y (pitch). ```json /odom.orientation.@yrp /odom.orientation.@yrp.yaw ``` **Ranges and wrapping** (applies to [`@rpy`](#functions-rpy), [`@ypr`](#functions-ypr), and [`@yrp`](#functions-yrp)). These functions recover a representative set of angles, not a continuously accumulated one. Each returns the middle axis of its sequence in the range **[−π/2, π/2]** (pitch for `@rpy` and `@ypr`, roll for `@yrp`) and the other two axes in **(−π, π]**; no output ever falls outside ±π. A quaternion encodes only orientation, so an angle whose natural value lies outside these ranges folds back into them — for example a yaw of 270° is reported as −90°. As an orientation rotates continuously, the outer angles therefore wrap, jumping between +180° and −180°: the conversion is evaluated independently per sample with no unwrapping, so expect a sawtooth rather than a continuously accumulating heading. At the middle-axis singularity (pitch = ±90° for `@rpy` and `@ypr`, roll = ±90° for `@yrp`) the remaining two axes are degenerate (gimbal lock); the combined rotation is reported on one axis with the other set to 0, and values near the singularity can change sharply. Converts an RPY object (`roll`, `pitch`, `yaw` in radians) to a quaternion, as the inverse of [`.@rpy`](#functions-rpy) using the same XYZ Euler convention. ```json /odometry.euler.@quat /odometry.euler.@quat.w ``` Append field access (for example, `.w`) when you need a scalar numeric result. ### Time-series {#functions-category-time-series} When an expression includes filters, time-series modifiers use consecutive samples that match the filter (for example, `/imu{sensor_id==3}.@timedelta`; see [`@timedelta`](#functions-timedelta)). Change between consecutive samples. In the Plot panel with a **timestamp** x-axis, plots the change in y-value between consecutive samples: `y[n] - y[n-1]`. Rate of change between consecutive samples. In the Plot panel with a **timestamp** x-axis, plots the rate of change in y-value: `(y[n] - y[n-1]) / (t[n] - t[n-1])`. The time values `t` come from the series' **Timestamp** setting (Log time, ROS header stamp, Publish time, or a custom field timestamp path). Elapsed time in seconds between consecutive samples. In the Plot panel with a **timestamp** x-axis, plots elapsed time in seconds between consecutive samples: `t[n] - t[n-1]`. The time values `t` come from the series' **Timestamp** setting. See the [Plot panel](/docs/visualization/panels/plot#functions) for timestamp sources, filter behavior with consecutive samples, and valid or invalid chains that mix time-series functions with other modifiers. ### Chaining functions {#functions-chaining} The sections above define each function on its own. To combine them, append additional `.@` functions so each step receives the output of the previous one. Evaluation is left-to-right. ```json /odom.orientation.@rpy.yaw.@degrees /odom.orientation.@rpy.roll.@abs ``` For example, [`@rpy`](#functions-rpy) with field access [`.yaw`](#functions-rpy), then [`@degrees`](#functions-degrees), as in the path `.@rpy.yaw.@degrees`, extracts yaw from a quaternion orientation and converts radians to degrees in one expression. Time-series functions ([`@delta`](#functions-delta), [`@derivative`](#functions-derivative), [`@timedelta`](#functions-timedelta)) have extra rules: at most one may appear in a chain, and only [scalar functions](#functions-category-scalar) may follow it. How filters interact with consecutive samples for those modifiers is covered in the note under the [Time-series](#functions-category-time-series) category above. See the [Plot panel](/docs/visualization/panels/plot#functions) for valid and invalid examples with time-series functions in a chain. --- ## Shareable links **URL**: /docs/visualization/shareable-links **Description**: Build and share deep links with your teammates to open Foxglove with specific layouts, data sources, and timeline views. Build and share deep links with your teammates to open Foxglove with specific layouts, data sources, and timeline views. To open Foxglove using a specific layout or data source, construct a "deep link" URL using the format: ```text https://app.foxglove.dev/~/view?param1=value1¶m2=value2 ``` Supported query parameters are documented below, or you can copy any URL while you are using the app. ## Web vs. desktop By default, links will open in the browser. You can open the desktop app instead using the `openIn` parameter: | parameter | type | required | description | | --------- | ------ | -------- | ----------------------------- | | `openIn` | string | | `web` (default)
`desktop` | `openIn=desktop` opens in the Foxglove app. If it is not installed, it presents a landing page that prompts the user to download the app or open the link in the browser. This is the preferred approach to open the Foxglove app. The `foxglove://` URL scheme also opens in the Foxglove app. This fails silently if the Foxglove app is not installed. | prefix | example | | --- | --- | | `https://app.foxglove.dev/~/view` | https://app.foxglove.dev/~/view?ds=foxglove-websocket&ds.url=ws://localhost:8765&openIn=desktop | | `foxglove://open` | foxglove://open?ds=foxglove-websocket&ds.url=ws://localhost:8765 | ## Layouts To load a saved layout (personal or team): | parameter | type | required | description | | ---------- | ------ | -------- | ----------------------------------------- | | `layoutId` | string | ✓ | Foxglove's ID for a remotely saved layout | To get a URL with the correct `layoutId` param, open the web app and click your desired layout in the "Layouts" menu. ## Timestamp | parameter | type | required | description | | --- | --- | --- | --- | | `time` | timestamp
([RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)) | | Timestamp to seek to (for non-live data sources) | ## Data sources To specify your data source, use the `ds` parameter as documented according to your desired data source below. For example, a Rosbridge connection may look like the following: ```text https://app.foxglove.dev/~/view?ds=rosbridge-websocket&ds.url=ws%3A%2F%2Flocalhost%3A9090&layoutId=2644147b-f205-456c-ace9-69466aaac0b7 ``` ### Foxglove WebSocket | parameter | type | required | description | | --------- | ------ | -------- | ------------------------------------------ | | `ds` | string | ✓ | `foxglove-websocket` | | `ds.url` | string | ✓ | WebSocket URL (i.e. `ws://localhost:8765`) | ### Rosbridge | parameter | type | required | description | | --------- | ------ | -------- | ------------------------------------------ | | `ds` | string | ✓ | `rosbridge-websocket` | | `ds.url` | string | ✓ | WebSocket URL (i.e. `ws://localhost:9090`) | ### ROS 1 Native (Supported on desktop only) | parameter | type | required | description | | --- | --- | --- | --- | | `ds` | string | ✓ | `ros1-socket` | | `ds.url` | string | ✓ | [ROS Master URI](http://wiki.ros.org/ROS/EnvironmentVariables#ROS_MASTER_URI) | ### Foxglove | parameter | type | required | description | | --- | --- | --- | --- | | `ds` | string | ✓ | `foxglove-stream` | | `ds.deviceId` | string | See below\* | The device id in Foxglove | | `ds.deviceName` | string | See below\* | User assigned device name | | `ds.start` | timestamp
([RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)) | | Start of data playback range | | `ds.end` | timestamp
([RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)) | | End of data playback range | | `ds.recordingId` | string | See below\* | ID of the recording to open | | `ds.recordingKey` | string | See below\* | Key of the recording to open | | `ds.eventId` | string | See below\* | ID of the event to open | | `ds.sessionId` | string | See below\* | ID of the session to open | | `ds.sessionKey` | string | See below\* | Key of the session to open | :::info One of `ds.deviceId`, `ds.deviceName`, `ds.recordingId`, `ds.recordingKey`, `ds.eventId`, `ds.sessionId`, or `ds.sessionKey` is required. If multiple are provided, Foxglove uses the first one in this order: `ds.recordingId`, `ds.recordingKey`, `ds.deviceId`, `ds.deviceName`, `ds.eventId`, `ds.sessionId`, `ds.sessionKey`. ::: ### Remote files | parameter | type | required | description | | --- | --- | --- | --- | | `ds` | string | ✓ | `remote-file` | | `ds.url` | string | ✓ | URL to `.bag` or `.mcap` file(s). Use multiple parameter entries to specify multiple files. (i.e. `ds.url="…"&ds.url="…"`) | ### Remote data loader | parameter | type | required | description | | --- | --- | --- | --- | | `ds` | string | ✓ | `remote-data-loader` | | `ds.dataLoaderUrl` | string | ✓ | URL of your remote data loader deployment (for example `https://loader.example/`). | | `ds.manifestParams` | string | | Encoded [manifest](/docs/visualization/connecting/cloud-data/remote-data-loader#manifest-endpoint) query parameters (application/x-www-form-urlencoded, without a leading `?`). Example: `ds.manifestParams=recording%3Dfirst-recording`. When present, sibling `ds.*` keys are ignored. | | `ds.*` | string | | **Legacy.** Sibling manifest parameters when `ds.manifestParams` is omitted (for example `ds.recording=…`). Prefer `ds.manifestParams`. | | `ds.compare` | string | | Set to `true` to compare multiple sources. Requires two or more `ds.manifestParams` values. | ## Timeline To share links to the timeline view, use the format: ```text https://app.foxglove.dev/~/timeline?param1=value1¶m2=value2 ``` ### Time range Set the time range displayed on the timeline. Defaults to the last 7 days if omitted. | parameter | type | required | description | | --- | --- | --- | --- | | `start` | timestamp
([RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)) | | Start of the displayed time range | | `end` | timestamp
([RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)) | | End of the displayed time range | ### Device filter Filter which devices are shown on the timeline. | parameter | type | required | description | | -------------- | ------ | -------- | -------------------------------------- | | `deviceFilter` | string | | Filter displayed devices by name or ID | ### Device selection Highlight a time range on a specific device. These parameters are applied on load and then removed from the URL. | parameter | type | required | description | | --- | --- | --- | --- | | `selectedDevice` | string | | Device ID or name | | `selectedStart` | timestamp
([RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)) | | Start of the selected range | | `selectedEnd` | timestamp
([RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)) | | End of the selected range | --- ## Video export **URL**: /docs/visualization/video-export **Description**: Record the current panel layout to an MP4 or WebM file by stepping through your data one frame at a time. Video export records the current panel layout to a video file by stepping the player one frame at a time and capturing the layout's panels after each update. Use it to share a playback session, embed a clip in a report, or view recordings without having to wait for high-bandwidth data buffering. :::note Video export is available only on Enterprise plans, only in the [desktop app](https://foxglove.dev/download), and only for recorded data sources. Live data sources are not supported. ::: ## Export process 1. Open a recording in the desktop app and arrange your layout exactly as you want it to appear in the video. 2. Open the data source dropdown in the top bar and select **Export video**, or open the command palette and run the **Export video** command. 3. Review and configure the export parameters in the dialog and click **Start export**. 4. Choose where to save the video in the file dialog that appears. The video is written directly to this file as the export runs. Dismissing the dialog cancels the export and leaves the parameters dialog open. 5. A progress chip will appear over the playback bar and the panel layout will be locked for the duration of the export. To stop early, click the "Cancel export" button on the chip; the file keeps the frames captured so far and remains a valid, shorter video. 6. When the export finishes, a toast confirms completion (or surfaces an error). If an error occurs after at least one frame was encoded, Foxglove attempts to finalize the captured frames into a valid, shorter video and leaves it at the chosen destination. If no frames were encoded or the partial video cannot be finalized, Foxglove attempts to remove the unusable output file. ## Export parameters | Field | Description | | --- | --- | | Start time (s) | Inclusive start of the playback range, in seconds from the data source epoch. Defaults to the start of the active data source. | | End time (s) | Inclusive end of the playback range, in seconds. Defaults to the end of the active data source. | | Frame rate (fps) | Output video frame rate, between 1 and 120. Defaults to 24. Fractional values are rounded. Higher frame rates produce smoother video and longer export times. | | Playback speed | Multiplier for how much player time is captured per output video frame. `1x` records 1 second of player time per 1 second of video. `2x` and `5x` produce shorter videos that cover the same player time range. | | Format | `MP4 (H.264)` or `WebM (VP9)`. MP4 is preferred for editor compatibility; WebM is the fallback when your browser cannot encode H.264. Formats your browser cannot encode are disabled in the dropdown. | | File name | Suggested output file name for the save dialog. The matching extension (`.mp4` or `.webm`) is appended automatically. You can change the name and location in the save dialog before the export starts. | | Wait for range loading to finish before exporting | Enabled by default. Waits for [range loading](/docs/visualization/playback#range-loading) on the active data source to finish before the first frame is captured, so panels render against fully-loaded data. Disable to start capturing immediately; early frames may not show video keyframes in image panels loaded or plots and state transition panels not fully loaded. | The dialog reports the total number of frames it will record and the approximate output duration based on the current settings. When you start the export, a save dialog prompts you for the destination file, and the video is streamed directly to that file as it records. ## What gets captured Video export captures the **panel layout area** of the workspace at the size it is currently rendered. The playback bar, top navigation, and sidebars are not included in the frame, but their size affects the dimensions of the captured area. To get a clean, predictable output: - Resize the app window to the resolution you want for the final video before starting the export. - Minimize or close the left and right sidebars. - Don't resize the window or interact with the app while the export is running. :::tip Frames are captured at your display's device pixel ratio, so a HiDPI/Retina display produces a higher-resolution video for the same window size. To target a specific output resolution, switch to a display (or a window size) that matches. ::: ### Foxglove watermark Every exported video has a "Made with Foxglove" wordmark composited into the top-right corner of the frame. The watermark is rendered on top of the captured pixels at a fixed position and opacity and is part of every video that ships from this version of the desktop app. It cannot currently be repositioned, resized, or disabled from the UI. ## Troubleshooting ### The Export video menu item is disabled The data source menu's **Export video** item is disabled outside the desktop app and outside Enterprise plans. The command palette hides **Export video** when it is unavailable. The menu tooltip explains which gate applies: - "Video export is only available on Enterprise plans." — switch to an Enterprise seat to enable the feature. - "Video export is available only in the desktop app." — download the [desktop app](https://foxglove.dev/download) and reopen the recording there. If you don't see the menu item at all, the current data source is a live connection. Video export only applies to recorded data sources. ### "Selected video format is not supported by this browser" The browser/system did not advertise WebCodecs support for the selected codec. Switch to the other format (the dropdown disables formats your system cannot encode), or restart the desktop app to retry the capability probe. ### Export takes a long time Export time grows with the time range, the frame rate, and the cost of rendering each frame. To speed up an export: - Reduce the time range to only the portion you need. - Lower the frame rate (24 fps is a good baseline; 60 fps roughly doubles the export time). - Use a higher playback speed multiplier (`2x` or `5x`) to cover the same player range in fewer frames. - Close any expensive panels you do not need in the recording (large 3D scenes, dense plots, etc.) before starting. ### Export looks blurry or the wrong size Frames are captured at your display's device pixel ratio. The output video matches the panel layout area in device pixels (rounded to even numbers). To target a different size, resize the app window — or move it to a different display — before starting the export. The dialog and the progress chip do not show the output dimensions, but the encoded file reports them in its metadata. --- ## Open via CLI **URL**: /docs/visualization/open-via-cli **Description**: Open local, live, and cloud data sources via the command line. [Install the desktop app](https://foxglove.dev/download) to open local, live, and cloud data sources via the command line. ## Local files Once you've installed the desktop app, Foxglove will be the default handler for `.mcap` files. Depending on your operating system, you can open a local file using: ## Live and cloud data sources To open live and cloud data souces from CLI, call `foxglove://open` with any [shareable link](/docs/visualization/shareable-links). For example: This will open the desktop app and connect to `ws://myrobot:8765/` over the Foxglove websocket protocol. :::tip Make sure to wrap the URL in double quotes (") to prevent the shell from interpreting special characters. ::: --- ## Annotate ROS enum fields **URL**: /docs/visualization/annotate-ros-enum-fields **Description**: Annotate your ROS message definitions' enum fields for optimal visualization in Foxglove. ROS messages do not have built-in support for enums, but there are two ways Foxglove can treat constant values as enums: using a separate enum message, or with inline annotations. ## Separate enum messages Traditionally, ROS messages would declare enums by placing named constants in a message definition with a field of the same type. For example, a `PrimaryColor` message might be defined as follows: ```text # In color_msgs/PrimaryColor.msg: uint8 RED=1 uint8 YELLOW=2 uint8 BLUE=3 uint8 data ``` Then another message type could use the `PrimaryColor` type for one of its fields: ```text # In color_msgs/Object.msg color_msgs/PrimaryColor color ``` Foxglove will see that `PrimaryColor` defines the constants for `RED`, `YELLOW`, and `BLUE` adjacent to the `data` field, and they all have the matching `uint8` type, so it will display the named constants alongside the raw value. For example, in the [Raw Messages](/docs/visualization/panels/raw-messages) and [State Transitions](/docs/visualization/panels/state-transitions) panels: ![separate enum message visualized in Raw Message and State Transition panels](/img/docs/visualization/annotate-ros-enum-fields/separate-enum-message@2x.webp) :::tip Separate enum messages require extra indirection when accessing the value, such as `.color.data`. Use inline annotations to avoid this issue. ::: ## Inline enum annotations Foxglove also allows you to _annotate_ enum fields without introducing a separate message for indirection. To annotate an enum field, convert it to its primitive type (such as `uint8`). Then, **add another field named with a `_foxglove_enum` suffix** that uses the enum schema, on the line before the field definition: ```text # In color_msgs/Object.msg: color_msgs/PrimaryColor color_foxglove_enum uint8 color ``` Double underscores (`__foxglove_enum`) are also supported, although they are only valid syntax in ROS 1. You can now remove the `data` field from the enum message, so it has no fields. In ROS 1, the annotation will have no impact on serialization since the enum message is now empty. In ROS 2, empty messages do introduce an extra padding byte in serialization. ```text # In color_msgs/PrimaryColor.msg: uint8 RED=1 uint8 YELLOW=2 uint8 BLUE=3 ``` Now, the color value is stored directly in `.color` rather than `.color.data`. Foxglove will recognize the annotation and display the `PrimaryColor` constant names in the [Raw Messages](/docs/visualization/panels/raw-messages) and [State Transitions](/docs/visualization/panels/state-transitions) panels: ![inline enum annotation visualized in Raw Message and State Transition panels](/img/docs/visualization/annotate-ros-enum-fields/inline-enum-annotation@2x.webp) --- ## Controls and shortcuts **URL**: /docs/visualization/shortcuts **Description**: Use keyboard shortcuts and mouse interactions to navigate the visualization interface. Summary of keyboard shortcuts and mouse interactions. ## Playback controls
Shortcut Description Command
`Space` Toggle playback player.togglePlayback
`←` Seek backward 100ms player.seekBackwardMedium
`→` Seek forward 100ms player.seekForwardMedium
`Shift` + `←` Seek backward 10ms player.seekBackwardSmall
`Shift` + `→` Seek forward 10ms player.seekForwardSmall
`Alt` + `←` (Windows and Linux)
`Option` + `←` (macOS)
Seek backward 500ms player.seekBackwardLarge
`Alt` + `→` (Windows and Linux)
`Option` + `→` (macOS)
Seek forward 500ms player.seekForwardLarge
`Home` Seek to the start of the data player.seekStart
`End` Seek to the end of the data player.seekEnd
`Shift` + `↑` Increase playback speed player.speedUp
`Shift` + `↓` Decrease playback speed player.speedDown
`Ctrl` + `e` (Windows and Linux)
`Cmd` + `e` (macOS)
Open the create event dialog player.createEvent
`Shift` + `Alt` + `←` (Windows and Linux)
`Shift` + `Option` + `←` (macOS)
Move an event endpoint left to the current playback time (while editing an event) player.snapEventTimeLeft
`Shift` + `Alt` + `→` (Windows and Linux)
`Shift` + `Option` + `→` (macOS)
Move an event endpoint right to the current playback time (while editing an event) player.snapEventTimeRight
`Shift` + `Alt` + `←` (Windows and Linux)
`Shift` + `Option` + `←` (macOS)
Move a playback range endpoint left to the current time (while adjusting the range) player.trimRangeLeft
`Shift` + `Alt` + `→` (Windows and Linux)
`Shift` + `Option` + `→` (macOS)
Move a playback range endpoint right to the current time (while adjusting the range) player.trimRangeRight
[View full documentation](/docs/visualization/playback) ## General ### Global
Shortcut Description Command
`Ctrl` + `K` (Windows and Linux)
`Cmd` + `K` (macOS)
Open the [command palette](/docs#command-palette) global.commandPalette.toggle
`Ctrl` + `O` (Windows and Linux)
`Cmd` + `O` (macOS)
Open the [file picker](/docs/visualization/connecting/local-data) global.datasource.openFile
`Ctrl` + `Shift` + `O` (Windows and Linux)
`Cmd` + `Shift` + `O` (macOS)
Open the [connection picker](/docs/visualization/connecting/live) global.datasource.openConnection
`Ctrl` + `J` (Windows and Linux)
`Cmd` + `J` (macOS)
Open or close the agent sidebar global.chat.toggleOpen
### Panel selection
Shortcut Description Command
`Ctrl` + `A` (Windows and Linux)
`Cmd` + `A` (macOS)
Select all panels in the current layout or tab. panel.selectAll
Hover on panel + ` (backtick) Show panel shortcuts (remove from layout or split)
### Panel settings
Shortcut Description Command
`,` Toggle the panel settings sidebar panel.settings.toggleOpen
While inside input + `↑` Increment numeric panel setting values
While inside input + `↓` Decrement numeric panel setting values
Click input + drag right Increment numeric panel setting values
Click input + drag left Decrement numeric panel setting values
### Sidebar navigation
Shortcut Description Command
`[` Toggle left sidebar sidebar.left.toggleVisibility
`]` Toggle right sidebar sidebar.right.toggleVisibility
## Desktop tabs The [desktop app](https://foxglove.dev/download) supports browser-style tabs for working with multiple data sources in a single window.
Shortcut Description
`Ctrl` + `t` (Windows and Linux)
`Cmd` + `t` (macOS)
New tab
`Ctrl` + `w` (Windows and Linux)
`Cmd` + `w` (macOS)
Close tab
`Ctrl` + `Tab` Next tab
`Ctrl` + `Shift` + `Tab` Previous tab
`Ctrl` + `/` (Windows and Linux)
`Cmd` + `/` (macOS)
Go to last active tab
`Ctrl` + `[` (Windows and Linux)
`Cmd` + `[` (macOS)
Navigate back
`Ctrl` + `]` (Windows and Linux)
`Cmd` + `]` (macOS)
Navigate forward
## Panels ### 3D
Shortcut Description Command
`KeyW` Move camera forward panel.threeDee.moveForward
`KeyA` Move camera to the left panel.threeDee.moveLeft
`KeyS` Move camera backward panel.threeDee.moveBackward
`KeyD` Move camera to the right panel.threeDee.moveRight
`Shift`+`KeyW` Rotate camera up panel.threeDee.rotateUp
`Shift`+`KeyA` Rotate camera left panel.threeDee.rotateLeft
`Shift`+`KeyS` Rotate camera down panel.threeDee.rotateDown
`Shift`+`KeyD` Rotate camera right panel.threeDee.rotateRight
Scroll Zoom in and out
Drag Move camera parallel to the ground (x-y plane)
`Alt` + drag (Windows and Linux)
`Option` + drag (macOS)
Move camera parallel to the screen (enable z-axis)
`Shift` + drag, or right-click-drag Rotate the camera around the target position
`1` Re-center the camera on the chosen target frame panel.threeDee.resetCamera
`3` Toggle between 2D bird's-eye view and 3D perspective view panel.threeDee.togglePerspective
`i` Show or hide the object inspector panel.threeDee.toggleInspector
[View full documentation](/docs/visualization/panels/3d) ### Image
Scroll Zoom
[View full documentation](/docs/visualization/panels/image) ### Plot
Shortcut Description Command
Scroll Default zoom for plot type
Scroll with mouse over y-axis
Scroll + `Shift`
Zoom the y-axis only
Scroll with mouse over x-axis
Scroll + `Alt` (Windows and Linux)
Scroll + `Option` (macOS)
Zoom the x-axis only
Scroll + `Shift` + `Alt` (Windows and Linux)
Scroll + `Shift` + `Option` (macOS)
Zoom both axes
`r` (when panel is selected) Reset view to original viewport panel.plot.resetView
`z` (when panel is selected) Toggle zoom tool
[View full documentation](/docs/visualization/panels/plot) ### Publish
Shortcut Description Command
`Ctrl` + `Enter` (Windows and Linux)
`Cmd` + `Enter` (macOS)
(when the JSON editor is focused)
Publish the configured message from the JSON editor
[View full documentation](/docs/visualization/panels/publish) ### Raw Messages
Shortcut Description Command
`Alt` + `[` (Windows and Linux)
`Option` + `[` (macOS)
(when panel is selected)
Step to the previous message panel.stepBackward
`Alt` + `]` (Windows and Linux)
`Option` + `]` (macOS)
(when panel is selected)
Step to the next message panel.stepForward
[View full documentation](/docs/visualization/panels/raw-messages) ### State Transitions
Shortcut Description Command
Scroll Zoom
Scroll + `Shift` Scroll vertically
`r` (when panel is selected) Reset view to original viewport panel.stateTransitions.resetView
`z` (when panel is selected) Toggle zoom tool
[View full documentation](/docs/visualization/panels/state-transitions) ### Topic Graph
Shortcut Description
Scroll Zoom
[View full documentation](/docs/visualization/panels/topic-graph) ### User Scripts
Shortcut Description
`Cmd` + `s` Save script changes
[View full documentation](/docs/visualization/user-scripts) --- ## Migrate from other tools **URL**: /docs/visualization/migrate-from-other-tools **Description**: Migrate your existing developer tools to a Fox Foxglove streamlines robotics development by migrating many commonly used developer tools into modular [panels](/docs/visualization/panels) – all within one integrated development environment. | | tool | Foxglove panel | | --- | --- | --- | | **ROS 1** | [`rosparam`](http://wiki.ros.org/rosparam) | [Parameters](/docs/visualization/panels/parameters) | | | [`rostopic`](http://wiki.ros.org/rostopic) | [Data Source Info](/docs/visualization/panels/data-source-info), [Raw Messages](/docs/visualization/panels/raw-messages) | | | [`rqt_console`](https://wiki.ros.org/rqt_console) | [Log](/docs/visualization/panels/log) | | | [`rqt_graph`](https://wiki.ros.org/rqt_graph) | [Topic Graph](/docs/visualization/panels/topic-graph) | | | [`rqt_image_view`](https://wiki.ros.org/rqt_image_view) | [Image](/docs/visualization/panels/image) | | | [`rqt_plot`](https://wiki.ros.org/rqt_plot) | [Plot](/docs/visualization/panels/plot) | | | [`rqt_publisher`](http://wiki.ros.org/rqt_publisher) | [Publish](/docs/visualization/panels/publish) | | | [`rqt_runtime_monitor`](http://wiki.ros.org/rqt_runtime_monitor) | [Diagnostics](/docs/visualization/panels/diagnostics) | | | [`rviz`](http://wiki.ros.org/rviz) | [3D](/docs/visualization/panels/3d) | | | [`teleop_twist_keyboard`](http://wiki.ros.org/teleop_twist_keyboard) | [Teleop](/docs/visualization/panels/teleop) | | **ROS 2** | [`ros2 param`](https://docs.ros.org/en/galactic/How-To-Guides/Using-ros2-param.html) | [Parameters](/docs/visualization/panels/parameters) | | | [`ros2 topic`](https://docs.ros.org/en/galactic/Tutorials/Topics/Understanding-ROS2-Topics.html#ros2-topic-list) | [Data Source Info](/docs/visualization/panels/data-source-info), [Raw Messages](/docs/visualization/panels/raw-messages) | | | [`rqt_console`](https://docs.ros.org/en/galactic/Tutorials/Rqt-Console/Using-Rqt-Console.html) | [Log](/docs/visualization/panels/log) | | | [`rqt_graph`](https://docs.ros.org/en/galactic/Tutorials/Topics/Understanding-ROS2-Topics.html?#rqt-graph) | [Topic Graph](/docs/visualization/panels/topic-graph) | | | [`rqt_image_view`](https://index.ros.org/p/rqt_image_view/github-ros-visualization-rqt_image_view) | [Image](/docs/visualization/panels/image) | | | [`rqt_plot`](https://index.ros.org/p/rqt_plot/) | [Plot](/docs/visualization/panels/plot) | | | [`rqt_publisher`](https://index.ros.org/p/rqt_publisher/) | [Publish](/docs/visualization/panels/publish) | | | [`rqt_runtime_monitor`](https://index.ros.org/p/rqt_runtime_monitor/github-ros-visualization-rqt_runtime_monitor/) | [Diagnostics](/docs/visualization/panels/diagnostics) | | | [`rviz`](https://github.com/ros2/rviz) | [3D](/docs/visualization/panels/3d) | | | [`teleop_twist_keyboard`](https://github.com/ros2/teleop_twist_keyboard) | [Teleop](/docs/visualization/panels/teleop) | | **Other** | [`urdf-loader` demo](https://gkjohnson.github.io/urdf-loaders/javascript/example/bundle/) | [3D](/docs/visualization/panels/3d#urdf-custom-layer) | --- # Connecting to Data > Connect to live data, local files, or cloud data sources ## Remote data loader **URL**: /docs/visualization/connecting/cloud-data/remote-data-loader **Description**: Visualize queryable data sources using a remote data loader Foxglove can load data from any queryable source using a remote data loader deployment. To get started, build a backend which queries your data and produces [MCAP](https://mcap.dev) on-demand. Then, deploy the remote data loader to cache and process it for visualization. Finally, connect Foxglove to the remote data loader for visualization. ```mermaid graph LR; db(Data Backend) --- rdl(Remote Data Loader); cb(Cache Bucket) --- rdl; rdl --- app(Foxglove App); ``` ## Data backend A data backend is an HTTP service that you build that produces MCAP data on-demand. The Foxglove SDK includes utilities for building services written in Rust, Python, and C++. The data backend HTTP API consists of a manifest endpoint and one or more data endpoints. ### Manifest endpoint The manifest endpoint provides a list of sources to load data from. Requests include a set of user-defined query arguments, which describe the data to be visualized. Users include these query arguments in their [Foxglove URL](/docs/visualization/shareable-links). For example, a simple manifest endpoint might accept a single request parameter: ```text GET /manifest?recording=first-recording ``` and respond with: ```json { "name": "first-recording", "sources": [ { "topics": [ { "name": "/cos", "messageEncoding": "protobuf", "schemaId": 1 } ], "schemas": [ { "id": 1, "name": "point.Point", "encoding": "protobuf", "data": "CjQKC1BvaW50LnByb3RvEgVwb2ludCIWCgVQb2ludBINCgV2YWx1ZRgBIAIoAWIGcHJvdG8z" } ], "url": "/data?recording-id=first-recording", "id": "first-recording", "startTime": "1970-01-01T00:00:00Z", "endTime": "1970-01-01T00:00:10Z" } ] } ``` A full explanation of manifest properties can be found in the [development guide](./remote-data-loader/development-guide#manifest-format). This endpoint also enforces your data access policy. All backend requests include a bearer token in the `Authorization` header. If the manifest endpoint returns `401 Unauthorized`, the user is redirected to an [authentication flow](./remote-data-loader/auth). :::note The manifest endpoint is the source of truth for authorization. The manifest endpoint must not return a data endpoint URL if the requestor is not authorized for that source. Even if the data endpoint would deny access to that user, the remote data loader can still serve that data out of cache. If manifest response caching is enabled, cached manifest responses also cache authorization decisions until the configured time-to-live expires. ::: ### Data endpoints Your data endpoint(s) provide data for visualization. This endpoint serves [MCAP](https://mcap.dev) data to the remote data loader. For details on how to construct that response, check the [development guide](./remote-data-loader/development-guide). ## Remote data loader deployment The remote data loader loads, caches, and merges MCAP data from your backend. You deploy it to a Kubernetes cluster using [Helm](https://helm.sh). It stores cached data in a storage bucket to enable fast seeking and streaming. Check the [deployment guide](./remote-data-loader/deployment) for instructions to set it up. ## Usage Foxglove users connect to the remote data loader by opening a [shareable link](/docs/visualization/shareable-links#remote-data-loader). Put manifest parameters in `ds.manifestParams` as an encoded query string. Foxglove forwards those parameters to your [manifest endpoint](#manifest-endpoint). To open multiple sources, repeat `ds.manifestParams`. They merge onto one timeline by default; set `ds.compare=true` to compare them. Provide a single `ds.dataLoaderUrl`; it applies to every manifest. For example, a request to: ```text https://app.foxglove.dev/~/view?ds=remote-data-loader&ds.dataLoaderUrl=&ds.manifestParams=recording%3Dfirst-recording ``` results in this manifest request: ```text ?recording=first-recording ``` To compare two recordings from the same loader: ```text https://app.foxglove.dev/~/view?ds=remote-data-loader&ds.dataLoaderUrl=&ds.compare=true&ds.manifestParams=recording%3Dfirst-recording&ds.manifestParams=recording%3Dsecond-recording ``` ## Metadata sent to Foxglove The Foxglove app makes one request to Foxglove's control plane when you open a remote data loader: a profile lookup to confirm your organization's plan supports the feature. This request doesn't include the data loader URL, manifest parameters, authentication tokens, or any data from your backend. Your data backend, manifest responses, MCAP content, and authentication all stay between your browser, your remote data loader deployment, and your data backend. None of it reaches Foxglove's control plane. ::::note The [layout](/docs/visualization/layouts#layout-data) you use to visualize this data is a separate concern. If you save that layout, its configuration, including topic and field names, is stored in Foxglove's control plane. :::: ## Build your data backend To get started building with remote data loaders, check out the [development guide](./remote-data-loader/development-guide). --- ## Connecting to data **URL**: /docs/visualization/connecting **Description**: Connect to live robots, open local files, or stream cloud data with Foxglove. Foxglove can visualize data from a variety of sources. To get started, go to the [Foxglove dashboard](https://app.foxglove.dev/~/dashboard) and choose a data source option. ![Data source dialog](/img/docs/getting-started/dialog@2x.webp) ## Data sources | | Description | | --- | --- | | [**Live**](/docs/visualization/connecting/live) | Stream real-time data from a live robotics stack | | [**Local**](/docs/visualization/connecting/local-data) | Open local MCAP, ROS 1 bag, ROS 2, or PX4 ULog files from your computer | | [**Cloud**](/docs/visualization/connecting/cloud-data) | Stream data from the Foxglove Data Platform, or load remote MCAP and ROS 1 bag files via URL | :::tip If you don't have your own data, check out the [Foxglove sample data gallery](https://foxglove.dev/examples). ::: Once connected, Foxglove displays the data source's topics in the sidebar: ![sidebar](/img/docs/getting-started/sidebar@2x.webp) ## Next steps - **ROS users**: Set up the [Foxglove Bridge](/docs/fleet/bridge) for the best live-streaming experience - **Produce your own data**: Use the [Foxglove SDK](/docs/sdk) in Python, Rust, or C++ to record data or publish it live --- ## Development Guide **URL**: /docs/visualization/connecting/cloud-data/remote-data-loader/development-guide **Description**: Build a data backend for a remote data loader deployment This guide walks you through building a simple data backend for a remote data loader. [The Rust remote data loader example](https://github.com/foxglove/foxglove-sdk/blob/main/rust/examples/remote_data_loader_backend/src/main.rs) is a great starting point to understand and build your backend. You can clone and extend that project to fit your needs, or build your own using whatever stack suits your team. This guide uses excerpts from that example throughout. ## Context To recap, the data backend is the HTTP service that serves data to the remote data loader. It consists of: - A manifest endpoint, which provides a set of data endpoint URLs to load from. - Data endpoints. These provide visualization data on request. You can see the request flow in the following sequence diagram: ```mermaid sequenceDiagram participant FG as Foxglove App participant RDL as Remote data loader participant DB as Data backend Note over FG,DB: User opens shareable link with ds.dataLoaderUrl=… and ds.manifestParams=flightId=abc123 FG->>RDL: initialize with flightId=abc123 RDL->>DB: GET ?flightId=abc123 DB->>RDL: Manifest including [dataUrl1, dataUrl2, ...] RDL->>FG: topics and recording metadata Note over FG,DB: User sees app populated with topics and metadata FG->>RDL: get data with flightId=abc123 RDL->>DB: GET ?flightId=abc123 DB->>RDL: Manifest including [dataUrl1, dataUrl2, ...] par RDL->>DB: GET dataUrl1 DB->>RDL: and RDL->>DB: GET dataUrl2 DB->>RDL: and RDL->>FG: Visualization stream end Note over FG,DB: Playback begins ``` ## Manifest endpoint The manifest endpoint handler provides the source list and validates access before the remote data loader requests data. It serves two purposes: 1. To provide a manifest of your source data to the remote data loader. 2. To validate that the user may access all of that source data. :::note The manifest endpoint is the source of truth for authorization. The manifest endpoint must not return a data endpoint URL if the requestor is not authorized for that source. Even if the data endpoint would deny access to that user, the remote data loader can still serve that data out of cache. ::: If manifest response caching is enabled, the remote data loader can reuse a cached manifest response instead of calling this endpoint. Because the manifest endpoint controls access to source data, cached manifest responses also cache authorization decisions. If a token's access is revoked after a manifest response is cached, the remote data loader may continue to use that cached response until the configured time-to-live expires. The cache key is the pair of: 1. The full manifest endpoint URL, including query parameters. 2. The request's auth token. ### Manifest format The manifest is a JSON document with two top-level properties: 1. `name`: A human-readable name for the recording being visualized. This name is what the user sees in the title bar of their app. 2. `sources`: A list of data sources. Each source has these properties: - `url`: A URL that serves the MCAP data for this source. This can be a relative URL, these are interpreted as being relative to the manifest endpoint. This URL does _not_ need to be from the same domain or service as the manifest endpoint, or other source URLs. - `id`: An idempotency key for this source data. This is an explicit cache key that the remote data loader uses when caching the source. If this property is not defined, the `url` property is used as cache key. - `startTime` and `endTime`: timestamps of the earliest and latest message in the MCAP data. These can be estimates. However, `startTime` must not be later than the earliest message, and `endTime` must not be earlier than the latest. These set the time span of the playback bar in the Foxglove app. - `topics` and `schemas`: The set of MCAP [topics](https://mcap.dev/spec#channel-op0x04) and [schemas](https://mcap.dev/spec#schema-op0x03) served by this source. - `supportsRangeRequests`: `true` if the data URL serves a static MCAP file and supports [HTTP range requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests). If this is set to `true`, the `startTime`, `endTime`, `topics` and `schemas` properties are not required and are ignored. A full JSON Schema is available [here](./manifest-schema). :::note The schema IDs used in the `topics` and `schemas` entries do not need to match those served by the data URL. ::: ### Code example See the [manifest handler](https://github.com/foxglove/foxglove-sdk/blob/main/rust/examples/remote_data_loader_backend/src/main.rs#L82) as reference for this section. ```rust if let Err(status) = check_auth(&headers, ¶ms) { return status.into_response(); } ``` The first thing the manifest endpoint does is check user credentials. This check must ensure that the caller is allowed to access all of the data it lists in `sources`. ```rust let mut channels = ChannelSet::new(); channels.insert::("/demo"); let (topics, schemas) = channels.into_topics_and_schemas(); ``` Then, construct the set of Foxglove channels used in this recording. The [ChannelSet](https://docs.rs/foxglove/latest/foxglove/remote_data_loader_backend/struct.ChannelSet.html) helps map those channels into manifest types. ```rust let query = serde_urlencoded::to_string(¶ms).unwrap(); let source = StreamedSource { url: format!("{DATA_ROUTE}?{query}"), id: Some(format!("flight-v1-{query}")), topics, schemas, start_time: params.start_time, end_time: params.end_time, }; let manifest = Manifest { name: Some(format!("Flight {}", params.flight_id)), sources: vec![DataSource::Streamed(source)], }; Json(manifest).into_response() ``` Finally, serialize the manifest. Details worth noting include: - The `id` field must be unique to this source content, otherwise incorrect data may be served from cache. The example re-uses the query string as part of this ID, to ensure that cached data for different query parameters never clash. It also uses a version number to increment whenever the data handling code changes. - The start and end times come directly from query parameters. You may want to instead calculate these from the underlying data. The [SQLite example data backend](https://github.com/foxglove/sqlite-remote-data-loader-example/blob/main/src/main.rs#L146) demonstrates this approach. ## Data endpoint A data endpoint serves MCAP data to the remote data loader. You can use the Foxglove SDK to serialize MCAP in a stream, so you don't have to hold the entire recording in memory. Let's walk through the example code to see this in action: ```rust // Check auth. if let Err(status) = check_auth(&headers, ¶ms) { return status.into_response(); } ``` The data endpoint checks auth information for requests. This is necessary but not sufficient to ensure only authorized users read this data, because the remote data loader might serve it from cache without hitting this endpoint. The manifest endpoint is the source of truth for authorization. ```rust // construct a stream let (mut handle, mcap_stream) = create_mcap_stream(); // Declare channels. let channel = handle.channel_builder("/demo").build::(); ``` The `mcap_stream` writes MCAP data to the HTTP response body. You use the `handle` with Foxglove SDK [Channels](https://docs.rs/foxglove/latest/foxglove/struct.Channel.html) to write messages to this stream. ```rust // Spawn a task to stream data asynchronously rather than buffering it all up front. tokio::spawn(async move { ``` The data handler must return a Response object before the MCAP data is finished serializing, or the entire MCAP will be buffered in memory. To avoid this, the serialization work is done in a separate [tokio task](https://docs.rs/tokio/latest/tokio/task/index.html). ```rust channel.log_with_time( &Vector3 { x: inner.timestamp() as f64, y: 0.0, z: 0.0, }, inner, ); const FLUSH_THRESHOLD: usize = 1024 * 1024; if handle.buffer_size() >= FLUSH_THRESHOLD && let Err(e) = handle.flush().await { tracing::error!(%e, "flush failed"); return; } ``` While writing messages, periodically flush buffered data to the response stream. This serves two purposes: the client receives data incrementally instead of all at once, and memory usage stays bounded instead of growing with the entire recording. :::warning Messages _must_ be written in ascending log time order. The Foxglove app may render incorrectly if data is provided out-of-order. ::: ```rust // Finalize the streamed MCAP and ensure it is sent to the client. if let Err(e) = handle.close().await { tracing::error!(%e, "error closing MCAP stream"); } ``` When you're done serializing messages, finalize the stream. This writes the summary to the HTTP response body and finishes it. ## Error messages When your manifest endpoint or a streamed data source cannot handle the user's request, use the `x-foxglove-error-message` header to provide an informative error message through the Foxglove app. To do this, set the `x-foxglove-error-message` header on the non-2xx response with a short message explaining what went wrong. The app will display this message in the Problems panel. For example, a manifest endpoint that rejects an expired link might respond: ```text HTTP/1.1 400 Bad Request x-foxglove-error-message: This recording has been archived ``` The same applies to a streamed data source that rejects the request for the data itself: ```text HTTP/1.1 404 Not Found x-foxglove-error-message: This file is no longer available ``` Static file sources do not support the header. ## Local development To test out your backend in development, use `minikube` and in-memory caching to deploy a remote data loader locally. ### Setup First, start minikube: ```bash minikube start ``` Verify kubectl is hooked up to minikube: ```bash kubectl config current-context ``` #### Prepare Foxglove namespace Create a namespace to install the remote data loader into. ```bash kubectl create namespace foxglove ``` Once this completes, install the chart. ### Install the remote data loader Prepare a `values.yaml` file to configure the installation: ```yaml globals: manifestEndpoint: cache: enabled: true storageProvider: in_memory bucketName: foxglove-cache # You can disable OAuth 2.0 authentication during development if there is no provider # readily available. disableAuth: true remoteDataLoader: autoscaling: enabled: true deployment: env: # Developing locally, so the data backend will only be accessible over HTTP. - name: "ALLOW_HTTP_SOURCES" value: "true" ``` When you're happy with your `values.yaml`, install the helm chart: ```bash helm repo add foxglove https://helm-charts.foxglove.dev helm repo update helm upgrade --install \ foxglove-remote-data-loader foxglove/remote-data-loader \ --namespace foxglove \ --values ./values.yaml ``` Once this completes the helm chart has been deployed, validate you can reach the remote data loader ingress with curl: ```bash export MINIKUBE_ADDR="http://$(minikube ip)/" curl $MINIKUBE_ADDR -v ``` You can now use a helper script like this one to open visualization from Foxglove. ```python #!/usr/bin/env python3 import os from urllib.parse import urlencode import webbrowser query = urlencode({ "ds": "remote-data-loader", "ds.dataLoaderUrl": os.environ["MINIKUBE_ADDR"], "ds.manifestParams": urlencode({ "flightId": "abc123", }), }) webbrowser.open(f"https://app.foxglove.dev/~/view?{query}") ``` ## Next steps To authenticate your users with an OAuth2 provider, see the [auth guide](./auth). Once your data backend is ready, [deploy](./deployment) your remote data loader to production. --- ## Data loaders **URL**: /docs/visualization/connecting/local-data/data-loaders **Description**: Open files in custom formats directly in Foxglove without converting them first. Data loaders let you open files in custom formats directly in Foxglove. Instead of converting every recording to MCAP up front, package a small WASM binary as a [Foxglove extension](/docs/extensions) and the app can read your format natively. :::info Data loaders are currently in beta. The API may change in future releases. ::: ## When to use a data loader Data loaders fit between two common patterns: converting all of your recordings to MCAP up front, and writing a custom one-off tool to read a single format. Reach for a data loader when: - You have logs in a custom or proprietary format and want to open them in Foxglove without a conversion step. - You want to revive an older format that no other tool reads anymore. - Converting all your recordings to MCAP is impractical. If you only have a handful of files and a clean conversion pipeline, converting to MCAP is still the simplest path. Data loaders are most useful when conversion is expensive, lossy, or inconvenient. ## How they work A data loader is a Foxglove extension that bundles a WASM binary alongside a small registration script. When a user opens a file with the extension your loader registered for, Foxglove instantiates the WASM module and asks it to read the file. At a high level, the loader runs in two phases: 1. **Initialize.** Foxglove hands your loader a list of paths. Your code scans the files and returns a summary of the topics and channels present, their schemas, the time range covered, and per-channel message counts. 2. **Iterate.** As panels request data, Foxglove asks your loader for messages on specific channels in specific time ranges. Your code reads from the file and emits Foxglove messages. Foxglove handles caching, playback, seeking, and panel updates. Your loader only needs to know how to read the file. ## Languages You can write data loaders in: - **Rust**: recommended, with a [step-by-step guide](/docs/extensions/guides/create-data-loader) and a [published SDK](https://docs.rs/foxglove_data_loader/). - **C++**: also supported, with a [published SDK](https://github.com/foxglove/foxglove-sdk/tree/main/cpp/foxglove_data_loader). ## Distribution A data loader is packaged as a regular Foxglove extension (a `.foxe` file). You can install it locally for testing, share it privately with your Foxglove organization, or publish it through the public [extension registry](https://github.com/foxglove/extension-registry). Once installed, anyone on your team can open files in your format directly in Foxglove. ## Next steps - [Build a data loader in Rust](/docs/extensions/guides/create-data-loader) - [Announcing data loaders](https://foxglove.dev/blog/announcing-data-loaders) --- ## Authentication **URL**: /docs/visualization/connecting/cloud-data/remote-data-loader/auth **Description**: Authenticate users to your data backend using OAuth 2.0 The remote data loader supports authenticating with an identity provider that supports the OAuth 2.0 Authorization Code flow with PKCE. The authentication token returned from this flow will then be passed from the remote data loader to your data backend with every request. This behavior can be disabled by setting `DISABLE_AUTH=true` in the remote data loader container's environment. This might be appropriate if you protect access to the remote data loader through an internal VPN. ## Flow overview When a user tries to access your remote data loader from the Foxglove app, an authorization prompt from your identity provider will be shown. After the user logs in and consents to authorize the remote data loader, your identity provider will redirect to the remote data loader so it can do the authorization code exchange and return an auth token to access your data backend. ```mermaid sequenceDiagram participant U as User participant F as Foxglove App participant RDL as Remote Data Loader participant IDP as Your IDP participant DB as Data Backend U->>F: Open visualization F->>RDL: Initialize(flightId=abc123) RDL->>DB: GET ?flightId=abc123 DB->>RDL: 401 Unauthorized RDL->>F: 401 Unauthorized F->>RDL: GET /v1/login RDL->>RDL: Generate PKCE challenge and verifier RDL->>IDP: Authorization code request IDP-->>U: Redirect to login prompt U->>IDP: Authenticate and consent IDP-->>RDL: Authorization code RDL->>IDP: Exchange code for token IDP-->>RDL: Access token RDL-->>F: Set access token as private cookie F->>RDL: Request data RDL->>DB: Request data (with auth token) DB-->>RDL: Data RDL->>F: Data ``` ## Where is the authentication token stored? The authentication token is stored as a cookie on the domain of your remote data loader which is deployed in your infra. The cookie cannot be accessed from the Foxglove app as it is scoped to your domain. You can also optionally provide an encryption key to encrypt the cookie to ensure a malicious client cannot read the access token. ## How is the authentication token used? The authentication token is sent in the Authorization header as `Bearer ` to your manifest endpoint and any source URLs contained in manifests. ## Configuration Configure auth for your remote data loader by creating an `auth-credentials` secret in your cluster. This secret will contain all the environment variables required to configure authentication. To create the secret, start by creating the following file at `auth-credentials.yaml`. ```yaml apiVersion: v1 kind: Secret metadata: name: auth-credentials type: Opaque stringData: # OAuth 2.0 configuration OAUTH_CLIENT_ID: # OAUTH_CLIENT_SECRET: # OAUTH_AUTHORIZE_URL: # for example, https://abc.auth0.com/oauth/authorize OAUTH_TOKEN_URL: # for example, https://abc.auth0.com/oauth/token # The callback URL will be "https:///v1/login/callback" OAUTH_CALLBACK_URL: # for example, https://data.loader.dev/v1/login/callback # Optionally include a cookie encryption key to avoid user tokens being visible to the client. # # This must be at least 64 bytes of cryptographically secure random data encoded as base64. COOKIE_ENCRYPTION_KEY: # secure random secret # Optionally provide a comma-separated list of scopes to request from the auth provider at login. OAUTH_REQUESTED_SCOPES: # comma-separated list of scopes ``` Next apply the secret to your cluster: ```bash kubectl apply -f ./auth-credentials.yaml --namespace foxglove ``` To verify that the authentication flow is working, navigate to `https:///v1/login` in your browser. :::note The OAuth 2.0 Authorization Flow with PKCE does not work with the Foxglove Desktop app, it only works when running Foxglove from the browser. ::: --- ## Live **URL**: /docs/visualization/connecting/live **Description**: Connect to a live robotics stack over remote access, the Foxglove WebSocket protocol, Rosbridge, or a native ROS 1 connection. Connect to a live robotics stack to visualize and interact with data as it's produced: [inspect topic data](/docs/visualization/panels), [publish messages](/docs/visualization/panels/publish), [call services](/docs/visualization/panels/service-call), and [teleoperate](/docs/visualization/panels/teleop). ## Choosing a connection To connect, open the connection dialog: click **Open connection** on your [Foxglove dashboard](https://app.foxglove.dev/), or use the app menu in the top-left corner of the visualization. Then choose one of the connection types below. Remote access is the simplest way to connect, and a good default for most users. Once a gateway is running, you connect by picking the device from a list, with no address to enter or network to configure. Built for real-world networks, it also works behind firewalls and on cellular, keeps latency low on lossy links, and fans out to multiple simultaneous viewers. It's available to all [seat types](/docs/security/seat-types). Choose a direct connection over Foxglove WebSocket, Rosbridge, or ROS 1 when you want every message delivered reliably, or to connect to an existing Rosbridge or native ROS 1 stack. Direct connections require a [developer seat](/docs/security/seat-types). ### Remote access With [remote access](/docs/fleet/remote-access), the device connects out to the Foxglove platform, which brokers the connection to your client, so the device doesn't need to be reachable from your machine. Run a gateway with the [Foxglove Bridge](/docs/fleet/bridge#remote-access) for a ROS stack, or with the [Foxglove SDK](/docs/sdk/remote-access-gateway) for a custom or non-ROS stack. In the dialog, select a device with an active gateway rather than entering an address. ### Foxglove WebSocket A direct connection to a server on the device over your own network, without routing through the Foxglove platform. It delivers every message reliably, but the device must be reachable from your machine (same network, VPN, or port forwarding). Run a server with the [Foxglove Bridge](/docs/fleet/bridge) for a ROS stack, or with the [Foxglove SDK](/docs/sdk/websocket-server) for a custom or non-ROS stack. ### Rosbridge A direct connection to an existing [`rosbridge_suite`](https://github.com/RobotWebTools/rosbridge_suite) deployment over WebSocket, for ROS 1 and ROS 2. Supported for compatibility with existing deployments; for new setups we recommend the Foxglove Bridge instead. ### ROS 1 A direct native connection to a running ROS 1 master, available in the desktop app only. Set `ROS_MASTER_URI` and `ROS_HOSTNAME` to point Foxglove at your master. ## Feature support Live features depend on the software providing the connection: | | Foxglove SDK | Foxglove Bridge | Rosbridge | ROS 1 native | | --- | :-: | :-: | :-: | :-: | | **ROS 1 messages** | ✓ | ✓ | ✓ | ✓ | | **ROS 2 messages** | ✓ | ✓ | ✓ | | | **Other formats (JSON, Protobuf, etc.)** | ✓ | | | | | **[Publish messages](/docs/visualization/panels/publish)** | ✓ | ✓ | ✓ | ✓ | | **[Call services](/docs/visualization/panels/service-call)** | ✓ | ✓ | ✓ | | | **Call actions** | | | | | | **[Read and set parameters](/docs/visualization/panels/parameters)** | ✓ | ✓ | | ✓ | | **[Remote access](/docs/fleet/remote-access)** | ✓ | ✓ | | | --- ## Deployment **URL**: /docs/visualization/connecting/cloud-data/remote-data-loader/deployment **Description**: Deploy a remote data loader ## Architecture Create or use an existing Kubernetes cluster to deploy the remote data loader on. It needs access to a cache bucket (S3, GCS, Azure Blob Storage, etc.) and access to your data backend endpoints. ## Setup To deploy your remote data loader, create an account with a supported provider (AWS, Azure, or GCP) or choose to self-host Kubernetes with an S3-compatible object store like MinIO. Provision your cache bucket and a Kubernetes cluster before installing the Remote data loader. There are no region restrictions if your cloud provider supports these resources. Foxglove provides a set of [Terraform examples](https://github.com/foxglove/terraform-examples) to help with these initial provisioning steps in a supported cloud provider. ### Create cache bucket The Remote data loader requires a bucket for storing a cache of recently-used visualization data. Configure a bucket retention policy to limit storage of these files by age. The optimal retention period varies by use-case, but 7 days is a good starting point. ### Configure cloud credentials Create a service account with read and write access to the cache bucket. The Kubernetes workload will use this service account. See [Configure cloud credentials](./configure-cloud-credentials) for details on how to provide this credential to the deployment. This section also covers configuration of S3-compatible object storage. ### Prepare a values file Create a `values.yaml` file to configure the installation. Include a `storageProvider` (`aws`, `azure`, `google_cloud`, or `s3_compatible`) and the name of your cache bucket: ### Optional manifest response cache You can enable an in-memory manifest response cache to reduce repeated calls to your manifest endpoint for the same user and query. ```yaml globals: manifestCache: ttlMs: 30000 maxCapacity: 1024 ``` Set `globals.manifestCache.ttlMs` greater than zero to enable the cache. The maximum TTL allowed is 30 seconds. You can also set `globals.manifestCache.maxCapacity` to control the maximum number of cached manifest responses. The default capacity is 1024. The cache key is the pair of: 1. The full manifest endpoint URL, including query parameters. 2. The request's auth token. :::warning The manifest endpoint is the source of truth for authorization. Caching a manifest response also caches the authorization decision for that manifest endpoint URL and auth token. If access for a token is revoked, the remote data loader may continue using a cached manifest for that token until `globals.manifestCache.ttlMs` expires. Choose a time-to-live that matches how quickly access revocation must take effect, or leave manifest caching disabled when revocation must take effect immediately. ::: ### Install Helm will install the Remote data loader deployment to your current Kubernetes [context](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#context). ```shell helm repo add foxglove https://helm-charts.foxglove.dev helm repo update helm upgrade --install foxglove-remote-data-loader foxglove/remote-data-loader \ --values ./values.yaml \ --namespace foxglove \ --create-namespace ``` View info about the deployment: ```shell helm list -n foxglove ``` ### Ingress for data streaming The deployment installs an [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/), exposing the remote data loader as an HTTP endpoint to download available data. To reach the ingress from outside the cluster, assign a DNS name and HTTPS endpoint. Ensure that the HTTP endpoint is reachable from your organization's network or any network needing access to your data. This does not necessarily need to be exposed to the public internet; for example, if all clients will download data from within a VPN. :::note How to assign a DNS name and HTTPS endpoint to the ingress is specific to your organization and cloud environment. ::: You can configure the ingress using your values file. Below are the default values. ```yaml ingress: enabled: true className: annotations: {} ``` To completely disable the built-in Ingress, set `ingress.enabled` to false. You will need to provide your own Ingress resource for the deployment to function. You also need to set up an ingress controller in the cluster. Install the recommended controller for your managed Kubernetes provider, or choose a third-party implementation. The [Kubernetes project maintains a list of options here](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/#third-party-ingress-controllers). ## Use the Remote data loader You can visualize data from your HTTP sources by opening shareable links that direct Foxglove to your source. See the [Remote data loader](/docs/visualization/shareable-links#remote-data-loader) section for instructions for composing those links. ## Support For additional support contact us at [support@foxglove.dev](mailto:support@foxglove.dev). --- ## Local **URL**: /docs/visualization/connecting/local-data **Description**: Load local ROS 1 (.bag) and MCAP (.mcap) files for visualization. Load local recordings for visualization. :::note Opening local files requires a [developer seat](/docs/security/seat-types). ::: ### Supported formats | | File extension | | -------------------------------------------------------------------- | --------------- | | **[ROS 1](/docs/getting-started/frameworks/ros1?modality=recorded)** | `.bag` | | **[ROS 2](/docs/getting-started/frameworks/ros2?modality=recorded)** | `.mcap`, `.db3` | | **[MCAP](/docs/visualization/connecting/local-data)** | `.mcap` | | **[PX4 ULog](/docs/getting-started/frameworks/px4)** | `.ulg` | :::tip You can use [data loaders](./local-data/data-loaders) to add support for custom file formats like CSV, PCAP, or any other format you need. ::: :::tip Since legacy ROS 2 `.db3` files do not contain their message definitions, we recommend first converting those into self-contained MCAP files before loading into Foxglove. ::: ## Links and resources - [MCAP](https://mcap.dev) - [Introducing the MCAP File Format](https://foxglove.dev/blog/introducing-the-mcap-file-format) - [Data loaders](./local-data/data-loaders) --- ## Cloud **URL**: /docs/visualization/connecting/cloud-data **Description**: Load remote data via URL. Load cloud files or resources for visualization. Supported cloud resources include [Foxglove Data Platform](#foxglove-data-platform), [HTTP files](#http-remote-files), and [Remote data loaders](#remote-data-loader). ## Foxglove Data Platform The Foxglove Data Platform is a cloud-based data storage and management solution that allows you to store and manage your data in the cloud. It is a fully-managed control plane paired with a fully-managed data plane or self-hosted data plane. The supported formats for Foxglove Data Platform are: **[MCAP (.mcap)](/docs/visualization/connecting/local-data)** and **[ROS 1 (.bag)](/docs/getting-started/frameworks/ros1?modality=recorded)** After [importing data to Foxglove](/docs/data/importing-data), select individual resources to visualize on the [Timeline](https://app.foxglove.dev/~/timeline), [Events](https://app.foxglove.dev/~/events), or [Recordings](https://app.foxglove.dev/~/recordings) pages: ![Visualize on Recordings page](/img/docs/getting-started/visualize-recordings@2x.webp) To visualize multiple recordings, sessions, or events together on a shared timeline, select them and choose **Compare** to open them in [Comparison mode](/docs/visualization/comparison-mode#starting-a-comparison). ## HTTP Remote Files HTTP Remote Files are files hosted on a remote HTTP server that supports range requests and CORS. This allows Foxglove to load the file in chunks, rather than loading the entire file into memory. The supported formats for HTTP Remote Files are: **[MCAP (.mcap)](/docs/visualization/connecting/local-data)** and **[ROS 1 (.bag)](/docs/getting-started/frameworks/ros1?modality=recorded)**
Cross-Origin Resource Sharing (CORS) setup To load remote data files, you must host your files in a server or cloud provider that supports Cross-Origin Resource Sharing (CORS) and accepts range requests. When loading remote data into Foxglove for playback and analysis, we recommend hosting your files in a cloud provider like [Amazon Simple Storage Service (S3)](https://aws.amazon.com/pm/serv-s3/), [Google Cloud Storage (GCS)](https://cloud.google.com/storage), or [Azure Storage](https://azure.microsoft.com/en-us/product-categories/storage/). You can also host files on your own server, but it may be difficult and time-consuming to set up support for [Cross-Origin Resource Sharing (CORS)](https://web.dev/cross-origin-resource-sharing/) and [range requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests). If your data is sensitive, generate and use a signed URL – make sure that you point to the resource directly, as redirects will not work with CORS: - **S3** – [Sharing objects using presigned URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html) - **GCS** – [Signed URLS](https://cloud.google.com/storage/docs/access-control/signed-urls) - **Azure Storage** – [Grant limited access to Azure Storage resources using shared access signatures (SAS)](https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview) This signed URL will work for a limited period of time. You can set up your server to only sign URLs for authenticated users. Finally, set up your CORS configuration. Check out the following example of a [Terraform](https://www.terraform.io/) config for an S3 bucket ([docs](https://registry.terraform.io/providers/hashicorp%20%20/aws/latest/docs/resources/s3_bucket_cors_configuration)): ```terraform cors_rule { allowed_methods = ["GET", "HEAD", "OPTIONS"] allowed_origins = ["https://app.foxglove.dev", "https://embed.foxglove.dev"] allowed_headers = ["*"] expose_headers = ["ETag", "Content-Type", "Accept-Ranges", "Content-Length"] } ``` And a Terraform config for a GCS bucket ([docs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket#cors)): ```terraform cors { origin = ["https://app.foxglove.dev", "https://embed.foxglove.dev"] method = ["GET", "HEAD", "OPTIONS"] response_header = ["ETag", "Content-Type", "Accept-Ranges", "Content-Length"] } ```
## Remote data loader Foxglove can visualize data from any queryable source through a data backend and [remote data loader](./cloud-data/remote-data-loader) deployment. To get started, build a backend which queries your data and produces [MCAP](https://mcap.dev) on-demand. Then, deploy the remote data loader to load and cache that data. Finally, users connect Foxglove to the remote data loader for visualization. For more information, check out the detailed docs [here](./cloud-data/remote-data-loader). --- ## Configure cloud credentials **URL**: /docs/visualization/connecting/cloud-data/remote-data-loader/configure-cloud-credentials **Description**: Authenticate the remote data loader to the cache bucket Your remote data loader needs read and write credentials for a cache bucket. Choose your cloud provider below for configuration steps. --- ## Manifest schema **URL**: /docs/visualization/connecting/cloud-data/remote-data-loader/manifest-schema **Description**: A JSON Schema for validating manifests ```json { "$schema": "https://json-schema.org/draft-07/schema", "type": "object", "properties": { "sources": { "type": "array", "items": { "type": "object", "properties": { "supportsRangeRequests": { "const": true, "description": "If provided, indicates that the dataUrl supports byte-range requests via the HTTP Range header." }, "url": { "type": "string", "description": "The URL to load message data from. This URL should respond to a GET request with MCAP-formatted data for the described topics." }, "id": { "type": "string", "description": "A unique identifier for this source which is used as an idempotency key when caching. If omitted, the `url` is used as an idempotency key." }, "topics": { "type": "array", "items": { "type": "object", "required": ["name", "messageEncoding"], "properties": { "name": { "type": "string" }, "messageEncoding": { "type": "string" }, "schemaId": { "type": "integer", "description": "ID of the schema of messages on this topic, matching the `id` of an element of the `schemas` array. Omitting this property means the messages on this topic are schemaless.", "minimum": 1, "maximum": 65535 } } } }, "schemas": { "type": "array", "items": { "type": "object", "description": "MCAP Schema record. See spec at https://mcap.dev/spec#schema-op0x03 for field meanings.", "required": ["id", "name", "encoding", "data"], "properties": { "id": { "type": "integer", "minimum": 1, "maximum": 65535 }, "name": { "type": "string" }, "encoding": { "type": "string" }, "data": { "type": "string", "description": "base64-encoded content of the `data` field", "contentEncoding": "base64" } } } }, "startTime": { "type": "string", "format": "date-time", "description": "Timestamp of the first expected message" }, "endTime": { "type": "string", "format": "date-time", "description": "Timestamp of the last expected message" } }, "oneOf": [ { "required": ["url", "supportsRangeRequests"] }, { "required": ["url", "topics", "schemas", "startTime", "endTime"] } ] } }, "name": { "type": "string", "description": "A human-readable name to associate with this recording." } }, "required": ["sources"], "description": "A set of data sources to visualize." } ``` --- ## Troubleshooting network errors **URL**: /docs/visualization/connecting/troubleshooting **Description**: Diagnose and resolve "Failed to fetch" and other network errors when connecting to or streaming data in Foxglove. When Foxglove tries to talk to a server that it can't reach, errors often come through with a generic "Failed to fetch" message. Your browser intentionally hides the underlying cause for security reasons, so Foxglove can't tell exactly what went wrong. If you encounter one of these errors, work through the following steps to troubleshoot the issue. ## 1. Check your internet connection Confirm that your machine is online and can reach the wider internet, or your internal network. If you're on an unstable connection, a transient drop may cause a request to fail. Retry the request or refresh the page to try again. If the issue persists try disconnecting and reconnecting to the network. ## 2. Connect to your VPN If your organization hosts Foxglove, or uses a self-managed [primary site](/docs/data/primary-sites), [remote data loader](/docs/visualization/connecting/cloud-data/remote-data-loader), or [remote files](/docs/visualization/connecting/cloud-data) on your network, you may need to be connected to your corporate network through a VPN. If you're unable to reach these services: - Make sure your VPN client is connected. - Ensure you can reach other internal services. - Reconnect the VPN if it has been idle, then retry the connection or reload the page. ## 3. Enable local network access When you connect to a device on your own network or through a VPN, your operating system and browser must allow Foxglove to reach the local network. If you've been prompted to allow local network access, click **Allow** to ensure you can connect. If you've previously disallowed this setting, go to the site settings page in your browser for Foxglove and ensure the local network permission is set to **Allow**. ## 4. Check for a WAF or gateway login If your self-managed primary site, remote data loader or remote files sit behind a Web Application Firewall (WAF), proxy, or authentication gateway, requests can be silently blocked until you authenticate. - Open the gateway or SSO login page in a normal browser tab and sign in, then return to Foxglove and retry the connection or reload the page. - If a WAF is rate-limiting or blocking the requests, ask your infrastructure team to ensure Foxglove and your self-managed data is accessible. ## 5. Check the server's CORS configuration When you load a remote file by URL, it's served from a different origin than Foxglove, so the server must return the correct CORS headers. If it doesn't, the browser blocks the request and the remote file fails to open. - Make sure the server responds with an `Access-Control-Allow-Origin` header that permits the Foxglove origin (`app.foxglove.dev`, or your self-hosted domain). - Foxglove loads files using HTTP Range requests, so the server must also expose the range headers: include `Accept-Ranges` in `Access-Control-Expose-Headers`, and confirm the response carries an `Accept-Ranges: bytes` header. ## Still stuck? If you've worked through the steps above and the error persists: 1. Note the exact URL shown in the error details (expand the problem in the problems panel). 2. Check your browser's developer console (Network tab) for failed requests and their status. 3. Share these details with your infrastructure team, who can confirm whether the endpoint is reachable from your network, or reach out to [Foxglove support](https://foxglove.dev/contact). --- # Panels > Individual panel types for data visualization ## Panels **URL**: /docs/visualization/panels **Description**: Panels are modular visualization interfaces that can be configured and arranged into Foxglove layouts. Panels are modular visualization interfaces that can be configured and arranged into Foxglove layouts. Find the full list of available panels in the "Add panel" menu. ![panels thumbnail](/img/docs/visualization/panels/thumbnail.png) ### Add to layout Click any panel name to add it to your current layout. Alternatively, drag and drop a panel name into your current layout to add it to a specific location. Each panel's top bar contains the following: - **Menu** – Common panel actions, like splitting the panel or changing it to another panel type - **Expand or collapse** – Click the expand or collapse icon, or double-click the panel title - **Settings** – Click the cog icon or press `,` to open the panel's settings Use the top bar to easily drag a panel around the layout. ### Edit settings Click the cog icon in each panel's top bar to view and edit its settings in the sidebar. The selected panel will be designated with a purple border. ![panel settings tab](/img/docs/visualization/panels/settings-tab.png) Clicking different panels in your layout while the sidebar's panel settings are open will switch to its settings. ### Drag-and-drop topics Filter your data source's topics – and nested fields addressed with [FoxQL](/docs/visualization/foxql) – using the sidebar's Topics tab: ![panel settings tab](/img/docs/visualization/panels/filter-topics.png) Drag-and-drop filter results into certain panels for easy instant visualization: - **All topics** – [Raw Messages](/docs/visualization/panels/raw-messages) and [Table](/docs/visualization/panels/table) panels - **Image and image annotation topics** – [Image](/docs/visualization/panels/image) panel - **Expressions** – [Plot](/docs/visualization/panels/plot) and [State Transitions](/docs/visualization/panels/state-transitions) panels To drag-and-drop multiple expressions, use `Shift` to select a range of adjacent items, or `Ctrl` (`Cmd` on macOS) to select multiple non-adjacent items: ![panel settings tab](/img/docs/visualization/panels/multi-drag-and-drop.png) You can also drag topics and expressions from a panel's settings sidebar. For example, drag a series from a Plot panel's settings to quickly duplicate it into another Plot panel, or drag a topic from a 3D panel's settings into a Raw Messages panel to inspect the data. ## Controls and shortcuts - `,` – Open the panel settings sidebar - `Cmd` + `a` – Select all panels in the current layout - `Cmd` + `b` – Close sidebar if open - Hover on panel + `` ` `` – Show panel shortcuts (remove from layout or split) - Click input + Drag right – Increment numeric panel setting values - Click input + Drag left – Decrement numeric panel setting values ## Import/export settings Each panel stores its settings (also known as panel state) as a JSON object. When developing panels, it can be helpful to manually inspect or adjust the settings of a given panel. To view or edit a panel's settings, click "Import/Export Settings" in the panel settings menu: ![panel settings menu](/img/docs/visualization/panels/panel-settings-menu@2x.webp) This will open a modal displaying the panel's settings. Note that these settings are only for the current panel. To import or export an entire layout, see [Layouts](/docs/visualization/layouts#import-and-export). --- ## 3D panel **URL**: /docs/visualization/panels/3d **Description**: Display markers, entities, camera images, meshes, URDF models, and more in a 3D scene. Display markers, entities, camera images, meshes, URDF models, and more in a 3D scene. ![3d panel](/img/docs/visualization/panels/3d/panel.png) In [Comparison mode](/docs/visualization/comparison-mode#reading-the-comparison), use the **Data source** picker in the panel toolbar to choose which slot to display. ## Visualization Visualizing objects in the 3D scene correctly requires your data to conform to certain conventions. When you enable a topic for visualization, the 3D panel will subscribe to the topic, receive messages on the topic in **log time** order (see: [playback](/docs/visualization/playback#message-ordering)), and visualize the latest message on each enabled topic using the `timestamp` or `header.stamp` (ROS) information in the message. To render objects into the scene, there must exist a transform path from the object's coordinate frame to the display frame _at or before the timestamp of the object being visualized_. For objects this timestamp comes from the `timestamp` or `header.stamp` (ROS) field within the message, NOT the log time (aka receive time). Like other messages, transform messages are received in log time order but stored and accessed by the `timestamp` or `header.stamp` field within the message. This convention allows your robot to receive sensor data and transform state, perceive and identify objects in the scene, and produce a message describing the scene at some time that relates to the sensor data or processing rather than the log time which might come later. This provides a mechanism for more accurately representing the scene as the robot may have experienced it. ## Supported messages The 3D panel can visualize an assortment of different messages. To visualize a topic, the messages on that topic must conform to one of the known message schemas listed below. ### Camera field-of-view Calibration parameters for your scene's camera. | framework | schema | | --- | --- | | ROS 1 | [`sensor_msgs/CameraInfo`](https://docs.ros.org/en/noetic/api/sensor_msgs/html/msg/CameraInfo.html) | | ROS 2 | [`sensor_msgs/msg/CameraInfo`](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/CameraInfo.msg) | | Custom | [`foxglove.CameraCalibration`](/docs/sdk/schemas/camera-calibration) | ### Grid 2D colored grids. ![grid](/img/docs/visualization/panels/3d/occupancygrid.png) ![gridmap](/img/docs/visualization/panels/3d/gridmap.png) | framework | schema | | --- | --- | | ROS 1 | [`nav_msgs/OccupancyGrid`](https://docs.ros.org/en/noetic/api/nav_msgs/html/msg/OccupancyGrid.html), [`map_msgs/OccupancyGridUpdate`](https://docs.ros.org/en/noetic/api/map_msgs/html/msg/OccupancyGridUpdate.html), [`grid_map_msgs/GridMap`](http://docs.ros.org/en/jade/api/grid_map_msgs/html/msg/GridMap.html) | | ROS 2 | [`nav_msgs/msg/OccupancyGrid`](https://github.com/ros2/common_interfaces/blob/master/nav_msgs/msg/OccupancyGrid.msg), [`map_msgs/msg/OccupancyGridUpdate`](https://github.com/ros-planning/navigation_msgs/blob/rolling/map_msgs/msg/OccupancyGridUpdate.msg), [`grid_map_msgs/msg/GridMap`](https://docs.ros.org/en/ros2_packages/jazzy/api/grid_map_msgs/msg/GridMap.html) | | Custom | [`foxglove.Grid`](/docs/sdk/schemas/grid) | #### `foxglove.Grid` and `GridMap` settings | field | description | | --- | --- | | Color mode | One of:
Flat: solid color
Color map: pre-defined color palette
Gradient: smooth transition between two custom colors
RGBA (separate fields): use embedded color from each cell's `red`, `green`, `blue`, and `alpha` fields (see below)

Note: only `Color Map` and `Gradient` are currently supported for `GridMap` messages | | Flat color | Only shown if "Color mode" is set to "Flat"; hex code for color of each cell | | Color field
Color layer
| Only shown if "Color mode" is not set to "Flat"; numeric field in message used for coloring logic | | Color map | Only shown if "Color mode" is set to "Color map"; "Turbo" (Google) or "Rainbow" (RViz); for mapping "Color field" values to colors | | Gradient | Only shown if "Color mode" is set to "Gradient"; The two colors to interpolate between, based on the "color field" | | Opacity | Only shown if "Color mode" is set to "Color map" or "BGR (packed)"; sets alpha value for all cells | | Value min | Only shown if "Color mode" is not set to "Flat" and the message is not a `GridMap` schema; minimum value used to normalize incoming grid's "Color field" values | | Value max | Only shown if "Color mode" is not set to "Flat" and the message is not a `GridMap` schema; maximum value used to normalize incoming grid's "Color field" values | | Elevation field
Elevation layer
| Optional field to displace each grid cell in the Z axis; if not set, elevation is 0 for all cells | | Interpolation method | Only shown if "Elevation" is set; either Linear or Nearest Neighbor. Determines how elevation and color values are sampled spatially across the grid. | | Show lines | Only shown if "Elevation" is set; whether to render subtle grid cell borders | | Line color | Only shown if "Show lines" is enabled; color of the cell grid lines | | Lighting | Only shown if "Elevation" is set; enables shading from simulated directional lighting, based on elevation surface normals | | Frame lock | "On" means the grid is locked to the frame specified by its `frame_id`, and will move as that frame's transforms change. "Off" means the grid is relative to the fixed frame and will not move after it is first displayed | | Draw behind | "On" means the grid is rendered before the rest of the scene, so it appears behind other scene elements regardless of their relative depth. "Off" means the grid is rendered normally | ##### RGBA (separate fields) color mode Each cell can contain color information in four separate fields. **All four fields must be present** with the exact names `red`, `green`, `blue`, and `alpha`, of any numeric type: - **Floating-point values** — 0–1 range - **Unsigned integer values** — Maximum possible range (e.g. 0–255 for a `UINT8` field) - **Signed integer values** — `-max` to `max` (e.g. −127 to 127 for an `INT8` field; a value of −128 is treated as identical to −127) :::tip The field names must match exactly (though the order doesn't matter). When these fields are detected, additional "Color Mode" options will become available. ::: #### `nav_msgs/OccupancyGrid` settings | field | description | | --- | --- | | **Color mode** | One of:
Costmap: pre-defined RViz color palette. Cannot customize settings further.
Custom: custom color palette using settings below | | **Min color** | Color corresponding to minimum cell value (0) | | **Max color** | Color corresponding to maximum cell value (100). Note that cells with value exactly 100 are displayed as fully transparent. | | **Unknown color** | Color corresponding to unknown cell value (−1) | | **Fallback color** | Color corresponding to cell values that fall outside the range from −1 to 100 | | **Frame lock** | "On" means the grid is locked to the frame specified by its `frame_id`, and will move as that frame's transforms change. "Off" means the grid is relative to the fixed frame and will not move after it is first displayed. | #### Incremental occupancy grid updates The 3D panel supports `map_msgs/OccupancyGridUpdate` messages for incremental costmap updates. ROS's `costmap_2d` node publishes two topics per costmap: - A **full grid** (`nav_msgs/OccupancyGrid`) at a low rate (typically 1–2 Hz) - **Partial updates** (`map_msgs/OccupancyGridUpdate`) at a higher rate (typically 3–12 Hz) The panel automatically pairs update topics to their base grid using the `_updates` suffix convention (e.g. `/costmap` and `/costmap_updates`). Update topics do not appear as separate entries in the settings sidebar — they are consumed internally. :::note Performance Each update triggers a full texture rebuild of the entire grid. The cost scales linearly at approximately **13 ns per cell**. Grids up to ~1.28 million cells (~1130×1130) fit within a single frame at 60 fps. Larger grids (e.g. 2000×2000 global costmaps) may cause frame drops during updates. ::: ### Image Images displayed in the 3D scene, using the corresponding [Camera field-of-view](#camera-field-of-view) messages. | framework | schema | | --- | --- | | ROS 1 | [`sensor_msgs/Image`](https://docs.ros.org/en/noetic/api/sensor_msgs/html/msg/Image.html) | | ROS 2 | [`sensor_msgs/msg/Image`](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/Image.msg) | | ROS 1 | [`sensor_msgs/CompressedImage`](https://docs.ros.org/en/api/sensor_msgs/html/msg/CompressedImage.html) | | ROS 2 | [`sensor_msgs/msg/CompressedImage`](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/CompressedImage.msg) | | Custom | [`foxglove.RawImage`](/docs/sdk/schemas/raw-image) | | Custom | [`foxglove.CompressedImage`](/docs/sdk/schemas/compressed-image) | #### Render modes Image topics in the 3D panel support three render modes. Each mode has context-specific settings. - [**Default**](#default) – Projects the image onto a distorted plane at a fixed distance from the camera. - [**Plane projection**](#plane-projection) – Projects the image onto the XY plane of a selected coordinate frame. - [**Depth map**](#depth-map) – Renders depth images as point clouds. #### Default The default render mode displays images using standard camera projection. This mode shows the following settings: | field | description | | --- | --- | | Render mode | Select how the image is rendered: **Default**, **Plane projection**, or **Depth map**. | | Distance | Distance from the camera at which the image is projected | | Planar projection factor | Controls the planar projection effect, from zero to one. Zero results in a curved plane with all points equidistant to camera origin. One results in a flat plane orthogonal to the camera's z (depth) axis. | #### Depth map The 3D panel can render uncompressed depth images as point clouds, enabling visualization of depth camera data directly in 3D space. To render a depth image as a point cloud, you need: - An uncompressed depth image topic with a supported encoding (`32FC1`, `16UC1`, `MONO16`, `8UC1`, `MONO8`, or `8UC3`), or a compressed depth image topic using `compressedDepth` 16-bit grayscale PNG - A corresponding [Camera field-of-view](#camera-field-of-view) message with camera calibration data Enable the depth image topic in the panel's topic settings, then set the **Render mode** to **Depth map**. Compressed depth images must be 16-bit grayscale PNG payloads (for example, ROS `compressedDepth` transport using `sensor_msgs/CompressedImage` with a `compressedDepth` format string). Depth values are interpreted as millimeters (see **Distance type** below for how values are projected into 3D). Other compressed formats (JPEG, color PNG, or 8-bit PNG) are not supported and will show an error. | field | description | | --- | --- | | Render mode | Select how the image is rendered: **Default**, **Plane projection**, or **Depth map**. Only one mode can be active at a time. Settings shown below are only visible when "Render mode" is set to "Depth map". | | Distance type | How to interpret pixel values: **Z-axis** (default) interprets each pixel as the distance along the camera's optical axis, while **Euclidean** interprets each pixel as the straight-line distance from the sensor origin. Use **Euclidean** for sensors that report range (e.g., time-of-flight cameras). | | Point size | Size of each rendered point in pixels | | Depth scale | Scale factor to convert depth values to meters. Defaults to 1.0 for 32-bit float images (`32FC1`) and 0.001 for all other encodings (`16UC1`, `MONO16`, `8UC1`, `MONO8`, `8UC3`). Customize this to match your camera system's depth scale configuration. | | RGB topic | Sibling RGB image topic to colorize the depth point cloud. When set, each point in the cloud is colored using the corresponding pixel from the RGB image. The RGB image can have a different resolution than the depth image, and Foxglove uses bilinear interpolation to sample colors accurately. Select "None" to use distance-based coloring instead. | | Color mode | Only shown when **RGB topic** is set to "None". One of:
Color map: color points by depth using a pre-defined color palette
Gradient: color points by depth using a smooth transition between two custom colors
Flat: render every point with the same custom color | | Flat color | Only shown if **Color mode** is set to **Flat**. Sets the point color and alpha. | | Color map | Only shown if **Color mode** is set to **Color map**. Maps depth values to "Turbo" (Google) or "Rainbow" (RViz). | | Gradient | Only shown if **Color mode** is set to **Gradient**. Sets the start and end colors for depth-based coloring. | | Opacity | Only shown if **Color mode** is set to **Color map**. Sets alpha for all points. | | Value min | Only shown if **Color mode** is **Color map** or **Gradient**. Minimum depth value used to normalize point colors. | | Value max | Only shown if **Color mode** is **Color map** or **Gradient**. Maximum depth value used to normalize point colors. | For accurate depth-to-RGB colorization, the depth and RGB images should use the same camera frame, or have matching intrinsics with only a rigid transform between frames. The current implementation does not perform stereo rectification, so you may see subtle colorization errors when the sensors are offset and have different intrinsics. #### Plane projection The 3D panel can project camera images onto a reference plane in the 3D scene. This is especially useful for ground-plane projection, where the elements on the ground-plane in the image can be lined up with objects in the 3D scene. However, things that are protruding or not on the plane will appear distorted. To use plane projection, enable the camera image topic in the panel's topic settings, then set the **Render mode** to **Plane projection** and select a **Projection frame** from the available transform frames. | field | description | | --- | --- | | Render mode | Select how the image is rendered: **Default**, **Plane projection**, or **Depth map**. Only one mode can be active at a time. Settings shown below are only visible when "Render mode" is set to "Plane projection". | | Projection frame | The coordinate frame to project into. The image will be projected onto this frame's XY plane. There must be a valid transform between this frame and the camera's frame. | ### Laser scan A single scan from a planar laser range-finder. Laser scans share the same color and display settings as [point clouds](#point-cloud), including color mode, color map, and gradient. | framework | schema | | --- | --- | | ROS 1 | [`sensor_msgs/LaserScan`](https://docs.ros.org/en/noetic/api/sensor_msgs/html/msg/LaserScan.html) | | ROS 2 | [`sensor_msgs/msg/LaserScan`](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/LaserScan.msg) | | Custom | [`foxglove.LaserScan`](/docs/sdk/schemas/laser-scan) | ### Location fix A latitude, longitude, and altitude (LLA) coordinate, used to globally position [the scene](#frame), [3D Maps](#3d-map-custom-layer), and [map layers](#map-custom-layer) within it. Your location fix messages must use a `frame_id` in your transform tree. | framework | schema | | --- | --- | | ROS 1 | [`sensor_msgs/NavSatFix`](https://docs.ros.org/en/noetic/api/sensor_msgs/html/msg/NavSatFix.html) | | ROS 2 | [`sensor_msgs/msg/NavSatFix`](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/NavSatFix.msg) | | Custom | [`foxglove.LocationFix`](/docs/sdk/schemas/location-fix) | ### ROS polygons Timestamped polygons made up of a series of connected points. | framework | schema | | --- | --- | | ROS 1 | [`geometry_msgs/PolygonStamped`](https://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/PolygonStamped.html) | | ROS 2 | [`geometry_msgs/msg/PolygonStamped`](https://github.com/ros2/common_interfaces/blob/master/geometry_msgs/msg/PolygonStamped.msg) | ### ROS markers Similar to [scene entities](#scene-entity), these `Marker` messages describe primitive shapes or meshes. | framework | schema | | --- | --- | | ROS 1 | [`visualization_msgs/Marker`](https://docs.ros.org/en/noetic/api/visualization_msgs/html/msg/Marker.html) | | ROS 2 | [`visualization_msgs/msg/Marker`](https://github.com/ros2/common_interfaces/blob/master/visualization_msgs/msg/Marker.msg) | | ROS 1 | [`visualization_msgs/MarkerArray`](https://docs.ros.org/en/noetic/api/visualization_msgs/html/msg/MarkerArray.html) | | ROS 2 | [`visualization_msgs/msg/MarkerArray`](https://github.com/ros2/common_interfaces/blob/master/visualization_msgs/msg/MarkerArray.msg) | #### Mesh markers Markers with a `mesh_resource` field support the following URL schemes: - `http(s)://` - `package://` (Desktop app only) - `file://` (Desktop app only) And file formats: - glTF - STL - COLLADA - Wavefront OBJ **glTF (`.glb`)** This is the preferred format, as it enjoys the best performance of all supported file types. Binary glTF files bundle all required assets into a single file, with support for embedded meshes, compression, and the same physically-based material system used in Foxglove. As a result, your model should appear in Foxglove similarly to how it appears in other 3D programs. **STL (`.stl`)** STL files are well supported in Foxglove, but lack some of glTF's visualization features. The main advantage to STL is the ability to share the same files between your hardware manufacturing process and robot visualization tooling. STL was designed for 3D printing and CAD applications, and does not include materials or hierarchies of meshes. While they can be represented in a binary encoding, STL files are commonly represented with ASCII characters, which leads to larger files. **COLLADA (`.dae`)** As a predecessor to glTF, COLLADA has a similar feature set. With that said, it does have larger XML-based files, no compression, and additional processing overhead. There is a [bug in RViz](https://github.com/ros-visualization/rviz/issues/1045) where the up-axis metadata is ignored, resulting in incorrect orientations for many `.dae` files in ROS environments. To work around this, the 3D panel has a `Ignore COLLADA ` setting to toggle between observing the `` tag or ignoring it like RViz. **Wavefront OBJ (`.obj`)** OBJ is a simple ASCII format predating all other supported formats. It has large file sizes, no material support, no mesh hierarchies, no compression, and additional processing overhead. Material support was added to the OBJ format as separate `.mtl` files, which Foxglove does not read. ### Path An array of timestamped poses in a named coordinate frame, denoting an object's path through space. | framework | schema | | --- | --- | | ROS 1 | [`nav_msgs/Path`](https://docs.ros.org/en/noetic/api/nav_msgs/html/msg/Path.html) | | ROS 2 | [`nav_msgs/msg/Path`](https://github.com/ros2/common_interfaces/blob/master/nav_msgs/msg/Path.msg) | | Custom | [`foxglove.PosesInFrame`](/docs/sdk/schemas/poses-in-frame) | ### Point cloud A collection of N-dimensional points, which may contain additional fields with information like normals, intensity, etc. ![pointcloud](/img/docs/visualization/panels/3d/pointcloud.png) | framework | schema | | --- | --- | | ROS 1 | [`sensor_msgs/PointCloud2`](https://docs.ros.org/en/noetic/api/sensor_msgs/html/msg/PointCloud2.html) | | ROS 2 | [`sensor_msgs/msg/PointCloud2`](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/PointCloud2.msg) | | Custom | [`foxglove.PointCloud`](/docs/sdk/schemas/point-cloud) | | Custom | [`foxglove.CompressedPointCloud`](/docs/sdk/schemas/compressed-point-cloud) | :::note Foxglove supports `foxglove.CompressedPointCloud` messages encoded with Draco. ::: #### Settings | field | description | | --- | --- | | Point shape | Shape of each rendered point: "Circle", "Square", or "Cube". Default: "Circle". | | Point size | Size of each rendered point in pixels. Only shown when the point shape is "Circle" or "Square". | | Cube size | Size of each cube in 3D scene units. Only shown when the point shape is "Cube". | | Show outline | Toggle whether to display wireframe outline around cubes. Only shown when the point shape is "Cube". Default: "Off" | | Decay time | Duration of time (in sec) that each point stays rendered | | Color mode | One of:
Flat: solid color
Color map: pre-defined color palette
Gradient: smooth transition between two custom colors
BGR (packed): `sensor_msgs/PointCloud2` only; use embedded color from each point's `rgb` field (see below)
BGRA (packed): `sensor_msgs/PointCloud2` only; use embedded color from each point's `rgba` field (see below)
RGBA (separate fields): `foxglove.PointCloud` and `foxglove.CompressedPointCloud` only; use embedded color from each point's `red`, `green`, `blue`, and `alpha` fields (see below) | | Flat color | Only shown if "Color mode" is set to "Flat"; hex code for color of each point | | Color field | Only shown if "Color mode" is not set to "Flat"; value used for "Color map" coloring logic; any numeric field in message such as `x`, `y`, `z`, `` (L2 norm of coordinates), or custom defined field | | Color map | Only shown if "Color mode" is set to "Color map"; "Turbo" (Google) or "Rainbow" (RViz); for mapping "Color field" values to colors | | Opacity | Only shown if "Color mode" is set to "Color map" or "BGR (packed)"; sets alpha value for all points | | Value min | Only shown if "Color mode" is not set to "Flat"; minimum value used to normalize incoming points' "Color field" values | | Value max | Only shown if "Color mode" is not set to "Flat"; maximum value used to normalize incoming points' "Color field" values | | Stixel view | Visualize points as stixels that extend from the point's z location to 0 | #### RGBA color modes When using the "BGR (packed)", "BGRA (packed)", and "RGBA (separate fields)" color modes, your point cloud message must contain certain fields to display color information for each point. ##### RGBA (separate fields) For `foxglove.PointCloud` and `foxglove.CompressedPointCloud` messages, each point can contain color information in four separate fields, named `red`, `green`, `blue`, and `alpha`, of any numeric type: - **Floating-point values** — 0–1 range - **Unsigned integer values** — Maximum possible range (e.g. 0–255 for a `UINT8` field) - **Signed integer values** — `-max` to `max` (e.g. −127 to 127 for an `INT8` field; a value of −128 is treated as identical to −127) ##### BGR (packed) and BGRA (packed) For `sensor_msgs/PointCloud2` messages, each point can contain color information in a single field named `rgb` or `rgba`: - Must use a 4-byte type from [sensor_msgs/PointField](http://docs.ros.org/en/melodic/api/sensor_msgs/html/msg/PointField.html) (`UINT32`, value 6, is recommended) - Each red, green, blue, and alpha value is represented by one byte in the 0–255 range - Bytes must be packed in `[0xBB, 0xGG, 0xRR, 0xAA]` order (i.e. `(0xAA << 24) | (0xRR << 16) | (0xGG << 8) | 0xBB` in little-endian order). This order is compatible with RViz. If using the "BGR" mode, the alpha value must still be present, but is ignored. ### Pose Poses in a named coordinate frame. | framework | schema | | --- | --- | | ROS 1 | [`geometry_msgs/PoseArray`](https://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/PoseArray.html) | | ROS 2 | [`geometry_msgs/msg/PoseArray`](https://github.com/ros2/common_interfaces/blob/master/geometry_msgs/msg/PoseArray.msg) | | ROS 1 | [`geometry_msgs/PoseStamped`](https://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/PoseStamped.html) | | ROS 2 | [`geometry_msgs/msg/PoseStamped`](https://github.com/ros2/common_interfaces/blob/master/geometry_msgs/msg/PoseStamped.msg) | | Custom | [`foxglove.PosesInFrame`](/docs/sdk/schemas/poses-in-frame) | ### Scene entity A collection of primitive shapes (cubes, spheres, text, meshes, lines, etc) used to display anything from a basic bounding box to a complex 3D decision tree or road network. Scene entities must be wrapped in a `SceneUpdate` message. Model primitives (`foxglove.ModelPrimitive`) support both a `url` and `data` field. When both are provided and the URL fails to load (e.g., network error), the 3D panel falls back to the embedded `data` so the model can still be displayed. | framework | schema | | --------- | -------------------------------------------------------- | | Custom | [`foxglove.SceneEntity`](/docs/sdk/schemas/scene-entity) | | Custom | [`foxglove.SceneUpdate`](/docs/sdk/schemas/scene-update) | #### Settings | field | description | | --- | --- | | **Color** | Render all entities under this topic with this color, overriding colors defined in the entities. | | **Show outlines** | Display a wireframe outline around rendered entities. | | **Selection variable** | Set a global variable with this name to the selected entity ID. | | **Vertex normals** | Toggle calculation of vertex normals. Normals improve lighting and visual appearance but incur a significant performance penalty in some cases. | :::tip To control entity visibility, publish them on separate [topics](#topics) (for example, `/scene/walls`, `/scene/floor`). You can then toggle each topic independently. ::: ### Transforms A transform (translation and rotation) between two reference frames in 3D space. | framework | schema | | --- | --- | | ROS 1 | [`tf/tfMessage`](https://docs.ros.org/en/noetic/api/tf/html/msg/tfMessage.html) | | ROS 1 | [`tf2_msgs/TFMessage`](https://docs.ros.org/en/noetic/api/tf2_msgs/html/msg/TFMessage.html) | | ROS 2 | [`tf2_msgs/msg/TFMessage`](https://github.com/ros2/geometry2/blob/ros2/tf2_msgs/msg/TFMessage.msg) | | Custom | [`foxglove.FrameTransform`](/docs/sdk/schemas/frame-transform) | ### Velodyne scan Velodyne lidar scan packets from the Velodyne ROS driver. | framework | schema | | --- | --- | | ROS 1 | [`velodyne_msgs/VelodyneScan`](https://docs.ros.org/en/noetic/api/velodyne_msgs/html/msg/VelodyneScan.html) | | ROS 2 | [`velodyne_msgs/msg/VelodyneScan`](https://github.com/ros-drivers/velodyne/blob/ros2/velodyne_msgs/msg/VelodyneScan.msg) | ### Video Compressed videos displayed in the 3D scene, using the corresponding [Camera field-of-view](#camera-field-of-view) messages. | framework | schema | | --------- | ---------------------------------------------------------------- | | Custom | [`foxglove.CompressedVideo`](/docs/sdk/schemas/compressed-video) | ### Voxel Grid A 3D volumetric grid used to represent occupancy information in a spatial environment, typically used in robot navigation and obstacle avoidance. Each voxel can be marked (obstacle), present (observed), or unknown, and the grid is used to compute traversability. ![voxelgrid](/img/docs/visualization/panels/3d/voxelgrid.png) | framework | schema | | --- | --- | | ROS 1 | [costmap_2d/VoxelGrid](http://docs.ros.org/en/api/costmap_2d/html/msg/VoxelGrid.html) | | ROS 2 | [nav2_msgs/msg/VoxelGrid](https://github.com/ros-navigation/navigation2/blob/main/nav2_msgs/msg/VoxelGrid.msg) | | Custom | [`foxglove.VoxelGrid`](/docs/sdk/schemas/voxel-grid) | #### ROS Settings | **field** | **description** | | --- | --- | | **Color mode** | How to color each voxel. Options:
• **By type** (based on occupancy classification)
• **Gradient** (numeric field mapped to a gradient)
• **Rainbow** (predefined colormap) | | **Gradient** | Only shown if "Color mode" is set to "Gradient". User-defined color mapping for voxel values. | | **Marked and present** | Only shown if "Color mode" is set to "By type". RGBA color for voxels that are both marked and present. | | **Present** | Only shown if "Color mode" is set to "By type". RGBA color for voxels that are present but not marked. | | **Marked** | Only shown if "Color mode" is set to "By type". RGBA color for voxels that are marked but not present. | | **Use lighting** | Enables lighting for voxel shading in 3D view. | #### ROS Voxel classification When using **By type** coloring: - **Marked** voxels represent obstacles or areas flagged as non-traversable. - **Present** voxels represent observed but unmarked areas. - **Marked and present** voxels are both observed and flagged. Each voxel is rendered as a cube in 3D space and can be shaded based on lighting settings. The resolution and origin of the grid are defined in the message metadata (`resolutions`, `origin`, and `data` fields). #### `foxglove.VoxelGrid` settings | field | description | | --- | --- | | Color mode | One of:
Flat: solid color
Color map: pre-defined color palette
Gradient: smooth transition between two custom colors
RGBA (separate fields): use embedded color from each cell's `red`, `green`, `blue`, and `alpha` fields (see below) | | Color field | Only shown if "Color mode" is not set to "Flat"; numeric field in message used for coloring logic | | Color map | Only shown if "Color mode" is set to "Color map"; "Turbo" (Google) or "Rainbow" (RViz); for mapping "Color field" values to colors | | Gradient | Only shown if "Color mode" is set to "Gradient"; The two colors to interpolate between, based on the "color field" | | Opacity | Only shown if "Color mode" is set to "Color map" or "BGR (packed)"; sets alpha value for all cells | | Value min | Only shown if "Color mode" is not set to "Flat"; minimum value used to normalize incoming grid's "Color field" values | | Value max | Only shown if "Color mode" is not set to "Flat"; maximum value used to normalize incoming grid's "Color field" values | | Lighting | Only shown if "Elevation" is set; enables shading from simulated directional lighting, based on elevation surface normals | | Frame lock | "On" means the grid is locked to the frame specified by its `frame_id`, and will move as that frame's transforms change. "Off" means the grid is relative to the fixed frame and will not move after it is first displayed | | Draw behind | "On" means the grid is rendered before the rest of the scene, so it appears behind other scene elements regardless of their relative depth. "Off" means the grid is rendered normally | ##### RGBA (separate fields) color mode Each cell can contain color information in four separate fields, named `red`, `green`, `blue`, and `alpha`, of any numeric type: - **Floating-point values** — 0–1 range - **Unsigned integer values** — Maximum possible range (e.g. 0–255 for a `UINT8` field) - **Signed integer values** — `-max` to `max` (e.g. −127 to 127 for an `INT8` field; a value of −128 is treated as identical to −127) ## Settings ### Frame Select your 3D scene's references frames and target following mode. | field | description | | --- | --- | | **Fixed frame** | The stationary world reference relative to which all other objects in the scene are located. Elements that accumulate over time (for example, point clouds) are stored and displayed in this frame, and in 2D mode the camera faces this frame's x-y plane. By default the `` option is selected, which uses the TF tree root relative to the selected display frame. Choosing a fixed frame closer to the robot can help reduce floating-point precision issues when transforms span large distances. | | **Display frame** | The coordinate frame the camera follows. The camera position and orientation are defined relative to the origin of this frame. | | **Follow mode** |

How the camera moves relative to the display frame during playback:

  • **Pose** follows the display frame's position, roll, pitch, and yaw
  • **Heading** follows the display frame's position and yaw (heading), while pitch and roll are removed so the horizon stays level (the fixed frame's Z-axis stays pointing up)
  • **Position** follows the display frame's position only, with orientation aligned to the fixed frame
  • **Off** does not update viewport
| | **Sync timestamps** | Enables [timestamp synchronization](#timestamp-synchronization) for selected topics. See the dedicated section below for details. | | **Location topic** | Defines the geographic origin of [3D Maps](#3d-map-custom-layer) and [map layers](#map-custom-layer) using a [location fix](#location-fix) topic with a valid `frame_id` in the TF tree. **Auto** uses the first available topic. (Hidden when there are no location fix topics.) | | **ENU frame** | Defines the East-North-Up orientation of the scene and the alignment of [3D Maps](#3d-map-custom-layer) and [map layers](#map-custom-layer). (Hidden when there are no location fix topics.) | ### Scene Configure generic rendering properties and viewport properties. | field | description | | --- | --- | | **Show render stats** | Display rendering performance statistics in panel | | **Background** | Background color of the scene | | **Label scale** | Scale factor to apply to all rendered labels | | **Ignore COLLADA ``** | Match the behavior of RViz by ignoring the `` tag in COLLADA files | | **Mesh "up" axis** | The direction to use as "up" when loading meshes without orientation info (e.g. STL, OBJ) | ### View Configure the camera settings. | field | description | | --- | --- | | **Sync camera** | Sync the camera with other panels that also have this setting enabled | | **Distance** | Camera distance from the display frame origin | | **3D view** | Toggles between 3D and 2D views of the scene. The 2D view looks down the z-axis of the fixed frame and flattens the scene against its x-y plane. | | **Target** | Translational offset from the origin of the display frame | | **Theta** | Azimuthal angle offset from the origin of the display frame in degrees | | **Phi** | Polar angle offset from the origin of the display frame in degrees | | **Y-axis FOV** | Vertical field of view in degrees | | **Near** | Near clipping plane distance | | **Far** | Far clipping plane distance | | **Log depth** | Enable [logarithmic depth buffer](https://www.gamedeveloper.com/programming/logarithmic-depth-buffer) for more uniform depth precision. May result in rendering artifacts or performance degradation. | #### Interaction of log depth and near/far plane settings The **Near**, **Far**, and **Log depth** settings are interrelated. When using a very small **Near** value or a large **Far** value, enabling **Log depth** may reduce visual artifacts. For example, in the video below: - The left panel does not use logarithmic depth, and it has a near clipping plane of 1.0. The grids render smoothly, but objects close to the viewer are clipped. - The middle panel does not use logarithmic depth, and has a near plane of 0.01. The viewer can get closer to objects without clipping, but rendering artifacts (z-fighting) appear between multiple grids. - The right panel enables the logarithmic depth setting. This avoids z-fighting by providing more depth precision for the grids, while still allowing a near plane value of 0.01 for reduced clipping.