diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 10f80ef3654..6dd5c5bcf28 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -40,6 +40,9 @@
 #include "utils/rel.h"
 #include "utils/rls.h"
 
+static Oid LookupCustomFormat(char *fmt);
+static Node *GetCustomFormatRoutine(Oid handler, bool is_from);
+
 /*
  *	 DoCopy executes the SQL COPY statement
  *
@@ -477,95 +480,6 @@ defGetCopyLogVerbosityChoice(DefElem *def, ParseState *pstate)
 	return COPY_LOG_VERBOSITY_DEFAULT;	/* keep compiler quiet */
 }
 
-/*
- * Process the "format" option.
- *
- * This function checks whether the option value is a built-in format such as
- * "text" and "csv" or not. If the option value isn't a built-in format, this
- * function finds a COPY format handler that returns a CopyToRoutine (for
- * is_from == false) or CopyFromRountine (for is_from == true). If no COPY
- * format handler is found, this function reports an error.
- */
-static void
-ProcessCopyOptionFormat(ParseState *pstate,
-						CopyFormatOptions *opts_out,
-						bool is_from,
-						DefElem *defel)
-{
-	char	   *format;
-	bool		isBuiltin;
-	Oid			funcargtypes[1];
-	Oid			handlerOid = InvalidOid;
-	Datum		datum;
-	Node	   *routine;
-
-	format = defGetString(defel);
-
-	isBuiltin = true;
-	opts_out->csv_mode = false;
-	opts_out->binary = false;
-	/* built-in formats */
-	if (strcmp(format, "text") == 0)
-		 /* "csv_mode == false && binary == false" means "text" */ ;
-	else if (strcmp(format, "csv") == 0)
-		opts_out->csv_mode = true;
-	else if (strcmp(format, "binary") == 0)
-		opts_out->binary = true;
-	else
-		isBuiltin = false;
-	if (isBuiltin)
-	{
-		if (is_from)
-			opts_out->routine = (Node *) CopyFromGetBuiltinRoutine(opts_out);
-		else
-			opts_out->routine = (Node *) CopyToGetBuiltinRoutine(opts_out);
-		return;
-	}
-
-	/* custom format */
-	funcargtypes[0] = INTERNALOID;
-	handlerOid = LookupFuncName(list_make1(makeString(format)), 1,
-								funcargtypes, true);
-	if (!OidIsValid(handlerOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("COPY format \"%s\" not recognized", format),
-				 parser_errposition(pstate, defel->location)));
-
-	datum = OidFunctionCall1(handlerOid, BoolGetDatum(is_from));
-	routine = (Node *) DatumGetPointer(datum);
-	if (is_from)
-	{
-		if (routine == NULL || !IsA(routine, CopyFromRoutine))
-			ereport(
-					ERROR,
-					(errcode(
-							 ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("COPY handler function "
-							"%s(%u) did not return a "
-							"CopyFromRoutine struct",
-							format, handlerOid),
-					 parser_errposition(
-										pstate, defel->location)));
-	}
-	else
-	{
-		if (routine == NULL || !IsA(routine, CopyToRoutine))
-			ereport(
-					ERROR,
-					(errcode(
-							 ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("COPY handler function "
-							"%s(%u) did not return a "
-							"CopyToRoutine struct",
-							format, handlerOid),
-					 parser_errposition(
-										pstate, defel->location)));
-	}
-
-	opts_out->routine = routine;
-}
-
 /*
  * Process the statement option list for COPY.
  *
@@ -609,10 +523,25 @@ ProcessCopyOptions(ParseState *pstate,
 
 		if (strcmp(defel->defname, "format") == 0)
 		{
+			char	   *fmt = defGetString(defel);
+			Oid			handler;
+
 			if (format_specified)
 				errorConflictingDefElem(defel, pstate);
 			format_specified = true;
-			ProcessCopyOptionFormat(pstate, opts_out, is_from, defel);
+			if (strcmp(fmt, "text") == 0)
+				 /* default format */ ;
+			else if (strcmp(fmt, "csv") == 0)
+				opts_out->csv_mode = true;
+			else if (strcmp(fmt, "binary") == 0)
+				opts_out->binary = true;
+			else if ((handler = LookupCustomFormat(fmt)) != InvalidOid)
+				opts_out->custom_format_handler = handler;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("COPY format \"%s\" not recognized", fmt),
+						 parser_errposition(pstate, defel->location)));
 		}
 		else if (strcmp(defel->defname, "freeze") == 0)
 		{
@@ -763,15 +692,6 @@ ProcessCopyOptions(ParseState *pstate,
 					 parser_errposition(pstate, defel->location)));
 	}
 
