# URDF tutorial for ROS 2: describe a robot properly

> Build a URDF for ROS 2: links, joints, inertia and Xacro, then check the model in RViz and fix the errors that break simulation.

Canonical: https://roboticsengineering.in/urdf-ros2-tutorial/ | Published: 2026-10-27 | Updated: 2026-10-28 | Author: Jignesh Panchal

**A robot is a list of parts and the joints between them. URDF is how ROS 2 writes that list down.** Get the frames and inertias right and everything above them — TF, controllers, simulation, navigation — behaves. Get them wrong and the robot looks fine in RViz while behaving strangely in the real world.

This tutorial builds a URDF from a minimal two-wheel base, explains links, joints and inertia, then shows how to check the model before you trust it.

## What URDF describes, and what it does not

URDF (Unified Robot Description Format) is an XML file describing a tree of links and joints: geometry, mass, inertia, visual appearance and collision shape. It says nothing about controllers, sensors or behaviour. It is the skeleton, not the muscles.

| Format | Used by | Best for |
| --- | --- | --- |
| URDF | ROS 2, RViz, robot_state_publisher | Describing a single robot’s kinematics and inertia |
| SDF | Gazebo | Full simulation worlds, multiple robots, physics tuning |
| MJCF | MuJoCo | Contact-rich control research and reinforcement learning |
| USD | Isaac Sim, Omniverse | Large scenes, photoreal rendering, synthetic data |

Most ROS 2 projects still author URDF and let Gazebo or Isaac convert it. That is fine until you need contact properties or a complex world, at which point the conversion limits start to show.

## A minimal two-wheel robot

```
<?xml version="1.0"?>
<robot name="diffbot">

  <link name="base_link">
    <visual>
      <geometry><box size="0.35 0.25 0.12"/></geometry>
      <material name="grey"><color rgba="0.4 0.4 0.4 1"/></material>
    </visual>
    <collision>
      <geometry><box size="0.35 0.25 0.12"/></geometry>
    </collision>
    <inertial>
      <mass value="4.0"/>
      <inertia ixx="0.018" ixy="0" ixz="0"
               iyy="0.033" iyz="0" izz="0.045"/>
    </inertial>
  </link>

  <link name="left_wheel">
    <visual>
      <geometry><cylinder radius="0.06" length="0.03"/></geometry>
    </visual>
    <collision>
      <geometry><cylinder radius="0.06" length="0.03"/></geometry>
    </collision>
    <inertial>
      <mass value="0.3"/>
      <inertia ixx="0.00027" ixy="0" ixz="0"
               iyy="0.00027" iyz="0" izz="0.00054"/>
    </inertial>
  </link>

  <joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="0 0.15 -0.03" rpy="-1.5708 0 0"/>
    <axis xyz="0 0 1"/>
  </joint>

</robot>
```

Two details decide whether this model behaves. The joint origin places the wheel relative to the base, and the `rpy` rotation turns a cylinder’s default Z axis so it lies along the robot’s Y axis. Forget the rotation and the wheel renders as a disc standing on edge.

## Links and joints

| Joint type | Motion | Typical use |
| --- | --- | --- |
| fixed | None | Sensors, brackets, the base plate |
| continuous | Rotation, no limit | Wheels |
| revolute | Rotation with limits | Robot arm joints, gripper fingers |
| prismatic | Linear with limits | Lifts, linear rails, grippers |
| floating | Six degrees of freedom | Rare; usually a simulation shortcut |
| planar | Plane motion | Rare; mobile bases in simplified models |

Every joint defines a parent, a child and an origin. The tree must be connected and acyclic: one root link, no loops. If you need a loop, URDF cannot represent it and you need SDF or a workaround.

## Inertia: the numbers everyone guesses

Simulation uses the inertial block to compute how the robot reacts to forces. A wrong mass is obvious; a wrong inertia is subtle and produces robots that tip over too easily or feel weightless. Units are kilograms and kilogram square metres, not grams or millimetres.

| Shape | Ixx | Iyy | Izz |
| --- | --- | --- | --- |
| Solid box (m, x, y, z) | m(y²+z²)/12 | m(x²+z²)/12 | m(x²+y²)/12 |
| Solid cylinder (m, r, h) about axis | mr²/2 | mr²/2 | m(3r²+h²)/12 |
| Solid sphere (m, r) | 2mr²/5 | 2mr²/5 | 2mr²/5 |

For a first model, approximate each part with a box or cylinder and use the formulas. Export exact values from CAD once the design is frozen; hand-tuned numbers tend to drift between revisions.

## Xacro stops the copy-paste

Real robots repeat geometry: two wheels, four legs, six arm links. Xacro adds properties, macros and basic maths to URDF, so a wheel is defined once and instantiated twice with different origins.

