If you have trained a reinforcement learning agent to walk, looked at a robotics paper, or used Gymnasium's HalfCheetah or Humanoid environments, you have already used MuJoCo, possibly without knowing it.
MuJoCo (short for Multi-Joint dynamics with Contact) is an open-source physics engine maintained by Google DeepMind. It is built for fast and accurate simulation of articulated bodies (robots, animals, humans, mechanisms) interacting with their environment through contact.
This guide covers what a developer actually needs to know to get productive with it: how to install it, how its data model works, how to run and visualize a simulation, and where the sharp edges are.
Why MuJoCo matters
A few things make MuJoCo stand out from game physics engines and other robotics simulators:
- Accuracy over spectacle. It is designed for model-based control, optimization and research, where you need physics you can trust, not just physics that looks right.
- Speed. The core is written in C, uses preallocated memory, and avoids allocations during simulation. Stepping a model millions of times is normal.
- Contact handling. Contacts are solved with a convex optimization formulation, which makes it stable for grasping, walking and other contact-rich tasks.
- Free and open source. It is licensed under Apache 2.0, so you can use it in commercial and research projects.
- A large ecosystem. Gymnasium, dm_control, MuJoCo Playground, MuJoCo Menagerie and many robotics frameworks are built on top of it.
Installation
For most developers, Python is the starting point. The PyPI package ships with a copy of the MuJoCo library, so there is nothing else to download.
pip install mujoco Check that it works:
python -c "import mujoco; print(mujoco.__version__)" A few practical notes:
- The current README lists Python 3.11 or newer for the bindings.
- Prebuilt binaries exist for Linux (x86-64 and AArch64), Windows (x86-64) and macOS (universal).
- Linux wheels target
manylinux2014. - If you want the C library directly, download a release from the GitHub releases page. Building from
mainis possible, but the tip ofmaincan be unstable.
The two core objects: MjModel and MjData
This is the single most important concept to understand. MuJoCo splits a simulation into two structures:
| Object | What it holds | Changes during simulation? |
|---|---|---|
MjModel | The description of the world: bodies, joints, masses, geometry, actuators, timestep, solver options | No (treat it as constant) |
MjData | The current state and everything computed from it: positions, velocities, controls, forces, contacts, time | Yes |
You build one MjModel, then create one or more MjData objects from it. Because the state lives entirely in MjData, you can run many independent simulations of the same model in parallel just by creating more MjData instances.
The most-used fields in MjData:
-
data.qpos– generalized positions (joint angles, free-body positions and orientations) -
data.qvel– generalized velocities -
data.ctrl– control inputs for actuators (this is what your policy or controller writes to) -
data.time– simulation time -
data.xpos,data.xquat– global positions and orientations of bodies -
data.contact,data.ncon– active contacts
Describing a world with MJCF
Models are usually written in MJCF, MuJoCo's XML format. URDF files (common in ROS) can also be loaded, but MJCF exposes the full feature set.
Here is a minimal scene: a floor, a light, and a box that falls onto the floor.
<mujoco model="falling_box"> <option timestep="0.002" gravity="0 0 -9.81"/> <worldbody> <light pos="0 0 3" dir="0 0 -1"/> <geom name="floor" type="plane" size="2 2 0.1" rgba="0.8 0.8 0.8 1"/> <body name="box" pos="0 0 1"> <freejoint/> <geom type="box" size="0.1 0.1 0.1" rgba="0.2 0.4 0.8 1" mass="1"/> </body> </worldbody> </mujoco> Key MJCF building blocks:
-
worldbodyis the root of the kinematic tree. -
bodyelements nest inside each other to form a tree (a robot arm is a chain of nested bodies). -
jointdefines how a body moves relative to its parent (hinge,slide,ball, orfreejointfor a body that moves freely in 3D). -
geomdefines shape for collision and rendering, and contributes mass and inertia. -
actuatordefines motors, position servos and other force generators that you drive throughdata.ctrl. -
sensordefines measurements (joint positions, touch, IMU readings and so on) written todata.sensordata.
Your first simulation in Python
import mujoco XML = """ <mujoco> <worldbody> <light pos="0 0 3" dir="0 0 -1"/> <geom type="plane" size="2 2 0.1"/> <body name="box" pos="0 0 1"> <freejoint/> <geom type="box" size="0.1 0.1 0.1" mass="1"/> </body> </worldbody> </mujoco> """ model = mujoco.MjModel.from_xml_string(XML) data = mujoco.MjData(model) # Simulate 2 seconds of physics while data.time < 2.0: mujoco.mj_step(model, data) print("Time:", data.time) print("Box height:", data.body("box").xpos[2]) What is happening:
-
from_xml_stringcompiles the XML into anMjModel. Usefrom_xml_pathfor files. -
MjData(model)allocates state for that model. -
mj_stepadvances the simulation by one timestep (model.opt.timestep). -
data.body("box")is named access, which saves you from tracking integer indices.
Two other functions you will use constantly:
-
mujoco.mj_resetData(model, data)resets the state to its initial values. -
mujoco.mj_forward(model, data)computes all derived quantities (positions, contacts, sensor values) for the current state without advancing time. Call it after you manually setqposorqvel.
Controlling a robot
Actuators turn values in data.ctrl into forces. Here is a single pendulum with a motor at the hinge:
import mujoco import numpy as np XML = """ <mujoco> <option timestep="0.002"/> <worldbody> <body name="pole" pos="0 0 1"> <joint name="hinge" type="hinge" axis="0 1 0" damping="0.1"/> <geom type="capsule" fromto="0 0 0 0 0 -0.5" size="0.03" mass="1"/> </body> </worldbody> <actuator> <motor name="torque" joint="hinge" gear="1" ctrlrange="-5 5"/> </actuator> </mujoco> """ model = mujoco.MjModel.from_xml_string(XML) data = mujoco.MjData(model) target = np.pi / 4 # 45 degrees kp, kd = 20.0, 2.0 for _ in range(2000): angle = data.joint("hinge").qpos[0] velocity = data.joint("hinge").qvel[0] data.ctrl[0] = kp * (target - angle) - kd * velocity # simple PD controller mujoco.mj_step(model, data) print(f"Final angle: {np.degrees(data.joint('hinge').qpos[0]):.1f} degrees") The pattern is always the same: read state from data, compute a control, write it to data.ctrl, call mj_step. A reinforcement learning environment is this loop with a reward function around it.
Visualizing
Interactive viewer
The quickest way to inspect any model file is the built-in viewer:
python -m mujoco.viewer --mjcf=path/to/model.xml To watch your own simulation loop, use the passive viewer. It runs your code and renders alongside it:
import time import mujoco import mujoco.viewer model = mujoco.MjModel.from_xml_path("model.xml") data = mujoco.MjData(model) with mujoco.viewer.launch_passive(model, data) as viewer: while viewer.is_running(): step_start = time.time() mujoco.mj_step(model, data) viewer.sync() # Keep roughly real-time time.sleep(max(0, model.opt.timestep - (time.time() - step_start))) On macOS, launch_passive must be run with mjpython (installed with the package) instead of python, because macOS requires GUI work on the main thread.
Offscreen rendering
For datasets, videos or vision-based policies, render to NumPy arrays:
renderer = mujoco.Renderer(model, height=480, width=640) mujoco.mj_forward(model, data) renderer.update_scene(data) pixels = renderer.render() # shape (480, 640, 3), dtype uint8 On a headless Linux server, set the rendering backend before importing MuJoCo, for example MUJOCO_GL=egl (GPU) or MUJOCO_GL=osmesa (software).
Editing models in code with MjSpec
Writing XML by hand gets tedious when you need to generate variations (different terrain, randomized objects, procedurally built robots). MjSpec lets you build and modify models programmatically and then compile them:
import mujoco spec = mujoco.MjSpec() spec.worldbody.add_geom( type=mujoco.mjtGeom.mjGEOM_PLANE, size=[2, 2, 0.1] ) for i in range(3): body = spec.worldbody.add_body(name=f"ball_{i}", pos=[i * 0.3, 0, 1 + i * 0.5]) body.add_freejoint() body.add_geom(type=mujoco.mjtGeom.mjGEOM_SPHERE, size=[0.1, 0, 0]) model = spec.compile() data = mujoco.MjData(model) You can also load an existing XML with mujoco.MjSpec.from_file(...), change it, recompile, and even export it back to XML with spec.to_xml(). The official Model Editing notebook walks through this in depth.
Scaling up: MJX and MuJoCo Warp
Modern robot learning often needs thousands of simulations in parallel. MuJoCo offers two GPU-oriented options alongside the standard C engine:
- MJX (MuJoCo XLA) reimplements MuJoCo in JAX. You get
jitcompilation,vmapfor batching thousands of environments, and automatic differentiation through the physics step. It runs on GPU and TPU. - MuJoCo Warp is a GPU implementation built on NVIDIA Warp, developed with NVIDIA and officially released in MuJoCo 3.5.0. It targets high throughput on NVIDIA GPUs, especially for contact-heavy scenes.
A minimal MJX example:
import jax import mujoco from mujoco import mjx model = mujoco.MjModel.from_xml_path("model.xml") mjx_model = mjx.put_model(model) @jax.vmap def batched_step(qpos): d = mjx.make_data(mjx_model).replace(qpos=qpos) return mjx.step(mjx_model, d) step = jax.jit(batched_step) qpos_batch = jax.numpy.zeros((4096, model.nq)) result = step(qpos_batch) # 4096 simulations in one call A rule of thumb: use the standard C engine for single simulations, debugging and CPU work; use MJX if you live in the JAX ecosystem or need gradients; use MuJoCo Warp for maximum GPU throughput. Not every feature is supported in every backend, so check the MJX and Warp docs for your model's features.
The ecosystem around MuJoCo
You rarely need to start from scratch:
- MuJoCo Menagerie – a curated collection of high-quality robot models (arms, quadrupeds, humanoids, hands) ready to load.
- MuJoCo Playground – GPU-accelerated environments and training pipelines for locomotion and manipulation, including sim-to-real work.
- Gymnasium – the standard RL benchmark environments (
Ant,Hopper,Humanoidand others) use MuJoCo. - dm_control – DeepMind's control suite, including PyMJCF for composing models in Python.
- Bindings in other languages – first-party C#/Unity and JavaScript/WebAssembly bindings, plus community bindings for Rust, Julia, Swift, Java and MATLAB Simulink.
- Converters – tools exist for OpenSim, SDFormat, OBJ meshes and Onshape CAD assemblies to MJCF.
Things developers should know before going deep
1. Pin your version. MuJoCo releases roughly every month and uses a modified form of Semantic Versioning (introduced in 3.5.0). Physics fixes between releases can change simulation results. If you publish benchmarks or train long-running policies, pin the exact version in requirements.txt and record it with your results.
2. Do not use mujoco-py. Many older tutorials use mujoco-py, the legacy OpenAI wrapper. It is unmaintained. Use the official mujoco package instead.
3. Timestep matters. The default timestep is 0.002 seconds. Making it larger speeds things up but can make contact-heavy scenes unstable. If objects jitter, explode or pass through each other, reduce the timestep before changing anything else.
4. Generalized coordinates are not always obvious. qpos and qvel have different sizes when a model contains free or ball joints, because orientation is stored as a quaternion (4 values in qpos) but angular velocity has 3 values in qvel. Use model.nq and model.nv, and use named access (data.joint("name")) instead of hardcoding indices.
5. Call mj_forward after editing state. If you set qpos manually and then read body positions or sensor values without stepping, they will be stale until you call mj_forward.
6. Reuse MjData for performance. Allocating new MjData objects in a hot loop is wasteful. Reset with mj_resetData instead. For batches of CPU rollouts, look at the multithreaded rollout module.
A good learning path is: run the introductory notebook, load a robot from Menagerie in the viewer, write a small control loop for it, and then wrap that loop in a Gymnasium environment. From there, MJX or MuJoCo Warp are the natural next step if you need to train at scale.