diff --git a/contrib/file_fdw/data/list1.csv b/contrib/file_fdw/data/list1.csv
new file mode 100644
index 0000000000000000000000000000000000000000..203f3b2324e2940c75c6d7f9760c2bbf6783e124
--- /dev/null
+++ b/contrib/file_fdw/data/list1.csv
@@ -0,0 +1,2 @@
+1,foo
+1,bar
diff --git a/contrib/file_fdw/data/list2.bad b/contrib/file_fdw/data/list2.bad
new file mode 100644
index 0000000000000000000000000000000000000000..00af47f5ef69bf55fca50a9eba2f629b9f3eced8
--- /dev/null
+++ b/contrib/file_fdw/data/list2.bad
@@ -0,0 +1,2 @@
+2,baz
+1,qux
diff --git a/contrib/file_fdw/data/list2.csv b/contrib/file_fdw/data/list2.csv
new file mode 100644
index 0000000000000000000000000000000000000000..2fb133d0046dc83699858aac27a4adf33e145803
--- /dev/null
+++ b/contrib/file_fdw/data/list2.csv
@@ -0,0 +1,2 @@
+2,baz
+2,qux
diff --git a/contrib/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source
index 685561fc2a0fd64282ed1276911ae5e150488421..e6821d64d4416b177faa35914b63236ab5b122e3 100644
--- a/contrib/file_fdw/input/file_fdw.source
+++ b/contrib/file_fdw/input/file_fdw.source
@@ -162,6 +162,27 @@ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
 ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
 DROP TABLE agg;
 
+-- declarative partitioning tests
+SET ROLE regress_file_fdw_superuser;
+CREATE TABLE pt (a int, b text) partition by list (a);
+CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server
+OPTIONS (format 'csv', filename '@abs_srcdir@/data/list1.csv', delimiter ',');
+CREATE TABLE p2 partition of pt for values in (2);
+SELECT tableoid::regclass, * FROM pt;
+SELECT tableoid::regclass, * FROM p1;
+SELECT tableoid::regclass, * FROM p2;
+COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
+COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
+SELECT tableoid::regclass, * FROM pt;
+SELECT tableoid::regclass, * FROM p1;
+SELECT tableoid::regclass, * FROM p2;
+INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
+INSERT INTO pt VALUES (2, 'xyzzy');
+SELECT tableoid::regclass, * FROM pt;
+SELECT tableoid::regclass, * FROM p1;
+SELECT tableoid::regclass, * FROM p2;
+DROP TABLE pt;
+
 -- privilege tests
 SET ROLE regress_file_fdw_superuser;
 SELECT * FROM agg_text ORDER BY a;
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index 01e2690a8254435eaf866232cc7eb486749429d7..709c43ec804426eb0e7176f02f1726851acbef2c 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -289,6 +289,87 @@ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
 
 ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
 DROP TABLE agg;
+-- declarative partitioning tests
+SET ROLE regress_file_fdw_superuser;
+CREATE TABLE pt (a int, b text) partition by list (a);
+CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server
+OPTIONS (format 'csv', filename '@abs_srcdir@/data/list1.csv', delimiter ',');
+CREATE TABLE p2 partition of pt for values in (2);
+SELECT tableoid::regclass, * FROM pt;
+ tableoid | a |  b  
+----------+---+-----
+ p1       | 1 | foo
+ p1       | 1 | bar
+(2 rows)
+
+SELECT tableoid::regclass, * FROM p1;
+ tableoid | a |  b  
+----------+---+-----
+ p1       | 1 | foo
+ p1       | 1 | bar
+(2 rows)
+
+SELECT tableoid::regclass, * FROM p2;
+ tableoid | a | b 
+----------+---+---
+(0 rows)
+
+COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter ','); -- ERROR
+ERROR:  cannot route inserted tuples to a foreign table
+CONTEXT:  COPY pt, line 2: "1,qux"
+COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
+SELECT tableoid::regclass, * FROM pt;
+ tableoid | a |  b  
+----------+---+-----
+ p1       | 1 | foo
+ p1       | 1 | bar
+ p2       | 2 | baz
+ p2       | 2 | qux
+(4 rows)
+
+SELECT tableoid::regclass, * FROM p1;
+ tableoid | a |  b  
+----------+---+-----
+ p1       | 1 | foo
+ p1       | 1 | bar
+(2 rows)
+
+SELECT tableoid::regclass, * FROM p2;
+ tableoid | a |  b  
+----------+---+-----
+ p2       | 2 | baz
+ p2       | 2 | qux
+(2 rows)
+
+INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
+ERROR:  cannot route inserted tuples to a foreign table
+INSERT INTO pt VALUES (2, 'xyzzy');
+SELECT tableoid::regclass, * FROM pt;
+ tableoid | a |   b   
+----------+---+-------
+ p1       | 1 | foo
+ p1       | 1 | bar
+ p2       | 2 | baz
+ p2       | 2 | qux
+ p2       | 2 | xyzzy
+(5 rows)
+
+SELECT tableoid::regclass, * FROM p1;
+ tableoid | a |  b  
+----------+---+-----
+ p1       | 1 | foo
+ p1       | 1 | bar
+(2 rows)
+
+SELECT tableoid::regclass, * FROM p2;
+ tableoid | a |   b   
+----------+---+-------
+ p2       | 2 | baz
+ p2       | 2 | qux
+ p2       | 2 | xyzzy
+(3 rows)
+
+DROP TABLE pt;
 -- privilege tests
 SET ROLE regress_file_fdw_superuser;
 SELECT * FROM agg_text ORDER BY a;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4582a3caa00de7f6c67085cb8c0ac7ede8dcc736..9dcc358ec27c11d17ef46392c1bacb3447e2d7cd 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1097,8 +1097,9 @@ InitPlan(QueryDesc *queryDesc, int eflags)
  * CheckValidRowMarkRel.
  */
 void
