diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index d2b98e1..dc164ee 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -1,7 +1,7 @@
 # contrib/postgres_fdw/Makefile
 
 MODULE_big = postgres_fdw
-OBJS = postgres_fdw.o option.o deparse.o connection.o $(WIN32RES)
+OBJS = postgres_fdw.o option.o deparse.o connection.o shippable.o $(WIN32RES)
 PGFILEDESC = "postgres_fdw - foreign data wrapper for PostgreSQL"
 
 PG_CPPFLAGS = -I$(libpq_srcdir)
@@ -10,7 +10,10 @@ SHLIB_LINK = $(libpq)
 EXTENSION = postgres_fdw
 DATA = postgres_fdw--1.0.sql
 
-REGRESS = postgres_fdw
+# Note: shippable tests depend on postgres_fdw tests setup
+REGRESS = postgres_fdw shippable
+# Note: shippable tests require cube and seg
+EXTRA_INSTALL = contrib/cube contrib/seg
 
 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 697de60..aa575b3 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -233,6 +233,9 @@ foreign_expr_walker(Node *node,
 	Oid			collation;
 	FDWCollateState state;
 
+	/* Access extension metadata from fpinfo on baserel */
+	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)(glob_cxt->foreignrel->fdw_private);
+
 	/* Need do nothing for empty subexpressions */
 	if (node == NULL)
 		return true;
@@ -378,7 +381,8 @@ foreign_expr_walker(Node *node,
 				 * can't be sent to remote because it might have incompatible
 				 * semantics on remote side.
 				 */
-				if (!is_builtin(fe->funcid))
+				if (!is_builtin(fe->funcid) &&
+					!is_shippable(fe->funcid, ProcedureRelationId, fpinfo->server, fpinfo->extensions))
 					return false;
 
 				/*
@@ -426,7 +430,8 @@ foreign_expr_walker(Node *node,
 				 * (If the operator is, surely its underlying function is
 				 * too.)
 				 */
-				if (!is_builtin(oe->opno))
+				if (!is_builtin(oe->opno) &&
+					!is_shippable(oe->opno, OperatorRelationId, fpinfo->server, fpinfo->extensions))
 					return false;
 
 				/*
@@ -466,7 +471,8 @@ foreign_expr_walker(Node *node,
 				/*
 				 * Again, only built-in operators can be sent to remote.
 				 */
-				if (!is_builtin(oe->opno))
+				if (!is_builtin(oe->opno) &&
+					!is_shippable(oe->opno, OperatorRelationId, fpinfo->server, fpinfo->extensions))
 					return false;
 
 				/*
@@ -616,7 +622,9 @@ foreign_expr_walker(Node *node,
 	 * If result type of given expression is not built-in, it can't be sent to
 	 * remote because it might have incompatible semantics on remote side.
 	 */
-	if (check_type && !is_builtin(exprType(node)))
+	if (check_type &&
+		!is_builtin(exprType(node)) &&
+		!is_shippable(exprType(node), TypeRelationId, fpinfo->server, fpinfo->extensions))
 		return false;
 
 	/*
@@ -1351,6 +1359,9 @@ deparseConst(Const *node, deparse_expr_cxt *context)
 	bool		isfloat = false;
 	bool		needlabel;
 
+	/* Access extension metadata from fpinfo on baserel */
+	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)(context->foreignrel->fdw_private);
+
 	if (node->constisnull)
 	{
 		appendStringInfoString(buf, "NULL");
@@ -1428,9 +1439,21 @@ deparseConst(Const *node, deparse_expr_cxt *context)
 			break;
 	}
 	if (needlabel)
