diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 950245d..e8437ac 100644
*** a/doc/src/sgml/trigger.sgml
--- b/doc/src/sgml/trigger.sgml
***************
*** 212,218 ****
      change the data returned by
      <command>INSERT RETURNING</> or <command>UPDATE RETURNING</>,
      and is useful when the view will not show exactly the same data
!     that was provided.
     </para>
  
     <para>
--- 212,220 ----
      change the data returned by
      <command>INSERT RETURNING</> or <command>UPDATE RETURNING</>,
      and is useful when the view will not show exactly the same data
!     that was provided.  Likewise, for <command>DELETE</> operations the
!     <varname>OLD</> variable can be modified before returning it, and
!     the changes will be reflected in the output data.
     </para>
  
     <para>
diff --git a/src/backend/commandindex b502941..645c216 100644
*** a/src/backend/commands/trigger.c
--- b/src/backend/commands/trigger.c
***************
*** 2654,2666 **** ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
  	}
  }
  
! bool
  ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
! 					 HeapTuple trigtuple)
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  	TriggerData LocTriggerData;
! 	HeapTuple	rettuple;
  	int			i;
  
  	LocTriggerData.type = T_TriggerData;
--- 2654,2666 ----
  	}
  }
  
! TupleTableSlot *
  ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
! 					 HeapTuple trigtuple, TupleTableSlot *slot)
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  	TriggerData LocTriggerData;
! 	HeapTuple	rettuple = trigtuple;
  	int			i;
  
  	LocTriggerData.type = T_TriggerData;
***************
*** 2694,2704 **** ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
  									   relinfo->ri_TrigInstrument,
  									   GetPerTupleMemoryContext(estate));
  		if (rettuple == NULL)
! 			return false;		/* Delete was suppressed */
! 		if (rettuple != trigtuple)
! 			heap_freetuple(rettuple);
  	}
! 	return true;
  }
  
  void
--- 2694,2720 ----
  									   relinfo->ri_TrigInstrument,
  									   GetPerTupleMemoryContext(estate));
  		if (rettuple == NULL)
! 			return NULL;		/* Delete was suppressed */
  	}
! 
! 	if (rettuple != trigtuple)
! 	{
! 		/*
! 		 * Return the modified tuple using the es_trig_tuple_slot.  We assume
! 		 * the tuple was allocated in per-tuple memory context, and therefore
! 		 * will go away by itself. The tuple table slot should not try to
! 		 * clear it.
! 		 */
! 		TupleTableSlot *newslot = estate->es_trig_tuple_slot;
! 		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
! 
! 		if (newslot->tts_tupleDescriptor != tupdesc)
! 			ExecSetSlotDescriptor(newslot, tupdesc);
! 		ExecStoreTuple(rettuple, newslot, InvalidBuffer, false);
! 		slot = newslot;
! 	}
! 
! 	return slot;
  }
  
  void
