Skip to main content
A launch YAML file describes which units to run, how to group them into OS processes, optional recording settings, and how to compose multiple launch files together. The file name convention is <name>.launch.yaml.

File structure overview

A launch file is a YAML document with an optional Jinja2-rendered section. When the file declares arguments, it uses two YAML documents separated by ---: the first document holds the args block, and the second document holds the templated launch content.
# Document 1: argument declarations (optional)
args:
  camera_name:
    type: string
    default: front
---
# Document 2: templated launch content
units:
  my_driver:
    args:
      topic_namespace: /{{args.camera_name}}
If the file has no arguments, a single YAML document is sufficient.

Top-level fields

units
object
A map of unit instance names to unit definitions. Units declared at the top level are grouped into a single default process named /.
processes
object
Reserved top-level process grouping. In practice, use groups to define processes and nest units within them.
groups
object
A map of group names to group definitions. Each group can optionally run as a separate OS process. Groups may contain units, nested groups, and include directives.
recording
object
Optional recording configuration. See RecordingSettings.
include
object | array
Include and merge one or more other launch files into the current scope. See Includes.

Unit definition

Each key under units is an arbitrary instance name for the unit within its process. The value is an object with the following fields.
unit
string
The unit type name. This must match the name of the compiled unit shared object (.unit.so) found in /opt/basis/unit/. When omitted, the instance name is used as the type name.
units:
  my_camera_driver:          # instance name
    unit: v4l2_camera_driver # type (looks up v4l2_camera_driver.unit.so)
args
object
A map of argument key–value pairs passed to the unit at startup. Values are passed as strings and parsed by the unit’s generated argument parser.
units:
  my_camera_driver:
    args:
      device: /dev/video0
      topic_namespace: /camera
When unit is omitted and the instance name matches the binary name exactly, no additional configuration is required. An empty map {} is a valid unit definition.
units:
  foxglove: {}   # loads foxglove.unit.so with no arguments

Group definition

Groups map directly to unit definitions but add process isolation and nesting support.
units
object
Units to run within this group. Same structure as top-level units.
groups
object
Nested sub-groups. Sub-groups inherit the parent process unless process: true is set.
process
boolean
When true, this group spawns as a separate OS process. When false or omitted, units are merged into the parent process.
groups:
  perception:
    process: true   # spawns a dedicated OS process
    units:
      lidar_driver: {}
      camera_driver: {}
  ui:
    units:
      foxglove: {}  # runs in the root process
include
object | array
Include other launch files scoped to this group. See Includes.

RecordingSettings

The recording block configures the built-in .mcap recorder.
recording.async
boolean
default:"true"
When true (the default), recording is performed asynchronously to avoid blocking the transport layer.
recording.name
string
default:"basis"
Base filename for the recording. The final filename appended with the process name and a timestamp: <name><process>_<timestamp>.mcap.
recording.topics
string[]
List of glob patterns selecting which topics to record. Topics not matching any pattern are excluded.
recording:
  topics:
    - /log
    - /camera*
    - /lidar/**
recording.directory
string
Path to the directory where recording files are written.
recording:
  async: true
  name: my_run
  directory: /tmp/recordings
  topics:
    - /log
    - /camera*

Templating

Launch files support Inja (a Jinja2-compatible) template syntax in all YAML values in the second document. Templating is evaluated before YAML parsing.

Arguments block

Declare arguments in the first YAML document under args. The same sub-fields as unit YAML args apply.
args:
  camera_name:
    type: string
    default: front
  enable_recording:
    type: bool
    default: false
---

Accessing argument values

Use {{args.<name>}} to interpolate argument values anywhere in the second document.
units:
  camera_driver:
    args:
      topic_namespace: /{{args.camera_name}}/rgb
      device: /dev/video0

Simulation flag

The sim variable is automatically set to true when the launcher is started with --sim.
{% if sim %}
units:
  sim_time_publisher: {}
{% endif %}

Conditional blocks

Standard Jinja2 if / else if / endif directives are supported.
{% if args.enable_recording %}
recording:
  directory: /tmp/
  topics:
    - /log
{% endif %}

Process flag from template

The process field of a group can be driven by a template expression, allowing callers to choose whether a group gets its own process:
groups:
  perception:
    process: {{args.split_process}}
    units:
      lidar_driver: {}

Includes

Use include to compose launch files. Included processes are merged into the current launch definition; units from the included file’s root process (/) are merged into the current group.

Map syntax (most common)

include:
  foxglove.launch.yaml: {}
  perception.launch.yaml:
    lidar_topic: /lidar/points

Sequence syntax (include the same file more than once)

include:
  - camera.launch.yaml:
      camera: front
  - camera.launch.yaml:
      camera: back

Scoped include inside a group

groups:
  sensors:
    include:
      camera.launch.yaml:
        camera: front
Include paths are resolved relative to the directory of the file containing the include directive. Recursive includes are supported up to a depth of 32.

Complete example

args:
  camera_name:
    type: string
    default: front
    help: Name of the camera to use (front, rear, left, right)
  enable_recording:
    type: bool
    default: false
    help: Enable MCAP recording to /tmp/recordings
---
{% if args.enable_recording %}
recording:
  async: true
  name: robot_run
  directory: /tmp/recordings
  topics:
    - /log
    - /{{args.camera_name}}/**
{% endif %}

groups:
  # Perception runs in its own OS process
  perception:
    process: true
    units:
      # Instance name == type name, loads lidar_driver.unit.so
      lidar_driver:
        args:
          topic_namespace: /lidar
      # Explicit type override: instance "front_camera" runs v4l2_camera_driver
      front_camera:
        unit: v4l2_camera_driver
        args:
          device: /dev/video0
          topic_namespace: /{{args.camera_name}}

  # UI tooling runs in the root process
  ui:
    units:
      foxglove: {}

# Include a shared sensor configuration, passing an argument
include:
  imu_driver.launch.yaml:
    topic: /imu/data

Schema summary

<launch_file>.launch.yaml
├── args:                          # optional, first YAML document only
│   └── <name>:
│       ├── type: string
│       ├── help: string
│       ├── default: any
│       └── optional: bool
---                                # document separator (required when args present)
├── units:                         # top-level units (root process)
│   └── <instance_name>:
│       ├── unit: string           # optional type override
│       └── args:
│           └── <key>: <value>
├── groups:
│   └── <group_name>:
│       ├── process: bool
│       ├── units: ...
│       ├── groups: ...
│       └── include: ...
├── include:
│   └── <file.launch.yaml>: { <args> }
└── recording:
    ├── async: bool
    ├── name: string
    ├── directory: string
    └── topics:
        └── - <glob_pattern>

Build docs developers (and LLMs) love