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 rviz2In 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, ROS 2 launch files, Gazebo ROS 2 tutorial, topics, services and actions, robot arm kinematics.
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.
The URDF describes the robot; ros2_control makes it move, and MuJoCo can simulate the same model.
Wondering why Gazebo needs a different file; read URDF vs SDF.
A correct robot description is the first thing MoveIt needs, as the MoveIt 2 tutorial shows.
The ros2_control block in that URDF is wired up in the hardware interface tutorial.
Sources and further reading
- ROS 2 URDF tutorials — building and checking a model.
- URDF XML reference — link, joint and inertial elements.
- Using Xacro — properties, macros and includes.
- Gazebo documentation — how URDF becomes a simulated model.


