Build a Robot Arm URDF from Scratch

Write a URDF file from scratch to model a 3-DOF robot arm in VS Code.

Introduction

30 Second Summary

Robots need a blueprint before they can move. Whether it's a warehouse arm sorting packages or a surgical robot in an operating room, every robot starts as a description file that defines its shape, joints, and how far each part can move.

In this project, you will write a URDF (Unified Robot Description Format) file from scratch that describes a robot arm with a base, two arm segments, and a gripper. You'll define every piece and connection by hand, then watch your robot come to life in a 3D preview.

What You'll Build

A fully-defined URDF robot arm model written from scratch in XML, with a 3D visualization you can rotate and inspect inside VS Code.

By the end of this project, you'll have:

  • A .urdf file describing a 3-DOF robot arm with a gripper, built entirely from scratch.
  • A working 3D visualization of your robot directly inside VS Code that you can orbit and inspect.
  • Hands-on understanding of every core URDF element: links, joints, visual geometry, collision geometry, and materials.
  • Secret Mission: Add a prismatic sliding rail so your robot arm can translate along a track.

Are there any prerequisites?

None. This project includes full setup instructions for every tool you need. You don't need any prior robotics experience or ROS installed.

Before We Start

URDF (Unified Robot Description Format) is the standard file format used across robotics to describe the physical structure of a robot. Simulation tools, motion planners, and visualizers all rely on URDF to understand how a robot is built.

In this project, you'll write a complete URDF file from scratch that describes a 3-DOF robot arm with a gripper. Before we start building, you need a code editor and a way to preview your robot in 3D.

In this step, get ready to:
  • Install VS Code.
  • Install the Robot Developer Extensions for URDF.
  • Create your project folder and URDF file.
Install VS Code

VS Code is a free code editor that supports extensions for specialized file types like URDF. You might already have it installed.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter.

✔️ VS Code opened

You already have VS Code installed. You're ready to move on to the next substep.

ⓧ VS Code didn't open

  • Open your browser and go to https://code.visualstudio.com.
  • Click the download button. The site detects your operating system automatically.
  • Run the installer once it downloads. Accept the default settings.
  • Once the install finishes, open VS Code using the search method above.

Installer not running?

On macOS, you may need to drag the VS Code icon into your Applications folder before it appears in search. On Windows, make sure you check the box to add VS Code to your PATH during installation.

help me install VS Code on my computer

Install the Robot Developer Extensions for URDF

This extension gives you syntax highlighting, schema validation, 3D preview, IntelliSense, and Go-to-Definition for URDF files. It works without ROS installed.

  • Open the Extensions panel by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
  • In the search bar at the top, type Robot Developer Extensions for URDF.
  • Find the result by Ranch Hand Robotics.
  • Click Install.

What does this extension do?

The Robot Developer Extensions for URDF uses a standalone Xacro parser (no ROS required) to give you real-time validation, autocomplete suggestions, and a 3D viewer powered by BabylonJS. You'll use the 3D preview throughout this project to see your robot take shape.

You should see a blue Installed badge next to the extension name in the Extensions panel.

Can't find the extension?

Make sure you're searching for the exact name: Robot Developer Extensions for URDF. The publisher should say Ranch Hand Robotics. If no results appear, check your internet connection and try again.

help me find and install the URDF extension in VS Code

Create your project folder and URDF file

Every URDF project needs a folder to hold its files. You'll create a dedicated folder on your Desktop and then set up your first URDF file inside it.

  • Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, next to the 1 key) on both macOS and Windows.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder by running this command:
mkdir my_robot_arm
  • Now open this folder in VS Code by clicking File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the my_robot_arm folder, and click Open.

Why open the folder this way?

Opening a folder in VS Code sets it as your workspace root. The URDF extension uses this workspace context for features like schema validation and 3D preview. Opening just a single file (without the folder) would not give you the full extension experience.

  • In the Explorer sidebar on the left, hover over the MY_ROBOT_ARM heading.
  • Click the New File icon (looks like a page with a plus sign).
  • Type robot_arm.urdf and press Enter.

You should see robot_arm.urdf appear in the Explorer sidebar, and an empty editor tab opens for it.

Don't see the Explorer sidebar?

Press Cmd+Shift+E (macOS) or Ctrl+Shift+E (Windows) to toggle the Explorer panel. If you still don't see your folder name at the top, you may not have opened the folder correctly. Try File > Open Folder again.

help me get the Explorer sidebar showing in VS Code

Your workspace is ready. Next up, you'll write your first URDF elements and see a 3D robot part appear in the preview.

Set Up VS Code and the URDF Extension

To build a robot model, you need an editor that understands URDF and can show you the robot in 3D as you write it. VS Code with the Robot Developer Extensions for URDF handles both.

In this step, you'll open your robot file and launch the 3D preview panel. This confirms your tooling is wired up correctly before you start defining links and joints.

