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 before trusting it in simulation.

Reference
RE-749
Published
Read
6 min

About the author · Sources, testing and AI policy

URDF file on a laptop beside a simple two-wheel robot model in RViz

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.

FormatUsed byBest for
URDFROS 2, RViz, robot_state_publisherDescribing a single robot’s kinematics and inertia
SDFGazeboFull simulation worlds, multiple robots, physics tuning
MJCFMuJoCoContact-rich control research and reinforcement learning
USDIsaac Sim, OmniverseLarge 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 typeMotionTypical use
fixedNoneSensors, brackets, the base plate
continuousRotation, no limitWheels
revoluteRotation with limitsRobot arm joints, gripper fingers
prismaticLinear with limitsLifts, linear rails, grippers
floatingSix degrees of freedomRare; usually a simulation shortcut
planarPlane motionRare; 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.

ShapeIxxIyyIzz
Solid box (m, x, y, z)m(y²+z²)/12m(x²+z²)/12m(x²+y²)/12
Solid cylinder (m, r, h) about axismr²/2mr²/2m(3r²+h²)/12
Solid sphere (m, r)2mr²/52mr²/52mr²/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

SymptomLikely causeCheck
Robot invisible in RVizFixed frame does not existTF tree and frame names
Model looks correct but sinksCollision geometry missing or mass zerocollision blocks and inertial mass
Wheels spin in the wrong planeJoint origin rotationrpy on the joint
Robot explodes in simulationInertia too small or negativeInertial values and units
TF warnings about multiple parentsLink connected twiceJoint tree is not a strict tree
Meshes missingPackage path not installedpackage:// 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

Pass it onWhatsAppLinkedIn

Keep reading

Continue learning

The Build Note

One short email when something is worth your bench time.

A new build sheet, a part price that moved, or a mistake worth avoiding. No daily drip, no recycled news.

One short email when a build or a price is worth your time. Unsubscribe in one click.