diff --git a/src/backend/executor/nodindex 30add8e..d81c5a4 100644
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 704,716 **** ExecDelete(ModifyTableState *mtstate,
  	if (resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
  	{
! 		bool		dodelete;
  
! 		Assert(oldtuple != NULL);
! 		dodelete = ExecIRDeleteTriggers(estate, resultRelInfo, oldtuple);
  
! 		if (!dodelete)			/* "do nothing" */
  			return NULL;
  	}
  	else if (resultRelInfo->ri_FdwRoutine)
  	{
--- 704,726 ----
  	if (resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
  	{
! 		/*
! 		 * Store the heap tuple into the tuple table slot, making sure we have a
! 		 * writable copy.  We can use the trigger tuple slot.
! 		 */
! 		slot = estate->es_trig_tuple_slot;
! 		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
! 			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
! 		ExecStoreTuple(oldtuple, slot, InvalidBuffer, false);
! 		oldtuple = ExecMaterializeSlot(slot);
  
! 		slot = ExecIRDeleteTriggers(estate, resultRelInfo, oldtuple, slot);
  
! 		if (slot == NULL)			/* "do nothing" */
  			return NULL;
+ 
+ 		/* trigger might have changed tuple */
+ 		oldtuple = ExecMaterializeSlot(slot);
  	}
  	else if (resultRelInfo->ri_FdwRoutine)
  	{
***************
*** 851,864 **** ldelete:;
  	/* Process RETURNING if present */
  	if (resultRelInfo->ri_projectReturning)
  	{
- 		/*
- 		 * We have to put the target tuple into a slot, which means first we
- 		 * gotta fetch it.  We can use the trigger tuple slot.
- 		 */
  		TupleTableSlot *rslot;
  		HeapTupleData deltuple;
  		Buffer		delbuffer;
  
  		if (resultRelInfo->ri_FdwRoutine)
  		{
  			/* FDW must have provided a slot containing the deleted row */
--- 861,882 ----
  	/* Process RETURNING if present */
  	if (resultRelInfo->ri_projectReturning)
  	{
  		TupleTableSlot *rslot;
  		HeapTupleData deltuple;
  		Buffer		delbuffer;
  
+ 		/*
+ 		 * If we fired an INSTEAD OF trigger, we should use the tuple returned
+ 		 * from said trigger for the RETURNING projections.
+ 		 */
+ 		if (resultRelInfo->ri_TrigDesc &&
+ 			resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
+ 			return ExecProcessReturning(resultRelInfo, slot, planSlot);
+ 
+ 		/*
+ 		 * Otherwise we have to to fetch the target tuple into a slot.  We can
+ 		 * use the trigger tuple slot here as well.
+ 		 */
  		if (resultRelInfo->ri_FdwRoutine)
  		{
  			/* FDW must have provided a slot containing the deleted row */
diff --git a/src/include/commands/trigger.h bindex 36c1134..8582177 100644
*** a/src/include/commands/trigger.h
--- b/src/include/commands/trigger.h
***************
*** 209,217 **** extern void ExecARDeleteTriggers(EState *estate,
  					 ItemPointer tupleid,
  					 HeapTuple fdw_trigtuple,
  					 TransitionCaptureState *transition_capture);
! extern bool ExecIRDeleteTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
! 					 HeapTuple trigtuple);
  extern void ExecBSUpdateTriggers(EState *estate,
  					 ResultRelInfo *relinfo);
  extern void ExecASUpdateTriggers(EState *estate,
--- 209,218 ----
  					 ItemPointer tupleid,
  					 HeapTuple fdw_trigtuple,
  					 TransitionCaptureState *transition_capture);
! extern TupleTableSlot *ExecIRDeleteTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
! 					 HeapTuple trigtuple,
! 					 TupleTableSlot *slot);
  extern void ExecBSUpdateTriggers(EState *estate,
  					 ResultRelInfo *relinfo);
  extern void ExecASUpdateTriggers(EState *estate,
diff --git a/src/test/regress/expecteindex ac132b0..b445e26 100644
*** a/src/test/regress/expected/triggers.out
--- b/src/test/regress/expected/triggers.out
***************
*** 1309,1314 **** UPDATE 0
--- 1309,1341 ----
  DELETE FROM european_city_view;
  DELETE 0
  \set QUIET true
+ -- modifying RETURNING from INSTEAD OF triggers on DELETEs
+ CREATE VIEW instead_of_delete_returning AS SELECT 1 AS fff;
+ CREATE FUNCTION instead_of_delete_returning_f() RETURNS trigger LANGUAGE plpgsql AS $$
+ BEGIN
+     RETURN NULL;
+ END;
+ $$;
+ CREATE TRIGGER instead_of_delete_returning_t INSTEAD OF DELETE ON instead_of_delete_returning
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_delete_returning_f();
+ DELETE FROM instead_of_delete_returning RETURNING *;
+  fff 
+ -----
+ (0 rows)
+ 
+ CREATE OR REPLACE FUNCTION instead_of_delete_returning_f() RETURNS trigger LANGUAGE plpgsql AS $$
+ BEGIN
+     OLD.fff := 3;
+     RETURN OLD;
+ END;
+ $$;
+ DELETE FROM instead_of_delete_returning RETURNING *;
+  fff 
+ -----
+    3
+ (1 row)
+ 
+ DROP VIEW instead_of_delete_returning CASCADE;
  -- rules bypassing no-op triggers
  CREATE RULE european_city_insert_rule AS ON INSERT TO european_city_view
  DO INSTEAD INSERT INTO city_view
diff --git a/src/test/regress/sql/triggers.sqindex b10159a..636e387 100644
*** a/src/test/regress/sql/triggers.sql
--- b/src/test/regress/sql/triggers.sql
***************
*** 916,921 **** DELETE FROM european_city_view;
--- 916,941 ----
  
  \set QUIET true
  
+ -- modifying RETURNING from INSTEAD OF triggers on DELETEs
+ CREATE VIEW instead_of_delete_returning AS SELECT 1 AS fff;
+ CREATE FUNCTION instead_of_delete_returning_f() RETURNS trigger LANGUAGE plpgsql AS $$
+ BEGIN
+     RETURN NULL;
+ END;
+ $$;
+ CREATE TRIGGER instead_of_delete_returning_t INSTEAD OF DELETE ON instead_of_delete_returning
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_delete_returning_f();
+ 
+ DELETE FROM instead_of_delete_returning RETURNING *;
+ CREATE OR REPLACE FUNCTION instead_of_delete_returning_f() RETURNS trigger LANGUAGE plpgsql AS $$
+ BEGIN
+     OLD.fff := 3;
+     RETURN OLD;
+ END;
+ $$;
+ DELETE FROM instead_of_delete_returning RETURNING *;
+ DROP VIEW instead_of_delete_returning CASCADE;
+ 
  -- rules bypassing no-op triggers
  CREATE RULE european_city_insert_rule AS ON INSERT TO european_city_view
  DO INSTEAD INSERT INTO city_view