In this step, get ready to:
  • Open robot_arm.urdf in the VS Code editor.
  • Launch the URDF 3D Preview panel and confirm it loads.
Open your URDF file in the editor

Your my_robot_arm folder is already open in VS Code with robot_arm.urdf inside it. You need to open the file in the editor so you can start writing XML in the next step.

  • In the Explorer sidebar on the left, double-click robot_arm.urdf to open it in the editor.

You should see an empty editor tab labelled robot_arm.urdf in the main panel.

What is URDF?

URDF (Unified Robot Description Format) is an XML file format for describing the physical structure of a robot. It defines geometry, materials, and how parts connect through joints.

Every major robotics tool uses URDF as input. Gazebo uses it for simulation, MoveIt uses it for motion planning, and RViz uses it for visualization.

Don't see the file in the Explorer?

Make sure the Explorer sidebar is visible. Press Ctrl+Shift+E (Windows) or Cmd+Shift+E (macOS) to toggle it.

If the folder appears empty, confirm you have the my_robot_arm folder open (not a parent folder). Check the folder name at the top of the Explorer panel.

help me find my robot_arm.urdf file in VS Code

Launch the URDF 3D Preview

The Robot Developer Extensions for URDF adds a 3D preview feature that renders your robot model directly inside VS Code. You can orbit, zoom, and inspect the model without leaving your editor.

  • With robot_arm.urdf open in the editor, press Ctrl+Shift+P (Windows) or Cmd+Shift+P (macOS) to open the Command Palette.
  • Type URDF: Preview and press Enter.

A new panel opens beside your editor showing an empty 3D viewport. The scene is blank because your file has no content yet.

Why is the preview empty?

The 3D preview renders whatever your URDF file describes. Since the file is currently empty, the viewport shows a blank scene with just the grid and axes.

As you add links and joints in the next steps, the preview updates to show your robot taking shape.

Keep the preview open

Leave the preview panel open side-by-side with your editor. Each time you save your URDF file, the 3D view refreshes so you can see your changes instantly.

Preview command not showing up?

Make sure the Robot Developer Extensions for URDF extension is installed and enabled. Press Ctrl+Shift+X (Windows) or Cmd+Shift+X (macOS) to open Extensions and search for Robot Developer Extensions for URDF.

Confirm it shows Installed and is by Ranch Hand Robotics. If it is disabled, click Enable and try the preview command again.

help me get the URDF Preview command working in VS Code

Your editor and preview are ready. Next up, you'll define the robot's base link and see your first shape appear in the 3D viewport.

Define the Robot and Base Link

Your workspace is ready with an empty robot_arm.urdf file open in VS Code. Now it's time to bring this file to life by writing your first robot structure.

Every URDF file is XML with a single <robot> root element containing <link> elements for physical pieces and <joint> elements for connections. In this step, you'll set up the file, define reusable colors, and create a cylinder base.

In this step, get ready to:
  • Write the XML declaration, root robot tag, and material definitions.
  • Define the base link with a cylinder geometry and a color.
  • Preview your robot in the 3D viewport.
Write the robot structure and material definitions

Before you can add physical pieces to your robot, you need the file scaffolding and a color palette. URDF lets you define materials globally so every link can reference them by name.

  • In the robot_arm.urdf file open in VS Code, paste the following code to create the XML declaration, robot root element, and four reusable material colors:
<?xml version="1.0"?>
<robot name="my_robot_arm">

  <!-- Materials -->
  <material name="blue">
    <color rgba="0.2 0.2 0.8 1.0"/>
  </material>

  <material name="orange">
    <color rgba="0.9 0.5 0.1 1.0"/>
  </material>

  <material name="green">
    <color rgba="0.2 0.8 0.2 1.0"/>
  </material>

  <material name="grey">
    <color rgba="0.5 0.5 0.5 1.0"/>
  </material>

</robot>

What does this code do?

The first line is the standard XML declaration telling parsers this is an XML document. The <robot name="my_robot_arm"> tag is the root element that wraps everything in your URDF file.

Each <material> block defines a named color using RGBA values (red, green, blue, alpha). Defining materials globally means any link can reference a color by name without repeating the RGBA values.

  • Save robot_arm.urdf.
  • You won't see anything in the 3D Preview yet. Materials define colors but don't create visible shapes on their own. The next chunk adds the first visible piece.
Add the base link with a cylinder geometry

A link is a physical piece of your robot. Each link can have a <visual> section that describes what it looks like in the 3D preview.

  • In robot_arm.urdf, find the closing </robot> tag. Add the following code directly above it:
  <!-- Base Link -->
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
      <material name="blue"/>
    </visual>
  </link>

What does this code do?

The <link name="base_link"> element creates a physical piece called base_link. This will be the flat foundation of your robot arm.