-		appendStringInfo(buf, "::%s",
-						 format_type_with_typemod(node->consttype,
-												  node->consttypmod));
+	{
+		/*
+		 * References to extension types need to be fully qualified,
+		 * but references to built-in types shouldn't be.
+		 */
+		if (!is_builtin(node->consttype) &&
+			 is_shippable(node->consttype, TypeRelationId, fpinfo->server, fpinfo->extensions))
+		{
+			appendStringInfo(buf, "::%s", format_type_be_qualified(node->consttype));
+		}
+		else
+		{
+			appendStringInfo(buf, "::%s", format_type_with_typemod(node->consttype, node->consttypmod));
+		}
+	}
 }
 
 /*
diff --git a/contrib/postgres_fdw/expected/shippable.out b/contrib/postgres_fdw/expected/shippable.out
new file mode 100644
index 0000000..abf924e
--- /dev/null
+++ b/contrib/postgres_fdw/expected/shippable.out
@@ -0,0 +1,225 @@
+-- ===================================================================
+-- create FDW objects
+-- ===================================================================
+-- Error, extension isn't installed yet
+ALTER SERVER loopback OPTIONS (ADD extensions 'cube');
+ERROR:  required extension "cube" is not installed
+HINT:  Extension must be installed locally before it can be used on a remote server.
+-- Try again
+CREATE EXTENSION cube;
+ALTER SERVER loopback OPTIONS (ADD extensions 'cube');
+ALTER SERVER loopback OPTIONS (DROP extensions);
+-- ===================================================================
+-- create objects used through FDW loopback server
+-- ===================================================================
+CREATE SCHEMA "SH 1";
+CREATE TABLE "SH 1"."TBL 1" (
+	"C 1" int NOT NULL,
+	c2 int NOT NULL,
+	c3 cube,
+	c4 timestamptz
+);
+INSERT INTO "SH 1"."TBL 1"
+	SELECT id,
+	       2 * id,
+	       cube(id,2*id),
+	       '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval
+	FROM generate_series(1, 1000) id;
+ANALYZE "SH 1"."TBL 1";
+-- ===================================================================
+-- create foreign table
+-- ===================================================================
+CREATE FOREIGN TABLE shft1 (
+	"C 1" int NOT NULL,
+	c2 int NOT NULL,
+	c3 cube,
+	c4 timestamptz
+) SERVER loopback
+OPTIONS (schema_name 'SH 1', table_name 'TBL 1');
+-- ===================================================================
+-- simple queries
+-- ===================================================================
+-- without operator shipping
+EXPLAIN (COSTS false) SELECT * FROM shft1 LIMIT 1;
+         QUERY PLAN          
+-----------------------------
+ Limit
+   ->  Foreign Scan on shft1
+(2 rows)
+
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5);
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Foreign Scan on public.shft1  (cost=100.00..205.06 rows=15 width=4)
+   Output: c2
+   Filter: (shft1.c3 && '(1.5),(2.5)'::cube)
+   Remote SQL: SELECT c2, c3 FROM "SH 1"."TBL 1"
+(4 rows)
+
+SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5);
+ c2 
+----
+  2
+  4
+(2 rows)
+
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Foreign Scan on public.shft1  (cost=100.00..205.06 rows=15 width=4)
+   Output: c2
+   Filter: (shft1.c3 && '(1.5),(2.5)'::cube)
+   Remote SQL: SELECT c2, c3 FROM "SH 1"."TBL 1"
+(4 rows)
+
+-- with operator shipping
+ALTER SERVER loopback OPTIONS (ADD extensions 'cube');
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5);
+                                               QUERY PLAN                                                
+---------------------------------------------------------------------------------------------------------
+ Foreign Scan on public.shft1  (cost=100.00..146.86 rows=15 width=4)
+   Output: c2
+   Remote SQL: SELECT c2 FROM "SH 1"."TBL 1" WHERE ((c3 OPERATOR(public.&&) '(1.5),(2.5)'::public.cube))
+(3 rows)
+
+SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5);
+ c2 
+----
+  2
+  4
+(2 rows)
+
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube;
+                                               QUERY PLAN                                                
+---------------------------------------------------------------------------------------------------------
+ Foreign Scan on public.shft1  (cost=100.00..146.86 rows=15 width=4)
+   Output: c2
+   Remote SQL: SELECT c2 FROM "SH 1"."TBL 1" WHERE ((c3 OPERATOR(public.&&) '(1.5),(2.5)'::public.cube))
+(3 rows)
+
+EXPLAIN VERBOSE SELECT cube_dim(c3) FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube;
+                                               QUERY PLAN                                                
+---------------------------------------------------------------------------------------------------------
+ Foreign Scan on public.shft1  (cost=100.00..128.43 rows=7 width=32)
+   Output: cube_dim(c3)
+   Remote SQL: SELECT c3 FROM "SH 1"."TBL 1" WHERE ((c3 OPERATOR(public.&&) '(1.5),(2.5)'::public.cube))
+(3 rows)
+
+SELECT cube_dim(c3) FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube;
+ cube_dim 
+----------
+        1
+        1
+(2 rows)
+
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE cube_dim(c3) = 1 LIMIT 2;
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
+ Limit  (cost=100.00..107.22 rows=2 width=4)
+   Output: c2
+   ->  Foreign Scan on public.shft1  (cost=100.00..154.18 rows=15 width=4)
+         Output: c2
+         Remote SQL: SELECT c2 FROM "SH 1"."TBL 1" WHERE ((public.cube_dim(c3) = 1))
+(5 rows)
+
+SELECT c2 FROM shft1 WHERE cube_dim(c3) = 1 LIMIT 2;
+ c2 
+----
+  2
+  4
+(2 rows)
+
+-- ===================================================================
+-- add a second server with different extension shipping
+-- ===================================================================
+DO $d$
+    BEGIN
+        EXECUTE $$CREATE SERVER loopback_two FOREIGN DATA WRAPPER postgres_fdw
+            OPTIONS (dbname '$$||current_database()||$$',
+                     port '$$||current_setting('port')||$$'
+            )$$;
+    END;
+$d$;
+CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_two;
+CREATE EXTENSION seg;
+CREATE TABLE seg_local (
+	id integer,
+	s seg,
+  n text
+);
+INSERT INTO seg_local (id, s, n) VALUES (1, '1.0 .. 2.0', 'foo');
+INSERT INTO seg_local (id, s, n) VALUES (2, '3.0 .. 4.0', 'bar');
+INSERT INTO seg_local (id, s, n) VALUES (3, '5.0 .. 6.0', 'baz');
+ANALYZE seg_local;
+CREATE FOREIGN TABLE seg_remote_two (
+  id integer,
+  s seg,
+  n text
+) SERVER loopback_two
+OPTIONS (table_name 'seg_local');
+SELECT id FROM seg_local WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+ id 
+----
+  3
+(1 row)
+
+EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Foreign Scan on public.seg_remote_two  (cost=100.00..157.88 rows=1 width=4)
+   Output: id
+   Filter: (seg_remote_two.s && '5.8 .. 6.2'::seg)
+   Remote SQL: SELECT id, s FROM public.seg_local WHERE ((n = 'baz'::text))
+(4 rows)
+
+ALTER SERVER loopback_two OPTIONS (ADD extensions 'seg');
+EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+                                                           QUERY PLAN                                                           
+--------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on public.seg_remote_two  (cost=100.00..153.89 rows=1 width=4)
+   Output: id
+   Remote SQL: SELECT id FROM public.seg_local WHERE ((s OPERATOR(public.&&) '5.8 .. 6.2'::public.seg)) AND ((n = 'baz'::text))
+(3 rows)
+
+CREATE FOREIGN TABLE seg_remote_one (
+  id integer,
+  s seg,
+  n text
+) SERVER loopback
+OPTIONS (table_name 'seg_local');
+SELECT id FROM seg_remote_one WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+ id 
+----
+  3
+(1 row)
+
+EXPLAIN VERBOSE SELECT id FROM seg_remote_one WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Foreign Scan on public.seg_remote_one  (cost=100.00..157.88 rows=1 width=4)
+   Output: id
+   Filter: (seg_remote_one.s && '5.8 .. 6.2'::seg)
+   Remote SQL: SELECT id, s FROM public.seg_local WHERE ((n = 'baz'::text))
+(4 rows)
+
+EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+                                                           QUERY PLAN                                                           
+--------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on public.seg_remote_two  (cost=100.00..153.89 rows=1 width=4)
+   Output: id
+   Remote SQL: SELECT id FROM public.seg_local WHERE ((s OPERATOR(public.&&) '5.8 .. 6.2'::public.seg)) AND ((n = 'baz'::text))
+(3 rows)
+
+-- ===================================================================
+-- clean up
+-- ===================================================================
+DROP FOREIGN TABLE seg_remote_one, seg_remote_two;
+DROP USER MAPPING FOR CURRENT_USER SERVER loopback_two;
+DROP SERVER loopback_two;
+DROP TABLE seg_local;
+DROP FOREIGN TABLE shft1;
+DROP TABLE "SH 1"."TBL 1";
+DROP SCHEMA "SH 1";
+DROP EXTENSION cube;
+DROP EXTENSION seg;
+ALTER SERVER loopback OPTIONS (DROP extensions);
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 7547ec2..a20f626 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -19,6 +19,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_user_mapping.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -124,6 +126,11 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
 						 errmsg("%s requires a non-negative numeric value",
 								def->defname)));
 		}
+		else if (strcmp(def->defname, "extensions") == 0)
+		{
+			/* check that the requested extensions are actually installed */
+			(void) ExtractExtensionList(defGetString(def), false);
+		}
 	}
 
 	PG_RETURN_VOID();
