Explore ROS1 & ROS2 bag files in your browser. No installation required.
→ Live Demo · Report Bug · Request Feature
BAGEL is a fully static web application that lets you explore ROS bag files (.mcap, .db3, .bag) and standalone point cloud files (.pcd, .ply) entirely in your browser needing no server, no installation, no account. Just drag and drop!
Robotics engineers and researchers frequently generate bag files during experiments, SLAM runs, and sensor calibration. Inspecting these files currently requires a full ROS1 or ROS2 installation, Foxglove Studio (increasingly commercial), or writing custom Python scripts for every inspection task.
BAGEL eliminates this friction.
| Problem | BAGEL Solution |
|---|---|
| Need ROS1/ROS2 installed to inspect bag files | Works in any modern browser |
| Foxglove Studio going commercial | 100% open source, MIT licensed |
ros2 bag info gives text-only output |
Rich visual interface with search & filtering |
| Can't share bag contents easily | Zero-install, you can send anyone the URL |
| Students struggle with ROS tooling | No setup required, just drag and drop |
| Legacy ROS1 bags require old toolchains | Drag the .bag straight in (no conversion needed) |
Data featured in this demo is from the excellent open-access M2DGR dataset provided by the SJTU-ViSYS team, which was instrumental in stress-testing this visualizer's spatial rendering capabilities.
A condensed feature list is below. Detailed version-by-version release notes (with the design rationale behind every feature) live in FEATURES.md.
| Format | Notes |
|---|---|
ROS2 .mcap |
Including zstd-compressed chunks (the new ROS2 default). Foxglove Studio JSON-encoded channels (schemaEncoding: "jsonschema", encoding: "json") supported from v1.6.1. |
ROS2 .db3 |
SQLite via sql.js / WASM |
ROS1 .bag |
Including bz2 and lz4 compressed chunks |
| Remote URLs | HTTP Range requests, so only the chunks you scrub through hit the network. .mcap / .bag stream lazily; .db3 eager-fetches (sql.js needs it in memory). |
| Foxglove WebSocket (v1.5.0) | Paste a ws:// or wss:// URL to connect to a live robot running foxglove_bridge or rosbridge_suite. All panels update in real time. Per-topic ring buffer holds the last 10,000 messages per topic; Follow/Pause button lets you scrub back into history without disconnecting. Auto-reconnect with exponential backoff. |
.pcd point clouds (v1.6.0) |
All three PCD 0.7 encodings: ascii, binary, binary_compressed (LZF). All color modes (height, intensity, rgb, single). Feeds directly into the ThreeDScene panel via a synthetic sensor_msgs/PointCloud2 message. |
.ply point clouds (v1.6.0) |
ASCII + binary_little_endian + binary_big_endian. RGB from red/green/blue uchar properties or packed rgb float. Drop a .ply and the cloud appears instantly in the 3D panel with all existing color, range-filter, and accumulator settings. |
linear.x, orientation.w, etc.) on uPlot. Math expressions as derived series: type field_a * 2 + field_b in the series editor to plot any arithmetic combination of fields from the same topic without writing code. (v1.4.1)sensor_msgs/Image (rgb8 / bgr8 / rgba8 / mono8 / mono16) and CompressedImage (jpeg / png) with lazy single-message reads. Foxglove equivalents (foxglove.RawImage, foxglove.CompressedImage) supported via JSON schema translation. (v1.6.1) H264/H265 video via foxglove.CompressedVideo using the browser's WebCodecs VideoDecoder with a fast keyframe index for efficient seeking. (v1.6.2) Scroll to zoom (cursor-centered), drag to pan, double-click to reset; zoom percentage shown in the footer. (v1.6.3) Optional sensor_msgs/CameraInfo overlay (principal-point reticle + focal-length badge + calibration-likely-unfilled chip) toggles from the panel header. (v1.3.2) undistort button applies per-frame plumb-bob (Brown-Conrady) undistortion using the paired CameraInfo's D coefficients. (v1.3.4)PointCloud2, LaserScan, MarkerArray (all twelve primitives: CUBE / SPHERE / CYLINDER / ARROW / LINE_STRIP / LINE_LIST / CUBE_LIST / SPHERE_LIST / POINTS / TEXT_VIEW_FACING / MESH_RESOURCE / TRIANGLE_LIST from v1.3.1), OccupancyGrid, pose markers, camera frustums for every sensor_msgs/CameraInfo topic with per-camera hide checkboxes on multi-camera rigs (v1.3.2 / v1.3.4). Custom orbit pivot, range filter, point accumulation, configurable up-axis./tf + /tf_static hierarchy with current transforms at the playhead time.diagnostic_msgs/DiagnosticArray. (v1.0)rcl_interfaces/Log and rosgraph_msgs/Log with severity, node-name, and full-text filters. (v1.0)All panels resolve header.frame_id through /tf + /tf_static against a user-selected world frame.
wall-clock, bag-start, and anchor (with a "Set anchor" picker UI in v1.0 so you can lock runs to a physical sync event)..mcap. Replaces the mcap filter CLI workflow for the common cuts. v1.2 extends the editor to ROS1 .bag and ROS2 .db3 inputs alongside MCAP - output is always MCAP regardless of input format. .db3 topics whose type isn't in BAGEL's bundled registry are flagged in the modal and excluded by default; opt them in to include them with a schema-less channel. (v1.1 / v1.2).msg schema flow for ROS2 .db3 topics whose types aren't in the bundled registry. Persisted across sessions in localStorage.canvas.toBlob()) so every panel type captures correctly. PNG frames are zipped with fflate at level 0 (no re-compression of already-deflated PNGs); WebM uses a two-phase MediaRecorder + captureStream(0) + requestFrame() approach so video playback speed is always correct. (v1.4.2)+ button, or press M), click a tick to seek, hover to see the label. Bookmarks persist to localStorage per bag and are encoded in the URL hash (bm=) for sharing. (v1.4.3).urdf and the robot appears in every 3D panel, anchored to its root link in world space via the bag's /tf stream. Joints animate from sensor_msgs/JointState (auto-detected) when the bag publishes it; static URDFs render at their rest pose. A toolbar "Robot" button opens the load modal; the Display card in each 3D panel grows a per-panel robot model hide toggle..stl / .dae / .obj meshes. Loaders are lazy-imported on first use of each file type so primitives-only URDFs don't pay the Collada loader's bundle cost.package:// resolver: paste a URL prefix or drag-drop a folder per referenced package; URL bindings persist across sessions in localStorage under bagel:package-roots:v1. No auto-fetch from ROS distros - BAGEL only loads meshes from where you point it.public/sample-bags/sample-robot.urdf) pairs with tour.mcap so the "Try a sample robot" button in the modal demonstrates the full flow on a fresh checkout.ws://host:8765 URL into the new "Connect" input. Works with foxglove_bridge (ROS2) and rosbridge_suite (ROS1/ROS2). Implements foxglove.websocket.v1 - binary MESSAGE_DATA frames for message payloads, JSON frames for topic advertisement.@foxglove/rosmsg2-serialization, ROS1 CDR via @foxglove/rosmsg-serialization - both already bundled as bag-parsing dependencies, no new packages. (v1.5.3 adds encoding: "ros1" for ROS1 bridges)Record button in the Toolbar (visible when a live connection is active) buffers every incoming message. Click Stop to serialize all captured data to a fully-indexed MCAP file and download it instantly. The output opens back in BAGEL or any mcap-compatible tool without conversion./clock is advertised, messages with logTimeNs = 0 (common in Gazebo/Isaac Sim) use the simulation clock value instead of wall-clock time, keeping plots readable in simulation sessions. A SIM badge appears on the toolbar chip.Health button in the Toolbar. Data is computed once per bag in a background scan and cached. (v1.5.6) When multiple bags are loaded, a chip strip at the top lets you switch the view between bags without opening additional panels.vel_x * 2 + offset, sqrt(x*x + y*y)) as derived series directly in the TimeSeriesPlot panel. References other numeric fields from the same topic; evaluated in a sandboxed expression engine (no eval).bm= URL hash segment.Space to play, ← / → to step, L to loop, M to bookmark (v1.4.3), Esc to close panels, T to focus topic search, O to open a bag, ? for the cheat-sheet.Timeline toolbar toggle wraps the playhead back to start at end-of-bag instead of pausing, persisted across reloads. (v1.3.3)$ claude mcp add BAGEL \
-- python -m otcore.mcp_server <graph>