Inside <visual>, three things describe what the piece looks like:

  • <geometry> sets the shape. A cylinder with radius="0.1" and length="0.05" creates a wide, flat disk (10 cm radius, 5 cm tall).
  • <origin xyz="0 0 0.025"> shifts the cylinder up by half its height so it sits on the ground plane instead of poking through it.
  • <material name="blue"/> references the blue color you defined in the materials section above.
  • Save robot_arm.urdf.
  • Switch to the URDF 3D Preview panel. You should see a blue cylinder (a flat disk shape) appear in the viewport.

Don't see the blue cylinder?

  • Make sure you saved the file. The preview updates after the file is saved.
  • If the preview still shows a blank scene, re-run the preview command by pressing Ctrl+Shift+P and typing URDF: Preview.
  • Check that the base link code is above the closing </robot> tag, not below it. Anything outside the root tag is invalid XML.

Still stuck? Why isn't my URDF preview showing the blue cylinder after saving?

Explore the 3D preview

The 3D Preview panel gives you an interactive view of your robot model. You can inspect it from any angle.

  • Click and drag inside the 3D Preview panel to orbit around the cylinder.
  • Scroll to zoom in and out. You should see a flat blue disk from above and a short cylinder from the side.

Why is the base so flat?

A robot arm's base is typically a low, heavy platform that keeps the arm stable. The cylinder is only 5 cm tall but 20 cm in diameter, giving it a wide footprint. Everything else will stack on top of it.

✔️ Awesome, I've got everything!

Great. Double check you've saved robot_arm.urdf and can see the blue cylinder in your 3D Preview.

ⓧ I'd like to double check the full code

Here's the complete robot_arm.urdf file at the end of this step:

<?xml version="1.0"?>
<robot name="my_robot_arm">

  <!-- Materials -->
  <material name="blue">
    <color rgba="0.2 0.2 0.8 1.0"/>
  </material>

  <material name="orange">
    <color rgba="0.9 0.5 0.1 1.0"/>
  </material>

  <material name="green">
    <color rgba="0.2 0.8 0.2 1.0"/>
  </material>

  <material name="grey">
    <color rgba="0.5 0.5 0.5 1.0"/>
  </material>

  <!-- Base Link -->
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
      <material name="blue"/>
    </visual>
  </link>

</robot>

Your robot has a base. Next up, you'll add the first arm segment and connect it to the base with a revolute joint that lets it rotate.

Add the First Arm Segment with a Revolute Joint

Your robot has a base, but a stationary cylinder isn't much of an arm. To build upward, you need a second piece (a link) and a connection point that allows rotation (a joint).

In this step, you'll define a shoulder segment and attach it to the base with a revolute joint. A revolute joint is a hinge that rotates around a single axis within defined angular limits.

In this step, get ready to:
  • Define a new link representing the shoulder arm segment.
  • Connect it to the base with a revolute joint.
  • Preview the updated robot to confirm both pieces are attached.
Define the shoulder link

A link in URDF represents a rigid body. Your shoulder link will be a tall box that acts as the first arm segment.

  • In robot_arm.urdf, find the closing </link> tag of base_link.
  • Add the following code directly below it (before the closing </robot> tag):
  <!-- Shoulder Link -->
  <link name="shoulder_link">
    <visual>
      <geometry>
        <box size="0.05 0.05 0.2"/>
      </geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <material name="orange"/>
    </visual>
  </link>

What does this code do?

  • <link name="shoulder_link"> creates a new rigid body called shoulder_link.
  • <box size="0.05 0.05 0.2"/> defines a box 5cm wide, 5cm deep, and 20cm tall.
  • <origin xyz="0 0 0.1"/> shifts the visual geometry up by 10cm so the bottom of the box sits at the link's origin instead of the center.
  • <material name="orange"/> references the orange color you defined at the top of the file.
  • Save robot_arm.urdf.
  • You won't see the shoulder link in the correct position yet. The next chunk (the joint) tells the robot where to attach it.
Create the revolute joint

A joint connects two links and defines how they move relative to each other. A revolute joint acts like a hinge, allowing rotation around a single axis within defined angle limits.

  • In robot_arm.urdf, add the following code directly below the shoulder_link you just added (still before the closing </robot> tag):
  <!-- Shoulder Joint (revolute) -->
  <joint name="shoulder_joint" type="revolute">
    <parent link="base_link"/>
    <child link="shoulder_link"/>
    <origin xyz="0 0 0.05" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="10.0" velocity="1.0"/>
  </joint>