@@ -153,6 +160,8 @@ InitPgFdwOptions(void)
 		/* updatable is available on both server and table */
 		{"updatable", ForeignServerRelationId, false},
 		{"updatable", ForeignTableRelationId, false},
+		/* "extensions" option is available on server */
+		{"extensions", ForeignServerRelationId, false},
 		{NULL, InvalidOid, false}
 	};
 
@@ -293,3 +302,49 @@ ExtractConnectionOptions(List *defelems, const char **keywords,
 	}
 	return i;
 }
+
+/*
+ * Parse a comma-separated string and return a List of the Oids of the
+ * extensions in the string. If an extension provided cannot be looked
+ * up in the catalog (it hasn't been installed or doesn't exist) then
+ * raise an error.
+ */
+List *
+ExtractExtensionList(char *extensionString, bool populateList)
+{
+	List *extlist;
+	List *extensionOids = NIL;
+	ListCell   *l;
+
+	if (!SplitIdentifierString(extensionString, ',', &extlist))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			 errmsg("invalid extension list syntax")));
+	}
+
+	foreach(l, extlist)
+	{
+		const char *extension_name = (const char *) lfirst(l);
+		Oid extension_oid = get_extension_oid(extension_name, true);
+
+		if (!OidIsValid(extension_oid))
+			ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("required extension \"%s\" is not installed",
+						extension_name),
+				 errhint("Extension must be installed locally before it can be used on a remote server.")));
+		else if (populateList)
+		{
+			/*
+			 * Only add this extension OID to the list if it is not already
+			 * in included.
+			 */
+			if (!list_member_oid(extensionOids, extension_oid))
+				extensionOids = lappend_oid(extensionOids, extension_oid);
+		}
+	}
+
+	list_free(extlist);
+	return extensionOids;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index e4d799c..2614777 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -47,39 +47,6 @@ PG_MODULE_MAGIC;
 /* Default CPU cost to process 1 row (above and beyond cpu_tuple_cost). */
 #define DEFAULT_FDW_TUPLE_COST		0.01
 
