Been a while since I posted the first part in this ROS2 tutorial series, so let’s start with a quick recap. In the last blog, we created a simple robotic vehicle model that looked like this:

And the URDF that generated it looked something like this:
<?xml version="1.0"?>
<robot name="simple_vehicle">
<link name="base_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="2.0 1.2 0.4"/>
</geometry>
<material name="red">
<color rgba="1.0 0.2 0.2 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="2.0 1.2 0.4"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="200.0"/>
<inertia ixx="16.0" iyy="66.7" izz="80.7" ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
<!-- Left front wheel -->
<link name="fl_wheel">
<visual>
<origin xyz="0.0 0.0 0.0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.3" length= "0.2"/>
</geometry>
<material name="black">
<color rgba="0.1 0.1 0.1 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0.0 0.0 0.0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.3" length="0.2"/>
</geometry>
</collision>
<inertial>
<mass value="10.0"/>
<inertia ixx="0.45" iyy="0.45" izz="0.25" ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
<joint name="fl_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="fl_wheel"/>
<origin xyz="0.9 0.8 0.0"/>
</joint>
<!-- Right front wheel -->
<link name="fr_wheel">
<visual>
<origin xyz="0.0 0.0 0.0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.3" length= "0.2"/>
</geometry>
<material name="black">
<color rgba="0.1 0.1 0.1 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0.0 0.0 0.0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.3" length="0.2"/>
</geometry>
</collision>
<inertial>
<mass value="10.0"/>
<inertia ixx="0.45" iyy="0.45" izz="0.25" ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
<joint name="fr_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="fr_wheel"/>
<origin xyz="0.9 -0.8 0.0"/>
</joint>
<!-- Left rear wheel -->
<link name="rl_wheel">
<visual>
<origin xyz="0.0 0.0 0.0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.3" length= "0.2"/>
</geometry>
<material name="black">
<color rgba="0.1 0.1 0.1 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0.0 0.0 0.0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.3" length="0.2"/>
</geometry>
</collision>
<inertial>
<mass value="10.0"/>
<inertia ixx="0.45" iyy="0.45" izz="0.25" ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
<joint name="rl_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="rl_wheel"/>
<origin xyz="-0.9 0.8 0.0"/>
</joint>
<!-- Right rear wheel -->
<link name="rr_wheel">
<visual>
<origin xyz="0.0 0.0 0.0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.3" length= "0.2"/>
</geometry>
<material name="black">
<color rgba="0.1 0.1 0.1 1.0"/>
</material>
</visual>
<collision>
<origin xyz="0.0 0.0 0.0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="0.3" length="0.2"/>
</geometry>
</collision>
<inertial>
<mass value="10.0"/>
<inertia ixx="0.45" iyy="0.45" izz="0.25" ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
<joint name="rr_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="rr_wheel"/>
<origin xyz="-0.9 -0.8 0.0"/>
</joint>
</robot>
At first glance, this might look like a neat block of XML carefully handcrafted for each wheel. But if you peek closer, it’s really the same template copy-pasted four times with only small changes to names and positions.
That’s fine for a toy example — but as robots grow more complex, the URDF quickly becomes repetitive, hard to maintain, and a nightmare to edit.
Luckily, there’s a better way. Enter Xacro. In this post, we’ll take the exact same robot vehicle and rebuild it using Xacro. The end result will look the same in RViz, but under the hood the description will be modular, cleaner, and much easier to manage.
Table of Contents
What is Xacro?
Think of plain URDF like writing out an entire dinner recipe every time you want to cook. For spaghetti, you copy-paste: “boil water, add salt, cook pasta for 10 minutes”. For penne, you paste the same thing again, just swapping “spaghetti” for “penne.” It works — but your cookbook gets pretty messy.
Xacro is like giving yourself a reusable recipe card. You write “cook_pasta(type, time)” once, and then just call it: “cook_pasta(spaghetti, 10)” or “cook_pasta(penne, 12).” Same delicious result, but your recipe book is lean, organized, and much easier to update when you decide pasta really needs a pinch more salt. You get the idea.
So, in technical terms, Xacro (short for XML Macros) is a preprocessor for URDF files. Instead of writing the same XML over and over, you can define reusable chunks (called macros) and insert them wherever you need. You can also add variables (called properties), do math inside the XML, and even use simple conditionals.
Here is a small “hello world” version of a simple xacro:
<?xml version="1.0"?>
<!--
hello_world.xacro
A minimal example showing how to use Xacro macros and parameters
to create simple geometric links in a ROS 2 robot description.
-->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="hello_world_robot">
<!--
Macro: cube
Creates a box-shaped link and attaches it to a parent via a fixed joint.
Params:
name - name of the link
size - cube side length (m)
color - color material name
parent - parent link to attach to
xyz - offset position (x y z)
-->
<xacro:macro name="cube" params="name size color parent xyz">
<!-- Define the link -->
<link name="${name}">
<visual>
<geometry>
<box size="${size} ${size} ${size}"/>
</geometry>
<material name="${color}">
<!-- Simple color presets -->
<xacro:if value="${color == 'red'}">
<color rgba="1 0 0 1"/>
</xacro:if>
<xacro:if value="${color == 'green'}">
<color rgba="0 1 0 1"/>
</xacro:if>
<xacro:if value="${color == 'blue'}">
<color rgba="0 0 1 1"/>
</xacro:if>
</material>
</visual>
</link>
<!-- Attach via fixed joint -->
<joint name="joint_${name}" type="fixed">
<parent link="${parent}"/>
<child link="${name}"/>
<origin xyz="${xyz}" rpy="0 0 0"/>
</joint>
</xacro:macro>
<!-- Root link -->
<link name="base_link"/>
<!-- Instantiate a few cubes -->
<xacro:cube name="small_box" size="0.2" color="blue" parent="base_link" xyz="0 0 0.2"/>
<xacro:cube name="medium_box" size="0.5" color="green" parent="base_link" xyz="0.5 0 0.5"/>
<xacro:cube name="large_box" size="1" color="red" parent="base_link" xyz="-0.9 0.0 0.5"/>
</robot>
What’s happening here? We define a macro called cube
with three parameters: name
, size
, and color
. Inside the macro, we use those parameters with ${...}
placeholders. Basically, we are defining the template of the “cube” object or xacro. You may compare this to the classes we create in programming. Different instances or objects are created from that class, each having different parameter. Similarly, here we “call” the macro three times to generate three different links or instances. So when you run this file through Xacro, it expands into a URDF with three box links — no copy-paste needed.
Remember the urdf_tutorial
from the last blog where we played around with URDF files? Create a new file in the /urdf
folder of that package and name it hello_world.xacro. Copy-paste the above xacro code in it and build the package in your main ros2 workspace folder:
cd ~/ros2_ws
colcon build --packages-up-to urdf_tutorial
ros2 launch urdf_tutorial display.launch.py model:=urdf/hello_world.xacro
Run it like this:
ros2 run xacro xacro hello_world.urdf.xacro -o hello_world.urdf
If everything works, your RViz should show you something like this:

Feel free to tune the parameters of the boxes in the code in these lines:
<xacro:cube name="small_box" size="0.2" color="blue" parent="base_link" xyz="0 0 0.2"/>
<xacro:cube name="medium_box" size="0.5" color="green" parent="base_link" xyz="0.5 0 0.5"/>
<xacro:cube name="large_box" size="1" color="red" parent="base_link" xyz="-0.9 0.0 0.5"/>
Do not forget to build your package before running it so that the changes are realized by the compiler.
Now that we’ve seen how Xacro can simplify repetitive URDF structures, let’s use them by building something that we already this in the last blog — a simple vehicle. But this time, instead of repeating the code to define the wheels, we will combine multiple macros and reuse parameters that defines the vehicle.
Building a Vehicle in Xacro
Alright, time to build something that actually looks like it could drive!
In the previous blog, we modeled this same vehicle in plain URDF — and, as you probably remember, it got messy fast. Each wheel was basically a copy-paste of the same link and joint, just with a few numbers changed.
This is exactly the kind of problem Xacro was made to solve. Instead of repeating the same code four times, we’ll define a single wheel macro, and then just call it for each wheel, passing in parameters like its name and position.
Let’s start by setting up a new file for our vehicle. In the same /urdf
folder of urdf_tutorial package, create a file called my_vehicle.xacro and copy-paste the following code:
<?xml version="1.0"?>
<!--
vehicle.xacro
A simple example of using Xacro to create a vehicle-like robot
with a chassis and four wheels.
-->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="xacro_vehicle">
<!-- ========== Parameters ========== -->
<!-- You can tweak these at the top to reshape your robot -->
<xacro:property name="chassis_length" value="1.0"/>
<xacro:property name="chassis_width" value="0.6"/>
<xacro:property name="chassis_height" value="0.2"/>
<xacro:property name="wheel_radius" value="0.15"/>
<xacro:property name="wheel_width" value="0.05"/>
<!-- ========== Macros ========== -->
<!-- Chassis macro -->
<xacro:macro name="chassis" params="name color">
<link name="${name}">
<visual>
<geometry>
<box size="${chassis_length} ${chassis_width} ${chassis_height}"/>
</geometry>
<material name="${color}">
<color rgba="0.2 0.2 0.8 1"/>
</material>
</visual>
</link>
</xacro:macro>
<!-- Wheel macro -->
<xacro:macro name="wheel" params="name parent xyz color">
<link name="${name}">
<visual>
<geometry>
<cylinder length="${wheel_width}" radius="${wheel_radius}"/>
</geometry>
<material name="${color}">
<color rgba="0.1 0.1 0.1 1"/>
</material>
<origin rpy="1.5708 0 0"/> <!-- rotate to lay flat -->
</visual>
</link>
<joint name="joint_${name}" type="fixed">
<parent link="${parent}"/>
<child link="${name}"/>
<origin xyz="${xyz}" rpy="0 0 0"/>
</joint>
</xacro:macro>
<!-- ========== Robot Structure ========== -->
<!-- Root link -->
<xacro:chassis name="base_link" color="blue"/>
<!-- Wheels (x: forward/back, y: left/right, z: height) -->
<xacro:wheel name="front_left_wheel" parent="base_link" xyz="0.35 0.25 -0.15" color="black"/>
<xacro:wheel name="front_right_wheel" parent="base_link" xyz="0.35 -0.25 -0.15" color="black"/>
<xacro:wheel name="rear_left_wheel" parent="base_link" xyz="-0.35 0.25 -0.15" color="black"/>
<xacro:wheel name="rear_right_wheel" parent="base_link" xyz="-0.35 -0.25 -0.15" color="black"/>
</robot>
Before running this, take a few minutes to really read through the file and get a sense of its flow.
You’ll notice the file starts with a few parameters — things like the chassis size and wheel dimensions. These are declared using <xacro:property>
, and they make it easy to adjust your robot’s proportions later without digging through the whole file. Then we define two macros:
–> wheel
– another macro that creates a cylinder-shaped link for a wheel and attaches it to a parent link via a joint.
–> chassis
– a reusable block that defines a box-shaped link (the main body of the vehicle).
Each macro takes parameters like name
, parent
, xyz
, and color
, so we can reuse the same structure for multiple parts — each with its own position and label.
Finally, at the bottom, we have the robot structure itself. This is where the macros are called to build the full robot:
- One chassis (
<xacro:chassis name="base_link" color="blue"/>
) - Four wheels — each created from the same
<xacro:wheel>
macro, but placed at different coordinates around the chassis.
Notice that even though it looks like plain XML, Xacro adds an extra layer of logic on top. Each <xacro:macro>
acts like a mini template for a part of the robot (like a wheel or a chassis), and every time you call it, Xacro substitutes the parameters you provide. Instead of repeating long blocks of URDF for wheels, we simply reuse the same macro multiple times with different values for each wheel.
Now as you may have figured out, this new file needs to be acknowledged by the ros2 environment. Hence, you should build the package in the main ros2 workspace and run the launch command but make sure to change the urdf file to my_vehicle.xacro in the launch command. You should see the following in RViz:

What’s Next
And there we have it — our first Xacro-based robot!
We started with a bulky URDF full of copy-paste chaos in the previous blog, and turned it into a clean, parameterized, and modular Xacro file in this one that’s far easier to maintain and expand. Now, adjusting dimensions, colors, or even wheel positions takes seconds instead of hours. That’s the significance of Xacro — structure and scalability without sacrificing readability.
But we’re not stopping here. A vehicle that just sits still isn’t much fun, is it? In the next part of this series, we’ll take this vehicle for a ride. We’ll add joints that move, controllers that spin the wheels, and maybe even a launch file that makes it drive in simulation. In short — we’ll turn our static model into a moving robot.
Till then, create your own structures, define your custom Xacros and tag me on Instagram @machinelearningsite to show your designs. In case of doubts or issues, feel free to ping me via DM. Enjoy!