What does this code do?

  • <joint name="shoulder_joint" type="revolute"> declares a hinge joint with angular limits.
  • <parent link="base_link"/> and <child link="shoulder_link"/> define which links this joint connects. The child moves relative to the parent.
  • <origin xyz="0 0 0.05"/> positions the joint at the top of the base cylinder (which is 5cm tall).
  • <axis xyz="0 1 0"/> sets the rotation axis to the Y-axis, so the shoulder rotates forward and backward.
  • <limit lower="-1.57" upper="1.57" effort="10.0" velocity="1.0"/> constrains rotation to approximately -90 to +90 degrees (in radians). The effort is max torque in Nm, and velocity is max speed in rad/s.
  • Save robot_arm.urdf.
  • Open the URDF preview by pressing Ctrl+Shift+P and typing URDF: Preview, then pressing Enter. If the preview is already open, it should refresh automatically.
  • You should see the blue cylinder base with an orange box arm segment sitting on top of it.

Don't see the orange arm segment?

  • Make sure you saved the file. Unsaved changes won't appear in the preview.
  • Check that your <parent link="base_link"/> matches the exact name you used in your link definition. URDF names are case-sensitive.
  • Confirm the joint code is inside the <robot> tag but not nested inside a <link> tag. Joints and links are siblings, not nested.

Why isn't my shoulder link showing in the URDF preview?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete robot_arm.urdf file so far:

<?xml version="1.0"?>
<robot name="my_robot_arm">

  <!-- Materials -->
  <material name="blue">
    <color rgba="0.2 0.2 0.8 1.0"/>
  </material>

  <material name="orange">
    <color rgba="0.9 0.5 0.1 1.0"/>
  </material>

  <material name="green">
    <color rgba="0.2 0.8 0.2 1.0"/>
  </material>

  <material name="grey">
    <color rgba="0.5 0.5 0.5 1.0"/>
  </material>

  <!-- Base Link -->
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
      <material name="blue"/>
    </visual>
  </link>

  <!-- Shoulder Link -->
  <link name="shoulder_link">
    <visual>
      <geometry>
        <box size="0.05 0.05 0.2"/>
      </geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <material name="orange"/>
    </visual>
  </link>

  <!-- Shoulder Joint (revolute) -->
  <joint name="shoulder_joint" type="revolute">
    <parent link="base_link"/>
    <child link="shoulder_link"/>
    <origin xyz="0 0 0.05" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="10.0" velocity="1.0"/>
  </joint>

</robot>

Your robot now has a moving shoulder. Next up, you'll extend the arm further with a second segment and an elbow joint.

Build the Upper Arm and Elbow Joint

Your robot now has a base and a shoulder segment connected by a revolute joint. The next piece of the kinematic chain is the upper arm.

In this step, you'll define a long, thin cylinder to represent the upper arm and connect it to the shoulder with an elbow joint. This gives your robot two degrees of freedom: rotation at the shoulder and rotation at the elbow.

In this step, get ready to:
  • Define the upper arm link with cylinder geometry.
  • Connect it to the shoulder with a revolute elbow joint.
  • Preview all three parts in the 3D viewport.
Define the upper arm link

The upper arm is a thin cylinder that extends outward from the shoulder. You'll use a different material color so each segment is visually distinct in the preview.

  • In robot_arm.urdf, find the closing </joint> tag of shoulder_joint.
  • Below it (but above the closing </robot> tag), add the following code for the upper arm link:
  <!-- Upper Arm Link -->
  <link name="upper_arm_link">
    <visual>
      <geometry>
        <cylinder radius="0.02" length="0.25"/>
      </geometry>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
      <material name="green"/>
    </visual>
  </link>

What does this code do?

This defines the upper arm as a cylinder with a radius of 0.02 meters and a length of 0.25 meters. It's much thinner than the base, mimicking the proportions of a real robot arm segment.

The <origin xyz="0 0 0.125"> shifts the cylinder's visual center upward by half its length (0.25 / 2 = 0.125). Without this offset, the cylinder would be centered at the joint attachment point, with half of it poking below.

Create the elbow joint

Joints define how two links move relative to each other. The elbow joint connects the shoulder link (parent) to the upper arm link (child), allowing rotation around the Y axis.

  • Directly below the upper_arm_link you just added, paste the following elbow joint code:
  <!-- Elbow Joint (revolute) -->
  <joint name="elbow_joint" type="revolute">
    <parent link="shoulder_link"/>
    <child link="upper_arm_link"/>
    <origin xyz="0 0 0.2" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-2.0" upper="2.0" effort="10.0" velocity="1.0"/>
  </joint>

What does this code do?

The <origin xyz="0 0 0.2"> places this joint at the top of the shoulder link. The shoulder link's box is 0.2 meters tall, so z=0.2 puts the elbow right at its tip.

The <axis xyz="0 1 0"/> means the elbow rotates around the Y axis, the same axis as the shoulder. This gives a forward-backward bending motion.

The <limit> sets a wider range of -2.0 to 2.0 radians (about ±115 degrees). This is more than the shoulder's ±90 degrees, giving the elbow extra flexibility.