-CheckValidResultRel(Relation resultRel, CmdType operation)
+CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
 {
+	Relation	resultRel = resultRelInfo->ri_RelationDesc;
 	TriggerDesc *trigDesc = resultRel->trigdesc;
 	FdwRoutine *fdwroutine;
 
@@ -1169,10 +1170,16 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			break;
 		case RELKIND_FOREIGN_TABLE:
 			/* Okay only if the FDW supports it */
-			fdwroutine = GetFdwRoutineForRelation(resultRel, false);
+			fdwroutine = resultRelInfo->ri_FdwRoutine;
 			switch (operation)
 			{
 				case CMD_INSERT:
+					/*
+					 * If foreign partition to do tuple-routing for, skip the
+					 * check; it's disallowed elsewhere.
+					 */
+					if (resultRelInfo->ri_PartitionRoot)
+						break;
 					if (fdwroutine->ExecForeignInsert == NULL)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -3305,11 +3312,6 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		partrel = heap_open(lfirst_oid(cell), NoLock);
 		part_tupdesc = RelationGetDescr(partrel);
 
-		/*
-		 * Verify result relation is a valid target for the current operation.
-		 */
-		CheckValidResultRel(partrel, CMD_INSERT);
-
 		/*
 		 * Save a tuple conversion map to convert a tuple routed to this
 		 * partition from the parent's type to the partition's.
@@ -3323,8 +3325,10 @@ ExecSetupPartitionTupleRouting(Relation rel,
 						  rel,
 						  estate->es_instrument);
 
-		estate->es_leaf_result_relations =
-			lappend(estate->es_leaf_result_relations, leaf_part_rri);
+		/*
+		 * Verify result relation is a valid target for INSERT.
+		 */
+		CheckValidResultRel(leaf_part_rri, CMD_INSERT);
 
 		/*
 		 * Open partition indices (remember we do not support ON CONFLICT in
@@ -3335,6 +3339,9 @@ ExecSetupPartitionTupleRouting(Relation rel,
 			leaf_part_rri->ri_IndexRelationDescs == NULL)
 			ExecOpenIndices(leaf_part_rri, false);
 
+		estate->es_leaf_result_relations =
+			lappend(estate->es_leaf_result_relations, leaf_part_rri);
+
 		leaf_part_rri++;
 		i++;
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 70a6b847a0ead693e8fc4c0c60b296b78d5dc730..83267580a35cfdf273e2385c6ce31739929c82c5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1853,7 +1853,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		/*
 		 * Verify result relation is a valid target for the current operation
 		 */
-		CheckValidResultRel(resultRelInfo->ri_RelationDesc, operation);
+		CheckValidResultRel(resultRelInfo, operation);
 
 		/*
 		 * If there are indices on the result relation, open them and save
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index eacbea3c3655aec2314954648515f17235629777..379e7c77a181704c1787b8242ffcdfe62fd761dd 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -177,7 +177,7 @@ extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
 extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
-extern void CheckValidResultRel(Relation resultRel, CmdType operation);
+extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,