-/*
- * FDW-specific planner information kept in RelOptInfo.fdw_private for a
- * foreign table.  This information is collected by postgresGetForeignRelSize.
- */
-typedef struct PgFdwRelationInfo
-{
-	/* baserestrictinfo clauses, broken down into safe and unsafe subsets. */
-	List	   *remote_conds;
-	List	   *local_conds;
-
-	/* Bitmap of attr numbers we need to fetch from the remote server. */
-	Bitmapset  *attrs_used;
-
-	/* Cost and selectivity of local_conds. */
-	QualCost	local_conds_cost;
-	Selectivity local_conds_sel;
-
-	/* Estimated size and cost for a scan with baserestrictinfo quals. */
-	double		rows;
-	int			width;
-	Cost		startup_cost;
-	Cost		total_cost;
-
-	/* Options extracted from catalogs. */
-	bool		use_remote_estimate;
-	Cost		fdw_startup_cost;
-	Cost		fdw_tuple_cost;
-
-	/* Cached catalog information. */
-	ForeignTable *table;
-	ForeignServer *server;
-	UserMapping *user;			/* only set in use_remote_estimate mode */
-} PgFdwRelationInfo;
 
 /*
  * Indexes of FDW-private information stored in fdw_private lists.
@@ -405,6 +372,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	fpinfo->use_remote_estimate = false;
 	fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
 	fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
+	fpinfo->extensions = NIL;
 
 	foreach(lc, fpinfo->server->options)
 	{
@@ -416,6 +384,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
 			fpinfo->fdw_startup_cost = strtod(defGetString(def), NULL);
 		else if (strcmp(def->defname, "fdw_tuple_cost") == 0)
 			fpinfo->fdw_tuple_cost = strtod(defGetString(def), NULL);
+		else if (strcmp(def->defname, "extensions") == 0)
+			fpinfo->extensions =
+				ExtractExtensionList(defGetString(def), true);
 	}
 	foreach(lc, fpinfo->table->options)
 	{
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 3835ddb..f30d9d1 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -20,6 +20,43 @@
 
 #include "libpq-fe.h"
 
+/*
+ * FDW-specific planner information kept in RelOptInfo.fdw_private for a
+ * foreign table.  This information is collected by postgresGetForeignRelSize.
+ */
+typedef struct PgFdwRelationInfo
+{
+	/* baserestrictinfo clauses, broken down into safe and unsafe subsets. */
+	List	   *remote_conds;
+	List	   *local_conds;
+
+	/* Bitmap of attr numbers we need to fetch from the remote server. */
+	Bitmapset  *attrs_used;
+
+	/* Cost and selectivity of local_conds. */
+	QualCost	local_conds_cost;
+	Selectivity local_conds_sel;
+
+	/* Estimated size and cost for a scan with baserestrictinfo quals. */
+	double		rows;
+	int			width;
+	Cost		startup_cost;
+	Cost		total_cost;
+
+	/* Options extracted from catalogs. */
+	bool		use_remote_estimate;
+	Cost		fdw_startup_cost;
+	Cost		fdw_tuple_cost;
+
+	/* Optional extensions to support (list of Oids). */
+	List        *extensions;
+
+	/* Cached catalog information. */
+	ForeignTable *table;
+	ForeignServer *server;
+	UserMapping *user;			/* only set in use_remote_estimate mode */
+} PgFdwRelationInfo;
+
 /* in postgres_fdw.c */
 extern int	set_transmission_modes(void);
 extern void reset_transmission_modes(int nestlevel);