Preview the three-part arm
  • Save robot_arm.urdf.
  • Return to the URDF 3D Preview from earlier. The preview refreshes when you save the file.

You should now see three distinct parts: a blue cylinder base, an orange box shoulder segment, and a green cylinder upper arm extending upward from the shoulder.

Don't see the green upper arm?

  • Make sure the code is placed after the shoulder_joint closing tag but before the </robot> tag. Any code outside the <robot> element is ignored.
  • Check that <parent link="shoulder_link"/> in the elbow joint exactly matches the link name shoulder_link defined above. A single character mismatch breaks the kinematic chain.
  • If the preview didn't update, close the preview tab and reopen it by pressing Ctrl+Shift+P and selecting URDF: Preview.

Still stuck? Ask for help.

✔️ Awesome, I've got everything!

Great. Double check you've saved robot_arm.urdf and your preview shows all three segments.

ⓧ I'd like to double check the full code

<?xml version="1.0"?>
<robot name="my_robot_arm">

  <!-- Materials -->
  <material name="blue">
    <color rgba="0.2 0.2 0.8 1.0"/>
  </material>

  <material name="orange">
    <color rgba="0.9 0.5 0.1 1.0"/>
  </material>

  <material name="green">
    <color rgba="0.2 0.8 0.2 1.0"/>
  </material>

  <material name="grey">
    <color rgba="0.5 0.5 0.5 1.0"/>
  </material>

  <!-- Base Link -->
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
      <material name="blue"/>
    </visual>
  </link>

  <!-- Shoulder Link -->
  <link name="shoulder_link">
    <visual>
      <geometry>
        <box size="0.05 0.05 0.2"/>
      </geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <material name="orange"/>
    </visual>
  </link>

  <!-- Shoulder Joint (revolute) -->
  <joint name="shoulder_joint" type="revolute">
    <parent link="base_link"/>
    <child link="shoulder_link"/>
    <origin xyz="0 0 0.05" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="10.0" velocity="1.0"/>
  </joint>

  <!-- Upper Arm Link -->
  <link name="upper_arm_link">
    <visual>
      <geometry>
        <cylinder radius="0.02" length="0.25"/>
      </geometry>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
      <material name="green"/>
    </visual>
  </link>

  <!-- Elbow Joint (revolute) -->
  <joint name="elbow_joint" type="revolute">
    <parent link="shoulder_link"/>
    <child link="upper_arm_link"/>
    <origin xyz="0 0 0.2" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-2.0" upper="2.0" effort="10.0" velocity="1.0"/>
  </joint>

</robot>

Your robot arm now has two degrees of freedom. Next up, you'll add the gripper and give every link collision geometry for physics simulation.

Add the Gripper with a Fixed Joint

Your robot arm now has three parts connected by revolute joints that allow rotation. The last piece of the puzzle is an end effector: the gripper that sits at the tip of the upper arm.

In this step, you'll attach a gripper using a fixed joint (a rigid, non-moving connection) and add collision geometry to every link. Collision geometry defines the physical boundary that simulation engines use for detecting contact between your robot and the world.

In this step, get ready to:
  • Define the gripper link and attach it with a fixed joint.
  • Add collision geometry to all four links.
  • Preview the complete four-part robot arm.
Define the gripper link and fixed joint

The gripper sits at the tip of the upper arm. Unlike the shoulder and elbow, it doesn't rotate independently. A fixed joint rigidly locks two links together so they move as one piece.

  • In robot_arm.urdf, scroll to the bottom of your file and find the closing </joint> tag of elbow_joint (the last element before </robot>).
  • Add the following code below the elbow_joint closing tag, before </robot>:
  <!-- Gripper Link -->
  <link name="gripper_link">
    <visual>
      <geometry>
        <!-- Small flat box representing a gripper plate -->
        <box size="0.08 0.02 0.04"/>
      </geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <material name="grey"/>
    </visual>
  </link>

  <!-- Gripper Joint (fixed) -->
  <joint name="gripper_joint" type="fixed">
    <parent link="upper_arm_link"/>
    <child link="gripper_link"/>
    <!-- Position the gripper at the end of the upper arm -->
    <origin xyz="0 0 0.25" rpy="0 0 0"/>
  </joint>

What does this code do?

The gripper link uses a flat box geometry (0.08 x 0.02 x 0.04 meters) to represent a simple gripper plate.

The joint type is fixed instead of revolute. This means the gripper is permanently locked to the upper arm with no movement. Fixed joints don't need <axis> or <limit> elements because there's nothing to constrain.

The origin xyz="0 0 0.25" places the gripper at the end of the upper arm cylinder (which has a length of 0.25 meters).

  • Save robot_arm.urdf.
  • Check the 3D Preview. You should now see a small grey box at the tip of the green cylinder.

