What is a QGC .plan file?
A QGroundControl .plan file is a JSON document holding a mission, a geofence and rally points. Its top-level keys are fileType ("Plan"), version (1), groundStation, mission, geoFence and rallyPoints. The mission holds a list of items: a SimpleItem is one MAVLink mission item; ComplexItems such as Survey, CorridorScan and StructureScan are QGroundControl’s own generators, expanded into simple items on upload.
QGroundControl reads and writes .plan files natively. ArduPilot’s Mission Planner uses the plain-text .waypoints format instead, which OpenDroneKit also writes.
Every key
| Key | Meaning (QGC docs / MAVLink) | What OpenDroneKit writes |
|---|---|---|
fileType, version, groundStation | "Plan", 1, the writer's name | "Plan", 1, "OpenDroneKit" |
mission.firmwareType | A MAV_AUTOPILOT value: 3 = ArduPilot, 12 = PX4 | 12 (PX4), though the code intends ArduPilot; see known issues |
mission.vehicleType | A MAV_TYPE value: 2 = quadrotor | 2 |
mission.cruiseSpeed / hoverSpeed | QGC's speed for time estimates (fixed wing / multirotor) | Planned speed in both |
mission.globalPlanAltitudeMode | Altitude reference for the plan | 1 relative to home; 2 AMSL when the plan has a terrain model |
mission.plannedHomePosition | [lat, lon, AMSL alt] | First item's position |
mission.items[] | SimpleItem: command (MAV_CMD), frame (MAV_FRAME), params[7], Altitude, AltitudeMode, autoContinue, doJumpId | One SimpleItem per MAVLink item, same order as the .waypoints file without its home row |
geoFence.polygons[] | {inclusion, polygon: [[lat, lon]…], version} | Inclusion fence 5 m outside the drawn area; one exclusion polygon per no-fly zone |
geoFence.circles | Circular fences | [] |
rallyPoints.points | [[lat, lon, alt]…] | The plan's rally / emergency landing points |
Example file
Written by OpenDroneKit’s exporter for a 90 x 60 m rectangle centred on 41.30420 N, 81.75200 W (Aukerman Park, Ohio): grid template, 55 m above take-off, 8 m/s, 27 waypoints. The first lines:
{
"fileType": "Plan",
"geoFence": {
"circles": [],
"polygons": [
{
"inclusion": true,
"polygon": [
[
41.30388497139586,
-81.7525985094454
],
[
41.30388497139586,
-81.75140149055458
],
[
41.30451502860414,
-81.75140149055458
],
[
41.30451502860414,
-81.7525985094454
]
],
"version": 1
}
],
"version": 2
},
"groundStation": "OpenDroneKit",
"mission": {
"cruiseSpeed": 8.0,
"firmwareType": 12,
"globalPlanAltitudeMode": 1,
"hoverSpeed": 8.0,
"items": [
{
"AMSLAltAboveTerrain": null,
"Altitude": 55.0,The full Aukerman Park files in the table above come from the run shown on the homepage: the public OpenDroneMap survey, 152 waypoints at 55 m, generated 2026-09-29. Every file’s SHA-256 is listed so a copy can be checked.
What OpenDroneKit writes
OpenDroneKit builds the full MAVLink item stream first (build_mission_items): take-off, a DO_CHANGE_SPEED with the planned speed, then for each viewpoint a DO_MOUNT_CONTROL for gimbal pitch, an optional CONDITION_YAW, the NAV_WAYPOINT (or NAV_LOITER_TIME for a hold) and a DO_DIGICAM_CONTROL to take the photo, ending with return-to-launch. The .plan carries exactly that stream as SimpleItems, so capture behaviour survives, not just the path.
The inclusion fence is offset 5 m outward because ArduPilot breaches a polygon fence within its FENCE_MARGIN (2 m by default) and a mapping grid flies its outer lines on the area’s edge.
Known issues
Checked against the format’s reference and the exporter code on 2026-10-02. Listed here rather than hidden, so you can correct a file before you fly it.
- firmwareType is 12, which is PX4. The exporter’s default and its docstring say “ArduPilot multirotor (firmware 12, vehicle 2)”, but in MAVLink’s MAV_AUTOPILOT enum ArduPilot is 3 and 12 is PX4. QGroundControl may therefore open the file as a PX4 mission. Present on both branches; change the value to 3 for ArduPilot until it is fixed.
Public main versus the development branch
The example files were written by the development branch (0ae10e4), which is ahead of the public main you get from a source install (1ef6351). On main:
- Public main concatenates every no-fly zone into one exclusion polygon (two rectangles become one self-intersecting shape); the integration branch writes one polygon per zone.
- Public main writes the drawn area itself as the inclusion fence; the integration branch offsets it 5 m outward so a grid's outer lines do not breach ArduPilot's 2 m FENCE_MARGIN.
- Public main reads cruiseSpeed/hoverSpeed from a key the planner does not set, so they fall back to 12 and 5 m/s; it has no DO_CHANGE_SPEED item and no AMSL mode (AltitudeMode always 1).
How do I import it into QGroundControl?
- In QGroundControl, open Plan view.
- Use the Plan view’s file menu to open the .plan file.
- Check the vehicle firmware shown for the plan, the home position and the fence, then upload.
Pitfalls
- A firmwareType that does not match the connected vehicle makes QGroundControl warn or convert commands on upload.
- Fence polygons are [lat, lon] pairs while GeoJSON is [lon, lat]; swapping them puts the fence on the other side of the world.
- QGroundControl recalculates ComplexItems (Survey, StructureScan) on load; a file with only SimpleItems is flown exactly as written.
References
- QGroundControl: Plan file format
- MAVLink common messages (MAV_AUTOPILOT, MAV_TYPE, MAV_FRAME, MAV_CMD)
- OpenDroneKit exporter source (mission/exporters.py at 1ef6351)
Other formats: .waypoints · DJI WPML · Litchi CSV. Missions come from the mission templates; see mission planning.