URDF — Unified Robot Description Format — is the XML schema ROS and ROS 2 use to describe what a robot physically is: its links, the joints connecting them, and the geometry, mass, and limits attached to each. It answers a narrower question than a simulator scene file does — not "what's in the world" but "what is this one robot's kinematic structure" — and that narrowness is both its strength and the reason a data pipeline needs formats around it.
Links, joints, and the three geometries per link
A URDF file is a <robot> element containing <link> and <joint> elements. Each link can carry up to three separate descriptions of the same rigid body:
<visual>— the geometry rendered for a human, often a detailed mesh.<collision>— the geometry used for physics and collision checking, usually a simplified proxy (a box or convex hull) for speed.<inertial>— mass and the inertia tensor, needed for any dynamics simulation.
Splitting visual from collision geometry is a deliberate performance decision: a photorealistic mesh is expensive to collision-check tens of thousands of times a second, so simulators check against the cheap proxy and render the expensive one.
Joints connect exactly two links — a parent and a child — and declare a type:
- revolute
- Hinge with a limited range of motion (has upper/lower limits)
- continuous
- Hinge with unlimited rotation, no limits
- prismatic
- Slides along the joint axis, limited range
- fixed
- Rigid, no motion — used to attach sensors, tools, or static links
- floating
- Unconstrained 6-DOF motion between parent and child
- planar
- Motion confined to the plane orthogonal to the joint axis
Because every link has exactly one parent joint tracing back to a single root link, the whole structure is a tree — and that constraint is where URDF's limitations start.
What URDF cannot express
- Closed or parallel kinematic chains. A four-bar linkage, a parallel gripper with coupled fingers, or any mechanism where a link has two independent paths back to the root needs a second parent, which a tree forbids. The spec is explicit that only tree structures are representable, ruling out parallel robots.
- Multiple robots in one file. URDF describes a single robot. Combining two robots into one scene means either merging them into one
<robot>tree (usually via xacro macros) or handling multiple URDF files at the application layer — there's no native multi-robot container. - Sensors, actuator dynamics, contacts. Beyond a
<link>'s geometry, URDF has no native concept of a camera, an IMU, a motor's torque-speed curve, or which links can contact which. ROS/Gazebo integrations add these back through<gazebo>extension tags and plugins — functional, but outside the core spec and not portable to other consumers.
The ecosystem that fills the gaps
- xacro — a macro preprocessor for URDF XML: variables, arithmetic, conditionals, and reusable macros that expand into plain URDF before any tool consumes it. Almost no one hand-writes a large URDF file directly; they write xacro.
- SDF (Simulation Description Format) — Gazebo's native format. Not restricted to trees, so it represents closed-loop and parallel mechanisms, and a single
.sdffile can hold multiple models, lights, and physics solver settings — a whole scene, not one robot. - MJCF — MuJoCo's XML format. Captures everything URDF does plus contacts, tendons, equality constraints (which is how it represents closed loops), actuator dynamics, and native sensor definitions. MuJoCo and Isaac Lab workflows typically convert a URDF into MJCF or load both side by side.
- USD (Universal Scene Description) — Pixar's format, adopted by NVIDIA Omniverse and Isaac Sim as a scene-and-asset interchange format broader than any robot-specific schema, with physics schemas (PhysX) layered on top.
Most real pipelines don't pick one winner — they keep a xacro/URDF source of truth for ROS tooling (robot_state_publisher, MoveIt, rviz2) and generate SDF, MJCF, or USD for whichever simulator a given experiment needs.
Why the URDF matters for a data pipeline specifically
A demonstration dataset records joint angles, not hand positions. Every downstream use of that data — computing an end-effector pose for a task-space policy, retargeting a trajectory captured on one arm onto a different one, or replaying a session in simulation to check it against the real robot — runs forward kinematics through the URDF's link offsets, joint axes, and joint limits. If the URDF used to compute those poses doesn't match the physical robot that generated the recording (wrong tool offset, stale joint limit, mirrored axis), the joint recordings in your MCAP or LeRobot files are correct and every pose derived from them is quietly wrong.
That's also the practical argument for archiving the exact URDF (or xacro plus its resolved parameters) alongside a dataset rather than trusting that "the standard arm URDF" will still mean the same file eighteen months later, when someone tries to reproduce the dataset's poses from a workspace that has since updated its robot description package.
<?xml version="1.0"?>
<robot name="simple_arm">
<link name="base_link"/>
<link name="upper_arm"/>
<joint name="shoulder_pitch" type="revolute">
<parent link="base_link"/>
<child link="upper_arm"/>
<origin xyz="0 0 0.15" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit lower="-1.57" upper="1.57" effort="50" velocity="2.0"/>
</joint>
</robot>