Don't see the grey gripper?

  • Make sure the <parent link="upper_arm_link"/> in gripper_joint matches the exact name upper_arm_link defined in your link element. URDF names are case-sensitive.
  • Confirm the gripper_joint element is placed after both the gripper_link and upper_arm_link definitions. Joints must reference links that already exist in the file.

Still stuck? Help me debug why my gripper link isn't showing in the URDF preview.

Add collision geometry to every link

Collision geometry defines the shape a physics engine uses to detect contact between objects. In production robots, collision shapes are often simplified versions of the visual. For primitive shapes like ours, they're identical.

  • In robot_arm.urdf, find the base_link element. Locate the closing </visual> tag inside it.
  • Add this collision block directly after the </visual> tag (still inside <link name="base_link">):
    <collision>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
    </collision>
  • Find shoulder_link. Add this collision block after its </visual> tag:
    <collision>
      <geometry>
        <box size="0.05 0.05 0.2"/>
      </geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
    </collision>
  • Find upper_arm_link. Add this collision block after its </visual> tag:
    <collision>
      <geometry>
        <cylinder radius="0.02" length="0.25"/>
      </geometry>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
    </collision>
  • Find gripper_link (the one you just added). Add this collision block after its </visual> tag:
    <collision>
      <geometry>
        <box size="0.08 0.02 0.04"/>
      </geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
    </collision>

Why add collision geometry separately from visual?

In URDF, visual and collision geometry serve different purposes. Visual geometry is what you see in a viewer. Collision geometry is what a physics simulator (like Gazebo) uses to calculate contacts and forces.

For complex mesh-based robots, collision shapes are often simplified (a cylinder instead of a detailed arm mesh) to speed up physics calculations. For our primitive shapes, the collision matches the visual exactly.

  • Save robot_arm.urdf.
  • The 3D Preview won't look different visually because collision geometry is invisible by default. The extension includes a collision visualization toggle if you want to verify.

Seeing XML errors in the Problems panel?

  • Check that each <collision> block is inside a <link> element (between the opening <link> and closing </link> tags), not outside it.
  • Make sure you placed the collision block after </visual> but before </link>. Placing it outside the link is the most common mistake.
  • Verify every opening tag has a matching closing tag. A missing </collision> or </geometry> will cascade errors throughout the file.

Need help? Help me fix the XML validation errors in my URDF file after adding collision elements.

Preview the complete robot arm
  • Right-click robot_arm.urdf and select Preview (or press Ctrl+Shift+P and type URDF: Preview).
  • Orbit the view by dragging with the mouse to inspect all four parts.

You should see four distinct parts connected in a chain: a blue cylinder base, an orange box shoulder segment, a green cylinder upper arm, and a small grey box gripper at the tip.

What is a kinematic chain?

Your robot's structure forms a kinematic chain: a series of links connected by joints in a tree structure. Each joint defines how one link moves relative to its parent. The chain flows from base_link through the shoulder and elbow joints, ending at gripper_link.

This tree structure (no loops allowed) is how every robotics tool navigates your robot's geometry and plans motion.

✔️ Awesome, I've got everything!

Great. Double check you've saved robot_arm.urdf and that the 3D Preview shows all four parts.

ⓧ I'd like to double check the full code

Here's the complete robot_arm.urdf file at this point. Compare yours against it:

<?xml version="1.0"?>
<robot name="my_robot_arm">

  <!-- Materials -->
  <material name="blue">
    <color rgba="0.2 0.2 0.8 1.0"/>
  </material>

  <material name="orange">
    <color rgba="0.9 0.5 0.1 1.0"/>
  </material>

  <material name="green">
    <color rgba="0.2 0.8 0.2 1.0"/>
  </material>

  <material name="grey">
    <color rgba="0.5 0.5 0.5 1.0"/>
  </material>

  <!-- Base Link -->
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
      <material name="blue"/>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
    </collision>
  </link>

  <!-- Shoulder Link -->
  <link name="shoulder_link">
    <visual>
      <geometry>
        <box size="0.05 0.05 0.2"/>
      </geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <material name="orange"/>
    </visual>
    <collision>
      <geometry>
        <box size="0.05 0.05 0.2"/>
      </geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
    </collision>
  </link>

  <!-- Shoulder Joint (revolute) -->
  <joint name="shoulder_joint" type="revolute">
    <parent link="base_link"/>
    <child link="shoulder_link"/>
    <origin xyz="0 0 0.05" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="10.0" velocity="1.0"/>
  </joint>

  <!-- Upper Arm Link -->
  <link name="upper_arm_link">
    <visual>
      <geometry>
        <cylinder radius="0.02" length="0.25"/>
      </geometry>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
      <material name="green"/>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.02" length="0.25"/>
      </geometry>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
    </collision>
  </link>

  <!-- Elbow Joint (revolute) -->
  <joint name="elbow_joint" type="revolute">
    <parent link="shoulder_link"/>
    <child link="upper_arm_link"/>
    <origin xyz="0 0 0.2" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-2.0" upper="2.0" effort="10.0" velocity="1.0"/>
  </joint>

  <!-- Gripper Link -->
  <link name="gripper_link">
    <visual>
      <geometry>
        <box size="0.08 0.02 0.04"/>
      </geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <material name="grey"/>
    </visual>
    <collision>
      <geometry>
        <box size="0.08 0.02 0.04"/>
      </geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
    </collision>
  </link>

  <!-- Gripper Joint (fixed) -->
  <joint name="gripper_joint" type="fixed">
    <parent link="upper_arm_link"/>
    <child link="gripper_link"/>
    <origin xyz="0 0 0.25" rpy="0 0 0"/>
  </joint>