```
<xacro:property name="wheel_radius" value="0.06"/>
<xacro:property name="wheel_width" value="0.03"/>
<xacro:property name="track" value="0.30"/>

<xacro:macro name="wheel" params="prefix y">
  <link name="${prefix}_wheel"> ... </link>
  <joint name="${prefix}_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="${prefix}_wheel"/>
    <origin xyz="0 ${y} -0.03" rpy="-1.5708 0 0"/>
    <axis xyz="0 0 1"/>
  </joint>
</xacro:macro>

<xacro:wheel prefix="left" y="${track/2}"/>
<xacro:wheel prefix="right" y="${-track/2}"/>
```

Generate plain URDF with `xacro model.urdf.xacro > model.urdf` when a tool insists on the plain format.

## Check the model before simulating

```
check_urdf model.urdf
urdf_to_graphiz model.urdf
ros2 run robot_state_publisher robot_state_publisher model.urdf
ros2 run joint_state_publisher_gui joint_state_publisher_gui
ros2 run rviz2 rviz2
```

In RViz, set the fixed frame to `base_link`, add a RobotModel display and a TF display. Move the joint sliders and watch the wheels turn around the correct axis. If a wheel spins like a coin, the origin rotation is wrong. If the whole robot shakes, an inertia is too small or a mass is zero.

## Errors that look like other problems

| Symptom | Likely cause | Check |
| --- | --- | --- |
| Robot invisible in RViz | Fixed frame does not exist | TF tree and frame names |
| Model looks correct but sinks | Collision geometry missing or mass zero | collision blocks and inertial mass |
| Wheels spin in the wrong plane | Joint origin rotation | rpy on the joint |
| Robot explodes in simulation | Inertia too small or negative | Inertial values and units |
| TF warnings about multiple parents | Link connected twice | Joint tree is not a strict tree |
| Meshes missing | Package path not installed | package:// resolves in the install space |

Related: [ROS 2 for beginners](https://roboticsengineering.in/ros2-tutorial-beginners/), [ROS 2 launch files](https://roboticsengineering.in/ros2-launch-files-tutorial/), [Gazebo ROS 2 tutorial](https://roboticsengineering.in/gazebo-ros2-simulation-tutorial/), [topics, services and actions](https://roboticsengineering.in/ros2-topics-services-actions/), [robot arm kinematics](https://roboticsengineering.in/robot-arm-kinematics-basics/).

**What is URDF used for in ROS 2?**

URDF describes a robot as links and joints with geometry, mass and inertia. ROS 2 uses it for TF frames, RViz visualisation, robot_state_publisher and simulation.

**What is the difference between URDF and Xacro?**

Xacro is a preprocessor that adds properties, macros and maths to URDF. You author Xacro and generate plain URDF for tools that need it.

**Why does my robot look wrong in RViz?**

Usually the fixed frame does not exist, the joint origin rotation is wrong, or the TF tree is missing frames. Check check_urdf output first, then the rpy values on joints.

**What units does URDF use?**

Metres for lengths, kilograms for mass and kilogram square metres for inertia. Mixing millimetres or grams is a common cause of simulation instability.

**Do I need exact inertia values?**

Not for visualisation. For simulation, approximate with box or cylinder formulas and replace them with CAD values once the design is fixed.

A URDF defines the frames; TF2 tracks where they are: see [the ROS 2 TF2 tutorial](https://roboticsengineering.in/ros2-tf2-tutorial/).

The URDF describes the robot; [ros2_control](https://roboticsengineering.in/ros2-control-tutorial/) makes it move, and [MuJoCo](https://roboticsengineering.in/mujoco-robot-simulation/) can simulate the same model.

Wondering why Gazebo needs a different file; read [URDF vs SDF](https://roboticsengineering.in/urdf-vs-sdf/).

A correct robot description is the first thing MoveIt needs, as the [MoveIt 2 tutorial](https://roboticsengineering.in/moveit2-tutorial-ros2/) shows.

The ros2_control block in that URDF is wired up in the [hardware interface tutorial](https://roboticsengineering.in/ros2-hardware-interface-tutorial/).

## Sources and further reading

- [ROS 2 URDF tutorials](https://docs.ros.org/en/jazzy/Tutorials/Intermediate/URDF/URDF-Main.html) — building and checking a model.
- [URDF XML reference](https://wiki.ros.org/urdf/XML) — link, joint and inertial elements.
- [Using Xacro](https://docs.ros.org/en/jazzy/Tutorials/Intermediate/URDF/Using-Xacro-to-Clean-Up-a-URDF-File.html) — properties, macros and includes.
- [Gazebo documentation](https://gazebosim.org/docs) — how URDF becomes a simulated model.
