diff --git i/src/backend/executor/execPartition.c w/src/backend/executor/execPartition.c
index 79fcbd6b066..152a80cc6c9 100644
--- i/src/backend/executor/execPartition.c
+++ w/src/backend/executor/execPartition.c
@@ -903,8 +903,14 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
 	 * a slot dedicated to storing this partition's tuples.  The slot is used
 	 * for various operations that are applied to tuples after routing, such
 	 * as checking constraints.
+	 *
+	 * Even if there's no difference between the normal columns, RETURNING
+	 * might access system columns. So whenever RETURNING is specified we
+	 * force creation of the slot as well - which will cause use of the right
+	 * slot type for RETURNING processing. XXX: But what about triggers etc?
 	 */
-	if (partrouteinfo->pi_RootToPartitionMap != NULL)
+	if (partrouteinfo->pi_RootToPartitionMap != NULL ||
+		partRelInfo->ri_returningList)
 	{
 		Relation	partrel = partRelInfo->ri_RelationDesc;
 
diff --git i/src/backend/executor/nodeModifyTable.c w/src/backend/executor/nodeModifyTable.c
index 20a4c474cc4..eb318ac3f73 100644
--- i/src/backend/executor/nodeModifyTable.c
+++ w/src/backend/executor/nodeModifyTable.c
@@ -1949,6 +1949,12 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 
 		slot = execute_attr_map_slot(map->attrMap, slot, new_slot);
 	}
+	else if (partrouteinfo->pi_PartitionTupleSlot)
+	{
+		TupleTableSlot *new_slot = partrouteinfo->pi_PartitionTupleSlot;
+
+		slot = ExecCopySlot(new_slot, slot);
+	}
 
 	return slot;
 }