</robot>

Your robot arm is structurally complete. Next up, you'll validate the URDF and inspect how the kinematic chain is wired together.

Validate and Inspect Your URDF

Your robot arm is fully assembled with all four links and three joints. Before calling it done, you need to make sure the file has no hidden errors that would cause problems in a simulator or motion planner.

In this step, you will use the extension's built-in validation and navigation tools to confirm your URDF is structurally correct. A clean validation means your file is ready for any robotics tool that accepts URDF.

In this step, get ready to:
  • Check the Problems panel for validation errors.
  • Use Go-to-Definition and hover documentation to inspect your kinematic chain.
  • Confirm the full kinematic chain flows correctly from base to gripper.
Check for validation errors

The Robot Developer Extensions for URDF extension provides XML schema validation that checks your file for structural problems. These include missing required attributes, misspelled tags, and links referenced in joints but never defined.

  • Open the Problems panel by pressing Ctrl+Shift+M.
  • Look through the list for any warnings or errors related to robot_arm.urdf.
  • If you see zero errors and zero warnings, your file passes validation.

What does schema validation check?

Schema validation checks that every element in your URDF follows the correct structure. For example, it verifies that every <joint> has a <parent> and <child> element, and that every link name referenced in a joint actually exists as a defined <link> in your file.

Catching these errors now prevents confusing failures later when you load the file into a simulator or motion planner.

Seeing errors in the Problems panel?

Common issues to check:

  • A misspelled tag name (e.g. <jont> instead of <joint>). Check that all element names match the URDF spec exactly.
  • A link name in a <parent> or <child> tag that does not match any <link name="..."> in your file. Names are case-sensitive.
  • A missing <limit> element on a revolute joint. Revolute joints require lower, upper, effort, and velocity attributes.

Fix any flagged issues, save the file, and the Problems panel updates automatically.

Help me fix a URDF validation error

Use Go-to-Definition to navigate your kinematic chain

Go-to-Definition lets you click on a link name inside a joint and jump directly to where that link is defined. This confirms that every joint is wired to the correct parent and child.

  • In robot_arm.urdf, find the shoulder_joint element.
  • Click on base_link inside the <parent link="base_link"/> line.
  • Press F12 to jump to the base_link definition.
  • Your cursor jumps to the <link name="base_link"> line, confirming the connection is valid.
  • Now hover your mouse over the <joint> tag on any joint element.
  • You should see inline documentation showing the available parameters and what each attribute means.
  • Try hovering over <axis>, <limit>, and <origin> elements to see their documentation as well.

Why is Go-to-Definition useful for URDF?

URDF files define links in one place and reference them by name in joints. As your robot grows larger, it becomes easy to lose track of which joints connect which links. Go-to-Definition lets you trace the kinematic chain without scrolling through the whole file.

Review the complete kinematic chain

The final check is to confirm the chain of parent-child relationships forms a correct tree from base to gripper, with no loops or disconnected links.

  • Scroll through your file and trace the kinematic chain:
  • base_link connects via shoulder_joint to shoulder_link.
  • shoulder_link connects via elbow_joint to upper_arm_link.
  • upper_arm_link connects via gripper_joint to gripper_link.
  • Confirm that each joint's <parent> and <child> elements match this chain. Every link appears exactly once as a child (except base_link, which is the root).

Why must URDF be a tree?

URDF requires a strict tree structure. Every link has exactly one parent joint (except the root link). No loops are allowed. This constraint ensures that forward kinematics can always compute a unique position for every link given the joint angles.

If you ever need closed-loop chains (like a parallel-link gripper), you would use SDF (Simulation Description Format) instead of URDF.

  • Open the 3D Preview one more time by pressing Ctrl+Shift+P and typing URDF: Preview.
  • Orbit the view by dragging with your mouse to inspect all four parts: the blue base cylinder, orange shoulder box, green upper arm cylinder, and grey gripper plate.

Your robot arm is complete, validated, and ready for use in any robotics tool that accepts URDF.

✔️ Awesome, I've got everything!