-	/* If format option isn't specified, we use a built-in routine. */
-	if (!format_specified)
-	{
-		if (is_from)
-			opts_out->routine = (Node *) CopyFromGetBuiltinRoutine(opts_out);
-		else
-			opts_out->routine = (Node *) CopyToGetBuiltinRoutine(opts_out);
-	}
-
 	/*
 	 * Check for incompatible options (must do these three before inserting
 	 * defaults)
@@ -1104,3 +1024,63 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)
 
 	return attnums;
 }
+
+/*
+ * Return the Oid of the handler function corresponding to the given format
+ * name, if exists. Otherwise, return InvalidOid.
+ */
+static Oid
+LookupCustomFormat(char *fmt)
+{
+	Oid		funcargtypes[1];
+	Oid		handlerOid;
+
+	handlerOid = LookupFuncName(list_make1(makeString(fmt)), 1,
+								funcargtypes, true);
+
+	return OidIsValid(handlerOid) ? handlerOid : InvalidOid;
+}
+
+/*
+ * Workhorse for GetCopyFromCustomRoutine() and GetCopyToCustomRoutine().
+ *
+ * Call the specified custom COPY format handler function to get its
+ * either CopyFromRoutine or CopyToRoutine struct depending on is_from.
+ */
+static Node *
+GetCustomFormatRoutine(Oid handler, bool is_from)
+{
+	Datum	datum;
+	Node	*routine;
+
+	Assert(OidIsValid(handler));
+
+	datum = OidFunctionCall1(handler, BoolGetDatum(is_from));
+	routine = (Node *) DatumGetPointer(datum);
+
+	if (routine == NULL ||
+		(is_from && !IsA(routine, CopyFromRoutine)) ||
+		(!is_from && !IsA(routine, CopyToRoutine)))
+		elog(ERROR, "COPY format handler function %u did not return %s struct",
+			 handler, is_from ? "CopyFromRoutine" :"CopyToRoutine");
+
+	return routine;
+}
+
+/*
+ * Return CopyFromRoutine returned by the copy format handler function.
+ */
+CopyFromRoutine*
+GetCopyFromCustomRoutine(Oid handler)
+{
+	return castNode(CopyFromRoutine, GetCustomFormatRoutine(handler, true));
+}
+
+/*
+ * Return CopyToRoutine returned by the copy format handler function.
+ */
+CopyToRoutine *
+GetCopyToCustomRoutine(Oid handler)
+{
+	return castNode(CopyToRoutine, GetCustomFormatRoutine(handler, false));
+}
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 3f6b0031d94..257cda99b25 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -154,15 +154,17 @@ static const CopyFromRoutine CopyFromRoutineBinary = {
 };
 
 /* Return a built-in COPY FROM routine for the given options */
-const CopyFromRoutine *
-CopyFromGetBuiltinRoutine(CopyFormatOptions *opts)
+static void
+SetCopyFromFormatRoutine(CopyFromState cstate)
 {
-	if (opts->csv_mode)
-		return &CopyFromRoutineCSV;
-	else if (opts->binary)
-		return &CopyFromRoutineBinary;
+	if (cstate->opts.csv_mode)
+		cstate->routine = &CopyFromRoutineCSV;
+	else if (cstate->opts.binary)
+		cstate->routine = &CopyFromRoutineBinary;
+	else if (OidIsValid(cstate->opts.custom_format_handler))
+		cstate->routine = GetCopyFromCustomRoutine(cstate->opts.custom_format_handler);
 	else
-		return &CopyFromRoutineText;
+		cstate->routine = &CopyFromRoutineText;
 }
 
 /* Implementation of the start callback for text and CSV formats */