@@ -37,6 +74,11 @@ extern void pgfdw_report_error(int elevel, PGresult *res, PGconn *conn,
 extern int ExtractConnectionOptions(List *defelems,
 						 const char **keywords,
 						 const char **values);
+extern List *ExtractExtensionList(char *extensionString,
+								  bool populateList);
+
+/* in shippable.c */
+extern bool is_shippable(Oid objnumber, Oid classnumber, ForeignServer *server, List *extension_list);
 
 /* in deparse.c */
 extern void classifyConditions(PlannerInfo *root,
diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c
new file mode 100644
index 0000000..960a7ec
--- /dev/null
+++ b/contrib/postgres_fdw/shippable.c
@@ -0,0 +1,211 @@
+/*-------------------------------------------------------------------------
+ *
+ * shippable.c
+ *	  Facility to track database objects shippable to a foreign server.
+ *
+ * Determine if functions and operators for non-built-in types/functions/ops
+ * are shippable to the remote server.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  contrib/postgres_fdw/shippable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "postgres_fdw.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_depend.h"
+#include "utils/fmgroids.h"
+#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+/* Hash table for informations about remote objects we'll call */
+static HTAB *ShippableCacheHash = NULL;
+
+/* objid is the lookup key, must appear first */
+typedef struct
+{
+	/* extension the object appears within, or InvalidOid if none */
+	Oid serverid;
+	Oid	classid;
+	Oid	objid;
+} ShippableCacheKey;
+
+typedef struct
+{
+	/* lookup key - must be first */
+	ShippableCacheKey key;
+	bool shippable;
+} ShippableCacheEntry;
+
+/*
+ * Flush all cache entries when pg_foreign_server is updated.
+ */
+static void
+InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+	HASH_SEQ_STATUS status;
+	ShippableCacheEntry *entry;
+
+	hash_seq_init(&status, ShippableCacheHash);
+	while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL)
+	{
+		if (hash_search(ShippableCacheHash,
+						(void *) &entry->key,
+						HASH_REMOVE,
+						NULL) == NULL)
+			elog(ERROR, "hash table corrupted");
+	}
+}
+
+/*
+ * Initialize the cache of functions we can ship to remote server.
+ */
+static void
+InitializeShippableCache(void)
+{
+	HASHCTL ctl;
+
+	/* Initialize the hash table. */
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(ShippableCacheKey);
+	ctl.entrysize = sizeof(ShippableCacheEntry);
+	ShippableCacheHash =
+		hash_create("Shippable cache", 256, &ctl, HASH_ELEM);
+
+	CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
+								  InvalidateShippableCacheCallback,
+								  (Datum) 0);
+}
+
+/*
+ * Returns true if given operator/function is part of an extension listed in
+ * the server options.
+ */
+static bool
+lookup_shippable(Oid objnumber, Oid classnumber, List *extension_list)
+{
+	static int nkeys = 2;
+	ScanKeyData key[nkeys];
+	HeapTuple tup;
+	Relation depRel;
+	SysScanDesc scan;
+	bool is_shippable = false;
+
+	/* Always return false if the user hasn't set the "extensions" option */
+	if (extension_list == NIL)
+		return false;
+
+	depRel = heap_open(DependRelationId, RowExclusiveLock);
+
+	/*
+	 * Scan the system dependency table for all entries this object
+	 * depends on, then iterate through and see if one of them
+	 * is an extension declared by the user in the options
+	 */
+	ScanKeyInit(&key[0],
+				Anum_pg_depend_classid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(classnumber));
+
+	ScanKeyInit(&key[1],
+				Anum_pg_depend_objid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(objnumber));
+
+	scan = systable_beginscan(depRel, DependDependerIndexId, true,
+							  GetCatalogSnapshot(depRel->rd_id), nkeys, key);
+
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+		if (foundDep->deptype == DEPENDENCY_EXTENSION &&
+			list_member_oid(extension_list, foundDep->refobjid))
+		{
+			is_shippable = true;
+			break;
+		}
+	}
+
+	systable_endscan(scan);
+	relation_close(depRel, RowExclusiveLock);
+
+	return is_shippable;
+}
+
+/*
+ * is_shippable
+ *     Is this object (proc/op/type) shippable to foreign server?
+ *     Check cache first, then look-up whether (proc/op/type) is
+ *     part of a declared extension if it is not cached.
+ */
+bool
+is_shippable(Oid objnumber, Oid classnumber, ForeignServer *server, List *extension_list)
+{
+	ShippableCacheKey key;
+	ShippableCacheEntry *entry;
+
+	/* Always return false if the user hasn't set the "extensions" option */
+	if (extension_list == NIL)
+		return false;
+
+	/* Find existing cache, if any. */
+	if (!ShippableCacheHash)
+		InitializeShippableCache();
+
+	/* Zero out the key */
+	MemSet(&key, 0, sizeof(key));
+
+	key.objid = objnumber;
+	key.classid = classnumber;
+	key.serverid = server->serverid;
+
+	entry = (ShippableCacheEntry *)
+				 hash_search(ShippableCacheHash,
+					(void *) &key,
+					HASH_FIND,
+					NULL);
+
+	/* Not found in ShippableCacheHash cache.  Construct new entry. */
+	if (!entry)
+	{
+		/*
+		 * Right now "shippability" is exclusively a function of whether
+		 * the obj (proc/op/type) is in an extension declared by the user.
+		 * In the future we could additionally have a whitelist of functions
+		 * declared one at a time.
+		 */
+		bool shippable = lookup_shippable(objnumber, classnumber, extension_list);
+
+		/*
+		 * Don't create a new hash entry until *after* we have the shippable
+		 * result in hand, as the shippable lookup might trigger a cache
+		 * invalidation.
+		 */
+		entry = (ShippableCacheEntry *)
+					 hash_search(ShippableCacheHash,
+						(void *) &key,
+						HASH_ENTER,
+						NULL);
+
+		entry->shippable = shippable;
+	}
+
+	if (!entry)
+		return false;
+	else
+		return entry->shippable;
+}
diff --git a/contrib/postgres_fdw/sql/shippable.sql b/contrib/postgres_fdw/sql/shippable.sql
new file mode 100644
index 0000000..d3e03c2
--- /dev/null
+++ b/contrib/postgres_fdw/sql/shippable.sql
@@ -0,0 +1,133 @@
+-- ===================================================================
+-- create FDW objects
+-- ===================================================================
+
+-- Error, extension isn't installed yet
+ALTER SERVER loopback OPTIONS (ADD extensions 'cube');
+
+-- Try again
+CREATE EXTENSION cube;
+ALTER SERVER loopback OPTIONS (ADD extensions 'cube');
+ALTER SERVER loopback OPTIONS (DROP extensions);
+
+
+-- ===================================================================
+-- create objects used through FDW loopback server
+-- ===================================================================
+
+CREATE SCHEMA "SH 1";
+CREATE TABLE "SH 1"."TBL 1" (
+	"C 1" int NOT NULL,
+	c2 int NOT NULL,
+	c3 cube,
+	c4 timestamptz
+);
+
+INSERT INTO "SH 1"."TBL 1"
+	SELECT id,
+	       2 * id,
+	       cube(id,2*id),
+	       '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval
+	FROM generate_series(1, 1000) id;
+
+ANALYZE "SH 1"."TBL 1";
+
+-- ===================================================================
+-- create foreign table
+-- ===================================================================
+
+CREATE FOREIGN TABLE shft1 (
+	"C 1" int NOT NULL,
+	c2 int NOT NULL,
+	c3 cube,
+	c4 timestamptz
+) SERVER loopback
+OPTIONS (schema_name 'SH 1', table_name 'TBL 1');
+
+-- ===================================================================
+-- simple queries
+-- ===================================================================
+
+-- without operator shipping
+EXPLAIN (COSTS false) SELECT * FROM shft1 LIMIT 1;
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5);
+SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5);
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube;
+
+-- with operator shipping
+ALTER SERVER loopback OPTIONS (ADD extensions 'cube');
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5);
+SELECT c2 FROM shft1 WHERE c3 && cube(1.5,2.5);
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube;
+EXPLAIN VERBOSE SELECT cube_dim(c3) FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube;
+SELECT cube_dim(c3) FROM shft1 WHERE c3 && '(1.5),(2.5)'::cube;
+
+EXPLAIN VERBOSE SELECT c2 FROM shft1 WHERE cube_dim(c3) = 1 LIMIT 2;
+SELECT c2 FROM shft1 WHERE cube_dim(c3) = 1 LIMIT 2;
+
+-- ===================================================================
+-- add a second server with different extension shipping
+-- ===================================================================
+
+DO $d$
+    BEGIN
+        EXECUTE $$CREATE SERVER loopback_two FOREIGN DATA WRAPPER postgres_fdw
+            OPTIONS (dbname '$$||current_database()||$$',
+                     port '$$||current_setting('port')||$$'
+            )$$;
+    END;
+$d$;
+
+CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_two;
+
+CREATE EXTENSION seg;
+
+CREATE TABLE seg_local (
+	id integer,
+	s seg,
+  n text
+);
+
+INSERT INTO seg_local (id, s, n) VALUES (1, '1.0 .. 2.0', 'foo');
+INSERT INTO seg_local (id, s, n) VALUES (2, '3.0 .. 4.0', 'bar');
+INSERT INTO seg_local (id, s, n) VALUES (3, '5.0 .. 6.0', 'baz');
+
+ANALYZE seg_local;
+
+CREATE FOREIGN TABLE seg_remote_two (
+  id integer,
+  s seg,
+  n text
+) SERVER loopback_two
+OPTIONS (table_name 'seg_local');
+
+SELECT id FROM seg_local WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+ALTER SERVER loopback_two OPTIONS (ADD extensions 'seg');
+EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+
+CREATE FOREIGN TABLE seg_remote_one (
+  id integer,
+  s seg,
+  n text
+) SERVER loopback
+OPTIONS (table_name 'seg_local');
+
+SELECT id FROM seg_remote_one WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+EXPLAIN VERBOSE SELECT id FROM seg_remote_one WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+EXPLAIN VERBOSE SELECT id FROM seg_remote_two WHERE s && '5.8 .. 6.2'::seg AND n = 'baz';
+
+
+-- ===================================================================
+-- clean up
+-- ===================================================================
+DROP FOREIGN TABLE seg_remote_one, seg_remote_two;
+DROP USER MAPPING FOR CURRENT_USER SERVER loopback_two;
+DROP SERVER loopback_two;
+DROP TABLE seg_local;
+DROP FOREIGN TABLE shft1;
+DROP TABLE "SH 1"."TBL 1";
+DROP SCHEMA "SH 1";
+DROP EXTENSION cube;
+DROP EXTENSION seg;
+ALTER SERVER loopback OPTIONS (DROP extensions);
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 7c92282..1e7ec08 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -373,6 +373,38 @@
     foreign tables, see <xref linkend="sql-createforeigntable">.
    </para>
   </sect3>
+  
+  <sect3>
+   <title>Extension Options</title>
+
+   <para>
+    By default only built-in operators and functions will be sent from the 
+    local to the foreign server. This may be overridden using the following
+    option:
+   </para>
+
+   <variablelist>
+
+    <varlistentry>
+     <term><literal>extensions</literal></term>
+     <listitem>
+      <para>
+       This option controls the list of extensions that are expected to be
+       installed on the foreign server, using a comma-separated list of 
+       extension names. Those extensions are also expected to be installed
+       on the local server too. This option is available for servers.
+      </para>
+<programlisting>
+CREATE SERVER foreign_server
+        FOREIGN DATA WRAPPER postgres_fdw
+        OPTIONS (host '127.0.0.1', port '5432', dbname 'my_db', extensions 'cube, seg');
+</programlisting>
+     </listitem>
+    </varlistentry>
+
+   </variablelist>
+  </sect3>
+  
  </sect2>
 
  <sect2>