Great. Make sure you have saved robot_arm.urdf and the Problems panel shows zero errors.

ⓧ I'd like to double check the full code

Here is the complete robot_arm.urdf file. Compare your file to this version and correct any differences.

<?xml version="1.0"?>
<robot name="my_robot_arm">

  <!-- Materials -->
  <material name="blue">
    <color rgba="0.2 0.2 0.8 1.0"/>
  </material>

  <material name="orange">
    <color rgba="0.9 0.5 0.1 1.0"/>
  </material>

  <material name="green">
    <color rgba="0.2 0.8 0.2 1.0"/>
  </material>

  <material name="grey">
    <color rgba="0.5 0.5 0.5 1.0"/>
  </material>

  <!-- Base Link -->
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
      <material name="blue"/>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <origin xyz="0 0 0.025" rpy="0 0 0"/>
    </collision>
  </link>

  <!-- Shoulder Link -->
  <link name="shoulder_link">
    <visual>
      <geometry>
        <box size="0.05 0.05 0.2"/>
      </geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <material name="orange"/>
    </visual>
    <collision>
      <geometry>
        <box size="0.05 0.05 0.2"/>
      </geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
    </collision>
  </link>

  <!-- Shoulder Joint (revolute) -->
  <joint name="shoulder_joint" type="revolute">
    <parent link="base_link"/>
    <child link="shoulder_link"/>
    <origin xyz="0 0 0.05" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="10.0" velocity="1.0"/>
  </joint>

  <!-- Upper Arm Link -->
  <link name="upper_arm_link">
    <visual>
      <geometry>
        <cylinder radius="0.02" length="0.25"/>
      </geometry>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
      <material name="green"/>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.02" length="0.25"/>
      </geometry>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
    </collision>
  </link>

  <!-- Elbow Joint (revolute) -->
  <joint name="elbow_joint" type="revolute">
    <parent link="shoulder_link"/>
    <child link="upper_arm_link"/>
    <origin xyz="0 0 0.2" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-2.0" upper="2.0" effort="10.0" velocity="1.0"/>
  </joint>

  <!-- Gripper Link -->
  <link name="gripper_link">
    <visual>
      <geometry>
        <box size="0.08 0.02 0.04"/>
      </geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <material name="grey"/>
    </visual>
    <collision>
      <geometry>
        <box size="0.08 0.02 0.04"/>
      </geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
    </collision>
  </link>

  <!-- Gripper Joint (fixed) -->
  <joint name="gripper_joint" type="fixed">
    <parent link="upper_arm_link"/>
    <child link="gripper_link"/>
    <origin xyz="0 0 0.25" rpy="0 0 0"/>
  </joint>

</robot>

Secret mission

Add a Prismatic Sliding Rail

Your robot arm is anchored in place. Real industrial robots often slide along linear rails to extend their reach. Can you add a prismatic joint that lets your arm translate along a track?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing costs or cloud resources to worry about. Decide whether to keep your project files and extension installed, or remove them entirely.

Resources you used:

  • The my_robot_arm folder containing robot_arm.urdf.
  • The Robot Developer Extensions for URDF VS Code extension.

Keep everything running

No action needed. Choose this if you want to keep experimenting with your URDF file or plan to extend the robot arm further.

  • Your my_robot_arm folder stays wherever you created it.
  • The URDF extension stays installed in VS Code for future robotics projects.

Pause - I'll come back to this later

Since everything is local files and a lightweight extension, there is nothing to pause. Your project folder and extension use negligible disk space and no background resources.

  • Close the VS Code window when you are done.
  • Your files and extension will be right where you left them when you return.

Delete - I don't want to use this again

Remove all project files and the extension to clean up completely.

  • Delete the my_robot_arm folder. Open your terminal and navigate to the folder that contains my_robot_arm, then run:

macOS

rm -rf my_robot_arm

Windows

Remove-Item -Recurse -Force my_robot_arm
  • Uninstall the URDF extension from VS Code.
  • Press Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows) to open the Extensions sidebar.
  • Find Robot Developer Extensions for URDF in your installed extensions list.
  • Right-click the extension and select Uninstall.
  • Reload VS Code if prompted.

Nice Work!

Nice Work!

You just built a robot from nothing but XML. You now have a complete URDF file that describes a 3-DOF robot arm with a gripper, visualized in 3D directly inside VS Code.

You've learned how to:

  • Build a complete URDF file from scratch describing a multi-link robot arm with joints, visual geometry, collision geometry, and materials.
  • Define and connect links and joints using revolute and fixed joint types to form a kinematic chain.
  • Visualize and validate your robot model in 3D using the Robot Developer Extensions for URDF directly inside VS Code.
  • Added a prismatic sliding rail beneath the base, demonstrating all four common URDF joint types in a single model.

Ready to quiz yourself?