@@ -1568,7 +1570,7 @@ BeginCopyFrom(ParseState *pstate,
 	ProcessCopyOptions(pstate, &cstate->opts, true /* is_from */ , options);
 
 	/* Set the format routine */
-	cstate->routine = (const CopyFromRoutine *) cstate->opts.routine;
+	SetCopyFromFormatRoutine(cstate);
 
 	/* Process the target relation */
 	cstate->rel = rel;
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index da281f32950..86d01a0d9b3 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -115,15 +115,17 @@ static const CopyToRoutine CopyToRoutineBinary = {
 };
 
 /* Return a built-in COPY TO routine for the given options */
-const CopyToRoutine *
-CopyToGetBuiltinRoutine(CopyFormatOptions *opts)
+static void
+SetCopyToFormatRoutine(CopyToState cstate)
 {
-	if (opts->csv_mode)
-		return &CopyToRoutineCSV;
-	else if (opts->binary)
-		return &CopyToRoutineBinary;
+	if (cstate->opts.csv_mode)
+		cstate->routine = &CopyToRoutineCSV;
+	else if (cstate->opts.binary)
+		cstate->routine = &CopyToRoutineBinary;
+	else if (OidIsValid(cstate->opts.custom_format_handler))
+		cstate->routine = GetCopyToCustomRoutine(cstate->opts.custom_format_handler);
 	else
-		return &CopyToRoutineText;
+		cstate->routine = &CopyToRoutineText;
 }
 
 /* Implementation of the start callback for text and CSV formats */
@@ -656,7 +658,7 @@ BeginCopyTo(ParseState *pstate,
 	ProcessCopyOptions(pstate, &cstate->opts, false /* is_from */ , options);
 
 	/* Set format routine */
-	cstate->routine = (const CopyToRoutine *) cstate->opts.routine;
+	SetCopyToFormatRoutine(cstate);
 
 	/* Process the source/target relation or query */
 	if (rel)
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 586d6c0fe2e..f11173cc9ef 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -87,7 +87,7 @@ typedef struct CopyFormatOptions
 	CopyLogVerbosityChoice log_verbosity;	/* verbosity of logged messages */
 	int64		reject_limit;	/* maximum tolerable number of errors */
 	List	   *convert_select; /* list of column names (can be NIL) */
-	Node	   *routine;		/* CopyToRoutine or CopyFromRoutine */
+	Oid			custom_format_handler; /* handler function for custom format routine */
 } CopyFormatOptions;
 
 /* These are private in commands/copy[from|to]_internal.h */
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
index 389f887b2c1..9567b993b87 100644
--- a/src/include/commands/copyapi.h
+++ b/src/include/commands/copyapi.h
@@ -105,6 +105,9 @@ typedef struct CopyFromRoutine
 	void		(*CopyFromEnd) (CopyFromState cstate);
 } CopyFromRoutine;
 
+extern CopyFromRoutine* GetCopyFromCustomRoutine(Oid handler);
+extern CopyToRoutine * GetCopyToCustomRoutine(Oid handler);
+
 extern int	CopyFromStateGetData(CopyFromState cstate, void *dest, int minread, int maxread);
 
 extern void CopyFromSkipErrorRow(CopyFromState cstate);
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index a65bbbc962e..af425cf5fd9 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -197,6 +197,4 @@ extern bool CopyFromCSVOneRow(CopyFromState cstate, ExprContext *econtext,
 extern bool CopyFromBinaryOneRow(CopyFromState cstate, ExprContext *econtext,
 								 Datum *values, bool *nulls);
 
-const struct CopyFromRoutine *CopyFromGetBuiltinRoutine(CopyFormatOptions *opts);
-
 #endif							/* COPYFROM_INTERNAL_H */
