diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 60fa1a8..47f1d34 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -350,6 +350,14 @@
    </para>
 
    <para>
+    When an extension only uses SQL definitions (and does not need to ship
+    compiled binary code, usually from C source), then it can use
+    the <firstterm>template</> facility in order to upload the necessary
+    script to the <productname>PostgreSQL</> server, all using the usual
+    clients and protocol.
+   </para>
+
+   <para>
     The kinds of SQL objects that can be members of an extension are shown in
     the description of <xref linkend="sql-alterextension">.  Notably, objects
     that are database-cluster-wide, such as databases, roles, and tablespaces,
@@ -368,6 +376,41 @@
    </para>
 
    <sect2>
+    <title>Extension Templates</title>
+
+   <indexterm>
+    <primary>template for extension</primary>
+   </indexterm>
+
+    <para>
+     The <xref linkend="sql-create-template-for-extension"> command allows a
+     superuser to create an <firstterm>Extension's Template</>, that users
+     will then be able to use as the source for their script and control
+     parameters. Upgrade scripts can be provided with the same command, and
+     those upgrade scripts can include parameters update too, as would a
+     secondary control file.
+    </para>
+
+    <para>
+     Here's a very simple example at using a template for an extension:
+<programlisting>
+create template for extension myextension version '1.0' with (nosuperuser) as
+$script$
+  create function identity(i int)
+    returns int
+    language sql
+  as $$
+    select $1;
+  $$;
+$script$;
+
+create extension myextension;
+</programlisting>
+    </para>
+
+   </sect2>
+
+   <sect2>
     <title>Extension Files</title>
 
    <indexterm>
@@ -429,6 +472,21 @@
      </varlistentry>
 
      <varlistentry>
+      <term><varname>default_full_version</varname> (<type>string</type>)</term>
+      <listitem>
+       <para>
+        This option allows an extension author to avoid shiping all versions
+        scripts when shipping an extension. When a version is requested and
+        the matching script does not exist on disk,
+        set <replaceable>default_full_version</replaceable> to the first
+        script you still ship and PostgreSQL will apply the intermediate
+        upgrade script as per the <command>ALTER EXTENSION UPDATE</command>
+        command.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><varname>comment</varname> (<type>string</type>)</term>
       <listitem>
        <para>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 5846974..c23cace 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -32,6 +32,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterSequence      SYSTEM "alter_sequence.sgml">
 <!ENTITY alterTable         SYSTEM "alter_table.sgml">
 <!ENTITY alterTableSpace    SYSTEM "alter_tablespace.sgml">
+<!ENTITY alterTemplateForExtension SYSTEM "alter_extension_template.sgml">
 <!ENTITY alterTSConfig      SYSTEM "alter_tsconfig.sgml">
 <!ENTITY alterTSDictionary  SYSTEM "alter_tsdictionary.sgml">
 <!ENTITY alterTSParser      SYSTEM "alter_tsparser.sgml">
@@ -76,6 +77,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createTable        SYSTEM "create_table.sgml">
 <!ENTITY createTableAs      SYSTEM "create_table_as.sgml">
 <!ENTITY createTableSpace   SYSTEM "create_tablespace.sgml">
+<!ENTITY createTemplateForExtension SYSTEM "create_extension_template.sgml">
 <!ENTITY createTrigger      SYSTEM "create_trigger.sgml">
 <!ENTITY createTSConfig     SYSTEM "create_tsconfig.sgml">
 <!ENTITY createTSDictionary SYSTEM "create_tsdictionary.sgml">
@@ -116,6 +118,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropServer         SYSTEM "drop_server.sgml">
 <!ENTITY dropTable          SYSTEM "drop_table.sgml">
 <!ENTITY dropTableSpace     SYSTEM "drop_tablespace.sgml">
+<!ENTITY dropTemplateForExtension SYSTEM "drop_extension_template.sgml">
 <!ENTITY dropTrigger        SYSTEM "drop_trigger.sgml">
 <!ENTITY dropTSConfig       SYSTEM "drop_tsconfig.sgml">
 <!ENTITY dropTSDictionary   SYSTEM "drop_tsdictionary.sgml">
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 14e217a..42ebf79 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -60,6 +60,7 @@
    &alterServer;
    &alterTable;
    &alterTableSpace;
+   &alterTemplateForExtension;
    &alterTSConfig;
    &alterTSDictionary;
    &alterTSParser;
@@ -104,6 +105,7 @@
    &createTable;
    &createTableAs;
    &createTableSpace;
+   &createTemplateForExtension;
    &createTSConfig;
    &createTSDictionary;
    &createTSParser;
@@ -144,6 +146,7 @@
    &dropServer;
    &dropTable;
    &dropTableSpace;
+   &dropTemplateForExtension;
    &dropTSConfig;
    &dropTSDictionary;
    &dropTSParser;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index c4d3f3c..689dc37 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -38,6 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
+	 pg_extension_control.h pg_extension_template.h pg_extension_uptmpl.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
 	pg_foreign_table.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 97bc93c..634126e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -33,6 +33,9 @@
 #include "catalog/pg_default_acl.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_extension.h"
+#include "catalog/pg_extension_control.h"
+#include "catalog/pg_extension_template.h"
+#include "catalog/pg_extension_uptmpl.h"
 #include "catalog/pg_foreign_data_wrapper.h"
 #include "catalog/pg_foreign_server.h"
 #include "catalog/pg_language.h"
@@ -5051,6 +5054,138 @@ pg_extension_ownercheck(Oid ext_oid, Oid roleid)
 }
 
 /*
+ * Ownership check for an extension control (specified by OID).
+ */
+bool
+pg_extension_control_ownercheck(Oid ext_control_oid, Oid roleid)
+{
+	Relation	pg_extension_control;
+	ScanKeyData entry[1];
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	Oid			ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	/* There's no syscache for pg_extension_control, so do it the hard way */
+	pg_extension_control =
+		heap_open(ExtensionControlRelationId, AccessShareLock);
+
+	ScanKeyInit(&entry[0],
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(ext_control_oid));
+
+	scan = systable_beginscan(pg_extension_control,
+							  ExtensionControlOidIndexId, true,
+							  NULL, 1, entry);
+
+	tuple = systable_getnext(scan);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("extension control with OID %u does not exist",
+						ext_control_oid)));
+
+	ownerId = ((Form_pg_extension_control) GETSTRUCT(tuple))->ctlowner;
+
+	systable_endscan(scan);
+	heap_close(pg_extension_control, AccessShareLock);
+
+	return has_privs_of_role(roleid, ownerId);
+}
+
+/*
+ * Ownership check for an extension template (specified by OID).
+ */
+bool
+pg_extension_template_ownercheck(Oid ext_template_oid, Oid roleid)
+{
+	Relation	pg_extension_template;
+	ScanKeyData entry[1];
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	Oid			ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	/* There's no syscache for pg_extension_template, so do it the hard way */
+	pg_extension_template =
+		heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+	ScanKeyInit(&entry[0],
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(ext_template_oid));
+
+	scan = systable_beginscan(pg_extension_template,
+							  ExtensionTemplateOidIndexId, true,
+							  NULL, 1, entry);
+
+	tuple = systable_getnext(scan);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("extension template with OID %u does not exist",
+						ext_template_oid)));
+
+	ownerId = ((Form_pg_extension_template) GETSTRUCT(tuple))->tplowner;
+
+	systable_endscan(scan);
+	heap_close(pg_extension_template, AccessShareLock);
+
+	return has_privs_of_role(roleid, ownerId);
+}
+
+/*
+ * Ownership check for an extension update template (specified by OID).
+ */
+bool
+pg_extension_uptmpl_ownercheck(Oid ext_uptmpl_oid, Oid roleid)
+{
+	Relation	pg_extension_uptmpl;
+	ScanKeyData entry[1];
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	Oid			ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	/* There's no syscache for pg_extension_uptmpl, so do it the hard way */
+	pg_extension_uptmpl =
+		heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+	ScanKeyInit(&entry[0],
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(ext_uptmpl_oid));
+
+	scan = systable_beginscan(pg_extension_uptmpl,
+							  ExtensionUpTmplOidIndexId, true,
+							  NULL, 1, entry);
+
+	tuple = systable_getnext(scan);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("extension template for update with OID %u does not exist",
+						ext_uptmpl_oid)));
+
+	ownerId = ((Form_pg_extension_uptmpl) GETSTRUCT(tuple))->uptowner;
+
+	systable_endscan(scan);
+	heap_close(pg_extension_uptmpl, AccessShareLock);
+
+	return has_privs_of_role(roleid, ownerId);
+}
+
+/*
  * Check whether specified role has CREATEROLE privilege (or is a superuser)
  *
  * Note: roles do not have owners per se; instead we use this test in
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index fe17c96..7bf9b03 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -35,6 +35,9 @@
 #include "catalog/pg_depend.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_extension.h"
+#include "catalog/pg_extension_control.h"
+#include "catalog/pg_extension_template.h"
+#include "catalog/pg_extension_uptmpl.h"
 #include "catalog/pg_foreign_data_wrapper.h"
 #include "catalog/pg_foreign_server.h"
 #include "catalog/pg_language.h"
@@ -60,6 +63,7 @@
 #include "commands/proclang.h"
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
+#include "commands/template.h"
 #include "commands/trigger.h"
 #include "commands/typecmds.h"
 #include "nodes/nodeFuncs.h"
@@ -1245,6 +1249,18 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_EXTENSION_CONTROL:
+			RemoveExtensionControlById(object->objectId);
+			break;
+
+		case OCLASS_EXTENSION_TEMPLATE:
+			RemoveExtensionTemplateById(object->objectId);
+			break;
+
+		case OCLASS_EXTENSION_UPTMPL:
+			RemoveExtensionUpTmplById(object->objectId);
+			break;
+
 		case OCLASS_EVENT_TRIGGER:
 			RemoveEventTriggerById(object->objectId);
 			break;
@@ -2306,6 +2322,15 @@ getObjectClass(const ObjectAddress *object)
 		case ExtensionRelationId:
 			return OCLASS_EXTENSION;
 
+		case ExtensionControlRelationId:
+			return OCLASS_EXTENSION_CONTROL;
+
+		case ExtensionTemplateRelationId:
+			return OCLASS_EXTENSION_TEMPLATE;
+
+		case ExtensionUpTmplRelationId:
+			return OCLASS_EXTENSION_UPTMPL;
+
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
 	}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 4d22f3a..3aafc0f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -32,6 +32,9 @@
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_extension.h"
+#include "catalog/pg_extension_control.h"
+#include "catalog/pg_extension_template.h"
+#include "catalog/pg_extension_uptmpl.h"
 #include "catalog/pg_foreign_data_wrapper.h"
 #include "catalog/pg_foreign_server.h"
 #include "catalog/pg_language.h"
@@ -57,6 +60,7 @@
 #include "commands/extension.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
+#include "commands/template.h"
 #include "commands/trigger.h"
 #include "foreign/foreign.h"
 #include "funcapi.h"
@@ -176,6 +180,39 @@ static ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		ExtensionControlRelationId,
+		ExtensionControlOidIndexId,
+		-1,
+		-1,
+		Anum_pg_extension_control_ctlname,
+		InvalidAttrNumber,		/* extension doesn't belong to extnamespace */
+		Anum_pg_extension_control_ctlowner,
+		InvalidAttrNumber,
+		ACL_KIND_EXTCONTROL
+	},
+	{
+		ExtensionTemplateRelationId,
+		ExtensionTemplateOidIndexId,
+		-1,
+		-1,
+		Anum_pg_extension_template_tplname,
+		InvalidAttrNumber,		/* extension doesn't belong to extnamespace */
+		Anum_pg_extension_template_tplowner,
+		InvalidAttrNumber,
+		ACL_KIND_EXTTEMPLATE
+	},
+	{
+		ExtensionUpTmplRelationId,
+		ExtensionUpTmplOidIndexId,
+		-1,
+		-1,
+		Anum_pg_extension_uptmpl_uptname,
+		InvalidAttrNumber,		/* extension doesn't belong to extnamespace */
+		Anum_pg_extension_uptmpl_uptowner,
+		InvalidAttrNumber,
+		ACL_KIND_EXTUPTMPL
+	},
+	{
 		ForeignDataWrapperRelationId,
 		ForeignDataWrapperOidIndexId,
 		FOREIGNDATAWRAPPEROID,
@@ -431,6 +468,8 @@ static ObjectAddress get_object_address_type(ObjectType objtype,
 						List *objname, bool missing_ok);
 static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname,
 						List *objargs, bool missing_ok);
+static ObjectAddress get_object_address_tmpl(ObjectType objtype,
+						List *objname, List *objargs, bool missing_ok);
 static ObjectPropertyType *get_object_property_data(Oid class_id);
 
 static void getRelationDescription(StringInfo buffer, Oid relid);
@@ -521,6 +560,11 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
+			case OBJECT_EXTENSION_TEMPLATE:
+			case OBJECT_EXTENSION_UPTMPL:
+				address = get_object_address_tmpl(objtype,
+												  objname, objargs, missing_ok);
+				break;
 			case OBJECT_TYPE:
 			case OBJECT_DOMAIN:
 				address = get_object_address_type(objtype, objname, missing_ok);
@@ -1115,6 +1159,82 @@ get_object_address_opcf(ObjectType objtype,
 }
 
 /*
+ * Find the ObjectAddress for an extension template, control or update
+ * template.
+ */
+static ObjectAddress
+get_object_address_tmpl(ObjectType objtype,
+						List *objname, List *objargs, bool missing_ok)
+{
+	const char *name;
+	ObjectAddress address;
+
+	/*
+	 * The types of names handled by this function are not permitted to be
+	 * schema-qualified or catalog-qualified.
+	 */
+	if (list_length(objname) != 1)
+	{
+		const char *msg;
+
+		switch (objtype)
+		{
+			case OBJECT_EXTENSION_TEMPLATE:
+				msg = gettext_noop("extension template name cannot be qualified");
+				break;
+			case OBJECT_EXTENSION_UPTMPL:
+				msg = gettext_noop("extension update template name cannot be qualified");
+				break;
+			default:
+				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+				msg = NULL;		/* placate compiler */
+		}
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("%s", _(msg))));
+	}
+
+	name = strVal(linitial(objname));
+
+	switch (objtype)
+	{
+		case OBJECT_EXTENSION_TEMPLATE:
+		{
+			const char *version;
+
+			Assert(list_length(objargs) == 1);
+			version = strVal(linitial(objargs));
+
+			address.classId = ExtensionTemplateRelationId;
+			address.objectId = get_template_oid(name, version, missing_ok);
+			address.objectSubId = 0;
+			break;
+		}
+		case OBJECT_EXTENSION_UPTMPL:
+		{
+			const char *from, *to;
+
+			Assert(list_length(objargs) == 2);
+
+			from = strVal(linitial(objargs));
+			to = strVal(lsecond(objargs));
+
+			address.classId = ExtensionUpTmplRelationId;
+			address.objectId = get_uptmpl_oid(name, from, to, missing_ok);
+			address.objectSubId = 0;
+			break;
+		}
+		default:
+			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+			/* placate compiler, which doesn't know elog won't return */
+			address.classId = InvalidOid;
+			address.objectId = InvalidOid;
+			address.objectSubId = 0;
+	}
+	return address;
+}
+
+/*
  * Check ownership of an object previously identified by get_object_address.
  */
 void
@@ -1179,6 +1299,16 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
 							   NameListToString(objname));
 			break;
+		case OBJECT_EXTENSION_TEMPLATE:
+			if (!pg_extension_ownercheck(address.objectId, roleid))
+				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTTEMPLATE,
+							   NameListToString(objname));
+			break;
+		case OBJECT_EXTENSION_UPTMPL:
+			if (!pg_extension_ownercheck(address.objectId, roleid))
+				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTUPTMPL,
+							   NameListToString(objname));
+			break;
 		case OBJECT_FDW:
 			if (!pg_foreign_data_wrapper_ownercheck(address.objectId, roleid))
 				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FDW,
@@ -2143,6 +2273,42 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_EXTENSION_CONTROL:
+			{
+				char	   *extname;
+
+				extname = get_extension_control_name(object->objectId);
+				if (!extname)
+					elog(ERROR, "cache lookup failed for control template for extension %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("control template for extension %s"), extname);
+				break;
+			}
+
+		case OCLASS_EXTENSION_TEMPLATE:
+			{
+				char	   *extname;
+
+				extname = get_extension_template_name(object->objectId);
+				if (!extname)
+					elog(ERROR, "cache lookup failed for create template for extension %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("create template for extension %s"), extname);
+				break;
+			}
+
+		case OCLASS_EXTENSION_UPTMPL:
+			{
+				char	   *extname;
+
+				extname = get_extension_uptmpl_name(object->objectId);
+				if (!extname)
+					elog(ERROR, "cache lookup failed for update template for extension %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("update template for extension %s"), extname);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..378aad1 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -19,7 +19,7 @@ OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
 	schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
-	tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
+	template.o tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
 	variable.o view.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index b62ec70..e3889dd 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -47,6 +47,7 @@
 #include "commands/schemacmds.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/template.h"
 #include "commands/trigger.h"
 #include "commands/typecmds.h"
 #include "commands/user.h"
@@ -147,11 +148,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
  * objectId: OID of object to be renamed
  * new_name: CString representation of new name
  */
-static void
+void
 AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 {
 	Oid			classId = RelationGetRelid(rel);
-	int			oidCacheId = get_object_catcache_oid(classId);
 	int			nameCacheId = get_object_catcache_name(classId);
 	AttrNumber	Anum_name = get_object_attnum_name(classId);
 	AttrNumber	Anum_namespace = get_object_attnum_namespace(classId);
@@ -170,8 +170,8 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 	bool	   *replaces;
 	NameData	nameattrdata;
 
-	oldtup = SearchSysCache1(oidCacheId, ObjectIdGetDatum(objectId));
-	if (!HeapTupleIsValid(oldtup))
+	oldtup = get_catalog_object_by_oid(rel, objectId);
+	if (oldtup == NULL)
 		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
 			 objectId, RelationGetRelationName(rel));
 
@@ -291,8 +291,6 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 	pfree(nulls);
 	pfree(replaces);
 	heap_freetuple(newtup);
-
-	ReleaseSysCache(oldtup);
 }
 
 /*
@@ -342,6 +340,9 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TYPE:
 			return RenameType(stmt);
 
+		case OBJECT_EXTENSION_TEMPLATE:
+			return AlterExtensionTemplateRename(stmt->subname, stmt->newname);
+
 		case OBJECT_AGGREGATE:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
@@ -701,7 +702,11 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 			return AlterEventTriggerOwner(strVal(linitial(stmt->object)),
 										  newowner);
 
-			/* Generic cases */
+		case OBJECT_EXTENSION_TEMPLATE:
+			return AlterExtensionTemplateOwner(strVal(linitial(stmt->object)),
+											   newowner);
+
+		/* Generic cases */
 		case OBJECT_AGGREGATE:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 328e2a8..4eb5ef8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -92,6 +92,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"SERVER", true},
 	{"TABLE", true},
 	{"TABLESPACE", false},
+	{"TEMPLATE FOR EXTENSION", true},
 	{"TRIGGER", true},
 	{"TEXT SEARCH CONFIGURATION", true},
 	{"TEXT SEARCH DICTIONARY", true},
@@ -922,6 +923,8 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CONVERSION:
 		case OBJECT_DOMAIN:
 		case OBJECT_EXTENSION:
+		case OBJECT_EXTENSION_TEMPLATE:
+		case OBJECT_EXTENSION_UPTMPL:
 		case OBJECT_FDW:
 		case OBJECT_FOREIGN_SERVER:
 		case OBJECT_FOREIGN_TABLE:
@@ -992,6 +995,9 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_USER_MAPPING:
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
+		case OCLASS_EXTENSION_CONTROL:
+		case OCLASS_EXTENSION_TEMPLATE:
+		case OCLASS_EXTENSION_UPTMPL:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 798c92a..5c3de1d 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -37,12 +37,14 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
+#include "catalog/pg_extension_control.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/comment.h"
 #include "commands/extension.h"
 #include "commands/schemacmds.h"
+#include "commands/template.h"
 #include "funcapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -61,23 +63,6 @@ bool		creating_extension = false;
 Oid			CurrentExtensionObject = InvalidOid;
 
 /*
- * Internal data structure to hold the results of parsing a control file
- */
-typedef struct ExtensionControlFile
-{
-	char	   *name;			/* name of the extension */
-	char	   *directory;		/* directory for script files */
-	char	   *default_version;	/* default install target version, if any */
-	char	   *module_pathname;	/* string to substitute for MODULE_PATHNAME */
-	char	   *comment;		/* comment, if any */
-	char	   *schema;			/* target schema (allowed if !relocatable) */
-	bool		relocatable;	/* is ALTER EXTENSION SET SCHEMA supported? */
-	bool		superuser;		/* must be superuser to install? */
-	int			encoding;		/* encoding of the script file, or -1 */
-	List	   *requires;		/* names of prerequisite extensions */
-} ExtensionControlFile;
-
-/*
  * Internal data structure for update path information
  */
 typedef struct ExtensionVersionInfo
@@ -96,11 +81,13 @@ static List *find_update_path(List *evi_list,
 				 ExtensionVersionInfo *evi_start,
 				 ExtensionVersionInfo *evi_target,
 				 bool reinitialize);
-static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
+static void get_available_versions_for_extension(ExtensionControl *pcontrol,
 									 Tuplestorestate *tupstore,
 									 TupleDesc tupdesc);
+static void get_available_versions_for_extension_templates(Tuplestorestate *tupstore,
+														   TupleDesc tupdesc);
 static void ApplyExtensionUpdates(Oid extensionOid,
-					  ExtensionControlFile *pcontrol,
+					  ExtensionControl *pcontrol,
 					  const char *initialVersion,
 					  List *updateVersions);
 
@@ -232,7 +219,7 @@ get_extension_schema(Oid ext_oid)
 /*
  * Utility functions to check validity of extension and version names
  */
-static void
+void
 check_valid_extension_name(const char *extensionname)
 {
 	int			namelen = strlen(extensionname);
@@ -355,7 +342,10 @@ get_extension_control_directory(void)
 	return result;
 }
 
-static char *
+/*
+ * We need that function in template.c
+ */
+char *
 get_extension_control_filename(const char *extname)
 {
 	char		sharepath[MAXPGPATH];
@@ -370,7 +360,7 @@ get_extension_control_filename(const char *extname)
 }
 
 static char *
-get_extension_script_directory(ExtensionControlFile *control)
+get_extension_script_directory(ExtensionControl *control)
 {
 	char		sharepath[MAXPGPATH];
 	char	   *result;
@@ -393,7 +383,7 @@ get_extension_script_directory(ExtensionControlFile *control)
 }
 
 static char *
-get_extension_aux_control_filename(ExtensionControlFile *control,
+get_extension_aux_control_filename(ExtensionControl *control,
 								   const char *version)
 {
 	char	   *result;
@@ -411,7 +401,7 @@ get_extension_aux_control_filename(ExtensionControlFile *control,
 }
 
 static char *
-get_extension_script_filename(ExtensionControlFile *control,
+get_extension_script_filename(ExtensionControl *control,
 							  const char *from_version, const char *version)
 {
 	char	   *result;
@@ -432,6 +422,19 @@ get_extension_script_filename(ExtensionControlFile *control,
 	return result;
 }
 
+/*
+ * An extension version is said to be "full" when it has a full install script,
+ * so that we know we don't need any update sequences dances either from
+ * "unpackaged" or from "default_major_version".
+ */
+static bool
+extension_version_is_full(ExtensionControl *control, const char *version)
+{
+	char *filename = get_extension_script_filename(control, NULL, version);
+
+	return access(filename, F_OK) == 0
+		|| OidIsValid(get_template_oid(control->name, version, true));
+}
 
 /*
  * Parse contents of primary or auxiliary control file, and fill in
@@ -443,7 +446,7 @@ get_extension_script_filename(ExtensionControlFile *control,
  * worry about what encoding it's in; all values are expected to be ASCII.
  */
 static void
-parse_extension_control_file(ExtensionControlFile *control,
+parse_extension_control_file(ExtensionControl *control,
 							 const char *version)
 {
 	char	   *filename;
@@ -483,7 +486,7 @@ parse_extension_control_file(ExtensionControlFile *control,
 	FreeFile(file);
 
 	/*
-	 * Convert the ConfigVariable list into ExtensionControlFile entries.
+	 * Convert the ConfigVariable list into ExtensionControl entries.
 	 */
 	for (item = head; item != NULL; item = item->next)
 	{
@@ -507,6 +510,10 @@ parse_extension_control_file(ExtensionControlFile *control,
 
 			control->default_version = pstrdup(item->value);
 		}
+		else if (strcmp(item->name, "default_full_version") == 0)
+		{
+			control->default_full_version = pstrdup(item->value);
+		}
 		else if (strcmp(item->name, "module_pathname") == 0)
 		{
 			control->module_pathname = pstrdup(item->value);
@@ -579,16 +586,18 @@ parse_extension_control_file(ExtensionControlFile *control,
 /*
  * Read the primary control file for the specified extension.
  */
-static ExtensionControlFile *
+ExtensionControl *
 read_extension_control_file(const char *extname)
 {
-	ExtensionControlFile *control;
+	ExtensionControl *control;
 
 	/*
 	 * Set up default values.  Pointer fields are initially null.
 	 */
-	control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
+	control = (ExtensionControl *) palloc0(sizeof(ExtensionControl));
+	control->ctrlOid = InvalidOid;
 	control->name = pstrdup(extname);
+	control->is_template = false;
 	control->relocatable = false;
 	control->superuser = true;
 	control->encoding = -1;
@@ -604,20 +613,20 @@ read_extension_control_file(const char *extname)
 /*
  * Read the auxiliary control file for the specified extension and version.
  *
- * Returns a new modified ExtensionControlFile struct; the original struct
+ * Returns a new modified ExtensionControl struct; the original struct
  * (reflecting just the primary control file) is not modified.
  */
-static ExtensionControlFile *
-read_extension_aux_control_file(const ExtensionControlFile *pcontrol,
+static ExtensionControl *
+read_extension_aux_control_file(const ExtensionControl *pcontrol,
 								const char *version)
 {
-	ExtensionControlFile *acontrol;
+	ExtensionControl *acontrol;
 
 	/*
 	 * Flat-copy the struct.  Pointer fields share values with original.
 	 */
-	acontrol = (ExtensionControlFile *) palloc(sizeof(ExtensionControlFile));
-	memcpy(acontrol, pcontrol, sizeof(ExtensionControlFile));
+	acontrol = (ExtensionControl *) palloc(sizeof(ExtensionControl));
+	memcpy(acontrol, pcontrol, sizeof(ExtensionControl));
 
 	/*
 	 * Parse the auxiliary control file, overwriting struct fields
@@ -628,10 +637,88 @@ read_extension_aux_control_file(const ExtensionControlFile *pcontrol,
 }
 
 /*
+ * Read the control properties for given extension, either from a file on the
+ * file system or if it does not exists there, from a template catalog in
+ * pg_extension_control, if it exists.
+ *
+ * In the file system case, we get the default properties for the extension and
+ * one of them is the default_version property that allows us to know which
+ * version to install. Knowing that we can then read the right auxilliary
+ * control file to override some defaults if needs be.
+ *
+ * When reading from the catalogs, we have in pg_extension_control at most a
+ * row per version, with the whole set of properties we need to apply. So once
+ * we found the current default version to install, we don't need to read and
+ * another set of properties and override them.
+ *
+ * In both cases we return the structure ExtensionControl.
+ */
+static ExtensionControl *
+read_extension_control(const char *extname)
+{
+	char *filename;
+
+	filename = get_extension_control_filename(extname);
+
+	if (access(filename, F_OK) == -1 && errno == ENOENT)
+	{
+		/* ENOENT: let's look at the control templates
+		 *
+		 * We pass in missing_ok as true so as to be able to offer a better
+		 * error message.
+		 */
+		ExtensionControl *c = find_default_pg_extension_control(extname, true);
+
+		if (c == NULL)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("Extension \"%s\" is not available from \"%s\" nor as a template",
+							extname, get_extension_control_directory())));
+			return NULL;
+		}
+		return c;
+	}
+	else
+		/* we let the file specific routines deal with any other error */
+		return read_extension_control_file(extname);
+}
+
+static ExtensionControl *
+read_extension_aux_control(const ExtensionControl *pcontrol,
+						   const char *version)
+{
+	if (pcontrol->is_template)
+	{
+		/* we might already have read the right version */
+		if (strcmp(pcontrol->default_version, version) != 0)
+		{
+			ExtensionControl *control;
+			/*
+			 * While read_extension_aux_control() override pcontrol with the
+			 * auxilliary control file properties, in the case when we read
+			 * from the catalogs, the overriding has been done already at
+			 * CREATE TEMPLATE time, so we only need to load a single row from
+			 * pg_extension_control at any time.
+			 */
+			control = find_pg_extension_control(pcontrol->name, version, true);
+
+			return control ? control : (ExtensionControl *)pcontrol;
+		}
+		else
+			/* pcontrol is the control file for the right version. */
+			return (ExtensionControl *)pcontrol;
+	}
+	else
+		/* read ExtensionControl from files */
+		return read_extension_aux_control_file(pcontrol, version);
+}
+
+/*
  * Read an SQL script file into a string, and convert to database encoding
  */
 static char *
-read_extension_script_file(const ExtensionControlFile *control,
+read_extension_script_file(const ExtensionControl *control,
 						   const char *filename)
 {
 	int			src_encoding;
@@ -674,8 +761,6 @@ read_extension_script_file(const ExtensionControlFile *control,
 /*
  * Execute given SQL string.
  *
- * filename is used only to report errors.
- *
  * Note: it's tempting to just use SPI to execute the string, but that does
  * not work very well.	The really serious problem is that SPI will parse,
  * analyze, and plan the whole string before executing any of it; of course
@@ -685,7 +770,7 @@ read_extension_script_file(const ExtensionControlFile *control,
  * could be very long.
  */
 static void
-execute_sql_string(const char *sql, const char *filename)
+execute_sql_string(const char *sql)
 {
 	List	   *raw_parsetree_list;
 	DestReceiver *dest;
@@ -770,13 +855,12 @@ execute_sql_string(const char *sql, const char *filename)
  * If from_version isn't NULL, it's an update
  */
 static void
-execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
+execute_extension_script(Oid extensionOid, ExtensionControl *control,
 						 const char *from_version,
 						 const char *version,
 						 List *requiredSchemas,
 						 const char *schemaName, Oid schemaOid)
 {
-	char	   *filename;
 	int			save_nestlevel;
 	StringInfoData pathbuf;
 	ListCell   *lc;
@@ -802,8 +886,6 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 					 errhint("Must be superuser to update this extension.")));
 	}
 
-	filename = get_extension_script_filename(control, from_version, version);
-
 	/*
 	 * Force client_min_messages and log_min_messages to be at least WARNING,
 	 * so that we won't spam the user with useless NOTICE messages from common
@@ -858,8 +940,23 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 	CurrentExtensionObject = extensionOid;
 	PG_TRY();
 	{
-		char	   *c_sql = read_extension_script_file(control, filename);
-		Datum		t_sql;
+		char	*c_sql;
+		Datum	 t_sql;
+
+		if (control->is_template)
+		{
+			c_sql = read_extension_template_script(control->name,
+												   from_version,
+												   version);
+		}
+		else
+		{
+			char *filename = get_extension_script_filename(control,
+														   from_version,
+														   version);
+
+			c_sql = read_extension_script_file(control, filename);
+		}
 
 		/* We use various functions that want to operate on text datums */
 		t_sql = CStringGetTextDatum(c_sql);
@@ -908,7 +1005,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		/* And now back to C string */
 		c_sql = text_to_cstring(DatumGetTextPP(t_sql));
 
-		execute_sql_string(c_sql, filename);
+		execute_sql_string(c_sql);
 	}
 	PG_CATCH();
 	{
@@ -997,7 +1094,7 @@ get_nearest_unprocessed_vertex(List *evi_list)
  * the versions that can be reached in one step from that version.
  */
 static List *
-get_ext_ver_list(ExtensionControlFile *control)
+get_ext_ver_list_from_files(ExtensionControl *control)
 {
 	List	   *evi_list = NIL;
 	int			extnamelen = strlen(control->name);
@@ -1053,6 +1150,59 @@ get_ext_ver_list(ExtensionControlFile *control)
 }
 
 /*
+ * We scan pg_extension_template for all install scripts of given extension,
+ * then pg_extension_uptmpl for all update scripts of same extension.
+ */
+static List *
+get_ext_ver_list_from_catalog(ExtensionControl *control)
+{
+	List		*evi_list = NIL;
+	List		*installable, *direct_update_paths;
+	ListCell    *lc;
+
+	/* pg_extension_template contains install scripts */
+	installable = list_pg_extension_template_versions(control->name);
+
+	foreach(lc, installable)
+	{
+		ExtensionVersionInfo	*evi;
+		char					*vername = (char *) lfirst(lc);
+
+		evi = get_ext_ver_info(vername, &evi_list);
+		(void) evi; 			/* silence a compiler warning */
+	}
+
+	/* pg_extension_uptmpl contains upgrade scripts */
+	direct_update_paths = list_pg_extension_update_versions(control->name);
+
+	foreach(lc, direct_update_paths)
+	{
+		ExtensionVersionInfo	*evi, *evi2;
+		char					*vername = (char *) linitial(lfirst(lc));
+		char					*vername2 = (char *) lsecond(lfirst(lc));
+
+		evi = get_ext_ver_info(vername, &evi_list);
+		evi2 = get_ext_ver_info(vername2, &evi_list);
+		evi->reachable = lappend(evi->reachable, evi2);
+	}
+	return evi_list;
+}
+
+/*
+ * We have to implement that function twice. The first implementation deals
+ * with control files and sql scripts on the file system while the second one
+ * deals with the catalogs pg_extension_template and pg_extension_uptmpl.
+ */
+static List *
+get_ext_ver_list(ExtensionControl *control)
+{
+	if (control->is_template)
+		return get_ext_ver_list_from_catalog(control);
+	else
+		return get_ext_ver_list_from_files(control);
+}
+
+/*
  * Given an initial and final version name, identify the sequence of update
  * scripts that have to be applied to perform that update.
  *
@@ -1060,7 +1210,7 @@ get_ext_ver_list(ExtensionControlFile *control)
  * version is *not* included).
  */
 static List *
-identify_update_path(ExtensionControlFile *control,
+identify_update_path(ExtensionControl *control,
 					 const char *oldVersion, const char *newVersion)
 {
 	List	   *result;
@@ -1172,6 +1322,28 @@ find_update_path(List *evi_list,
 }
 
 /*
+ * Add a dependency from the extension on a given pg_extension_control entry.
+ */
+static void
+record_control_dependency(Oid extensionOid, Oid ctrlOid)
+{
+	if (OidIsValid(ctrlOid))
+	{
+		ObjectAddress myself, pg_extension_control;
+
+		myself.classId = ExtensionRelationId;
+		myself.objectId = extensionOid;
+		myself.objectSubId = 0;
+
+		pg_extension_control.classId = ExtensionControlRelationId;
+		pg_extension_control.objectId = ctrlOid;
+		pg_extension_control.objectSubId = 0;
+
+		recordDependencyOn(&myself, &pg_extension_control, DEPENDENCY_NORMAL);
+	}
+}
+
+/*
  * CREATE EXTENSION
  */
 Oid
@@ -1185,13 +1357,14 @@ CreateExtension(CreateExtensionStmt *stmt)
 	char	   *versionName;
 	char	   *oldVersionName;
 	Oid			extowner = GetUserId();
-	ExtensionControlFile *pcontrol;
-	ExtensionControlFile *control;
+	ExtensionControl *pcontrol;
+	ExtensionControl *control;
 	List	   *updateVersions;
 	List	   *requiredExtensions;
 	List	   *requiredSchemas;
 	Oid			extensionOid;
 	ListCell   *lc;
+	bool        unpackaged = false, target_version_is_full = false;
 
 	/* Check extension name validity before any filesystem access */
 	check_valid_extension_name(stmt->extname);
@@ -1233,7 +1406,7 @@ CreateExtension(CreateExtensionStmt *stmt)
 	 * any non-ASCII data, so there is no need to worry about encoding at this
 	 * point.
 	 */
-	pcontrol = read_extension_control_file(stmt->extname);
+	pcontrol = read_extension_control(stmt->extname);
 
 	/*
 	 * Read the statement option list
@@ -1272,6 +1445,11 @@ CreateExtension(CreateExtensionStmt *stmt)
 
 	/*
 	 * Determine the version to install
+	 *
+	 * Note that in the case when we install an extension from a template, and
+	 * when the target version to install is given in the SQL command, we could
+	 * arrange the code to only scan pg_extension_control once: there's no need
+	 * to read any primary control row in that case. There's no harm doing so.
 	 */
 	if (d_new_version && d_new_version->arg)
 		versionName = strVal(d_new_version->arg);
@@ -1287,42 +1465,88 @@ CreateExtension(CreateExtensionStmt *stmt)
 	check_valid_version_name(versionName);
 
 	/*
+	 * If we have a full script for the target version (or a create template),
+	 * we don't need to care about unpackaged or default_major_version, nor
+	 * about upgrade sequences.
+	 */
+	if (extension_version_is_full(pcontrol, versionName))
+	{
+		target_version_is_full = true;
+		oldVersionName = NULL;
+		updateVersions = NIL;
+	}
+	/*
 	 * Determine the (unpackaged) version to update from, if any, and then
 	 * figure out what sequence of update scripts we need to apply.
+	 *
+	 * When we have a default_full_version and the target is different from it,
+	 * apply the same algorithm to find a sequence of updates. If the user did
+	 * ask for a target version that happens to be the same as the
+	 * default_full_version, just install that one directly.
 	 */
-	if (d_old_version && d_old_version->arg)
+	else if ((d_old_version && d_old_version->arg) || pcontrol->default_full_version)
 	{
-		oldVersionName = strVal(d_old_version->arg);
-		check_valid_version_name(oldVersionName);
+		unpackaged = (d_old_version && d_old_version->arg);
 
-		if (strcmp(oldVersionName, versionName) == 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("FROM version must be different from installation target version \"%s\"",
-							versionName)));
+		if (unpackaged)
+			oldVersionName = strVal(d_old_version->arg);
+		else
+			oldVersionName = pcontrol->default_full_version;
 
-		updateVersions = identify_update_path(pcontrol,
-											  oldVersionName,
-											  versionName);
+		check_valid_version_name(oldVersionName);
 
-		if (list_length(updateVersions) == 1)
+		if (strcmp(oldVersionName, versionName) == 0)
 		{
-			/*
-			 * Simple case where there's just one update script to run. We
-			 * will not need any follow-on update steps.
-			 */
-			Assert(strcmp((char *) linitial(updateVersions), versionName) == 0);
-			updateVersions = NIL;
+			if (unpackaged)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("FROM version must be different from installation target version \"%s\"",
+								versionName)));
+			}
+			else
+			{
+				/*
+				 * CREATE EXTENSION ... VERSION = default_full_version, just
+				 * pretend we don't have a default_full_version for the
+				 * remaining of the code here, as that's the behavior we want
+				 * to see happening.
+				 */
+				pcontrol->default_full_version = NULL;
+				oldVersionName = NULL;
+				updateVersions = NIL;
+			}
 		}
 		else
 		{
-			/*
-			 * Multi-step sequence.  We treat this as installing the version
-			 * that is the target of the first script, followed by successive
-			 * updates to the later versions.
-			 */
-			versionName = (char *) linitial(updateVersions);
-			updateVersions = list_delete_first(updateVersions);
+			/* oldVersionName != versionName */
+			updateVersions = identify_update_path(pcontrol,
+												  oldVersionName,
+												  versionName);
+		}
+
+		/* in the create from unpackaged case, reduce the update list */
+		if (unpackaged)
+		{
+			if (list_length(updateVersions) == 1)
+			{
+				/*
+				 * Simple case where there's just one update script to run. We
+				 * will not need any follow-on update steps.
+				 */
+				Assert(strcmp((char *) linitial(updateVersions), versionName) == 0);
+				updateVersions = NIL;
+			}
+			else
+			{
+				/*
+				 * Multi-step sequence.  We treat this as installing the version
+				 * that is the target of the first script, followed by successive
+				 * updates to the later versions.
+				 */
+				versionName = (char *) linitial(updateVersions);
+				updateVersions = list_delete_first(updateVersions);
+			}
 		}
 	}
 	else
@@ -1334,7 +1558,7 @@ CreateExtension(CreateExtensionStmt *stmt)
 	/*
 	 * Fetch control parameters for installation target version
 	 */
-	control = read_extension_aux_control_file(pcontrol, versionName);
+	control = read_extension_aux_control(pcontrol, versionName);
 
 	/*
 	 * Determine the target schema to install the extension into
@@ -1452,7 +1676,8 @@ CreateExtension(CreateExtensionStmt *stmt)
 										versionName,
 										PointerGetDatum(NULL),
 										PointerGetDatum(NULL),
-										requiredExtensions);
+										requiredExtensions,
+										control->ctrlOid);
 
 	/*
 	 * Apply any control-file comment on extension
@@ -1462,19 +1687,56 @@ CreateExtension(CreateExtensionStmt *stmt)
 
 	/*
 	 * Execute the installation script file
-	 */
-	execute_extension_script(extensionOid, control,
-							 oldVersionName, versionName,
-							 requiredSchemas,
-							 schemaName, schemaOid);
-
-	/*
+	 *
 	 * If additional update scripts have to be executed, apply the updates as
 	 * though a series of ALTER EXTENSION UPDATE commands were given
 	 */
-	ApplyExtensionUpdates(extensionOid, pcontrol,
-						  versionName, updateVersions);
+	if (target_version_is_full)
+	{
+		/* InsertExtensionTuple set the template control dependency */
+		execute_extension_script(extensionOid, control,
+								 NULL, versionName,
+								 requiredSchemas,
+								 schemaName, schemaOid);
+	}
+	else if (pcontrol->default_full_version && !unpackaged)
+	{
+		/* Set the pg_extension_control dependency, when relevant. */
+		if (pcontrol->is_template)
+		{
+			ExtensionControl *old =
+				find_pg_extension_control(stmt->extname,
+										  pcontrol->default_full_version,
+										  false);
 
+			record_control_dependency(extensionOid, old->ctrlOid);
+		}
+		execute_extension_script(extensionOid, control,
+								 NULL, oldVersionName,
+								 requiredSchemas,
+								 schemaName, schemaOid);
+
+		ApplyExtensionUpdates(extensionOid, pcontrol,
+							  oldVersionName, updateVersions);
+	}
+	else
+	{
+		/* Set the pg_extension_control dependency, when relevant. */
+		if (pcontrol->is_template && oldVersionName)
+		{
+			ExtensionControl *old =
+				find_pg_extension_control(stmt->extname, oldVersionName, false);
+
+			record_control_dependency(extensionOid, old->ctrlOid);
+		}
+		execute_extension_script(extensionOid, control,
+								 oldVersionName, versionName,
+								 requiredSchemas,
+								 schemaName, schemaOid);
+
+		ApplyExtensionUpdates(extensionOid, pcontrol,
+							  versionName, updateVersions);
+	}
 	return extensionOid;
 }
 
@@ -1495,7 +1757,7 @@ Oid
 InsertExtensionTuple(const char *extName, Oid extOwner,
 					 Oid schemaOid, bool relocatable, const char *extVersion,
 					 Datum extConfig, Datum extCondition,
-					 List *requiredExtensions)
+					 List *requiredExtensions, Oid ctrlOid)
 {
 	Oid			extensionOid;
 	Relation	rel;
@@ -1565,6 +1827,10 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 
 		recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
 	}
+
+	/* Record dependency on pg_extension_control, if created from a template */
+	record_control_dependency(extensionOid, ctrlOid);
+
 	/* Post creation hook for new extension */
 	InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
 
@@ -1635,13 +1901,15 @@ Datum
 pg_available_extensions(PG_FUNCTION_ARGS)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	MemoryContext per_query_ctx;
-	MemoryContext oldcontext;
-	char	   *location;
-	DIR		   *dir;
-	struct dirent *de;
+	TupleDesc			 tupdesc;
+	Tuplestorestate		*tupstore;
+	MemoryContext		 per_query_ctx;
+	MemoryContext		 oldcontext;
+	char				*location;
+	DIR					*dir;
+	struct dirent		*de;
+	List				*templates;
+	ListCell			*lc;
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -1684,7 +1952,7 @@ pg_available_extensions(PG_FUNCTION_ARGS)
 	{
 		while ((de = ReadDir(dir, location)) != NULL)
 		{
-			ExtensionControlFile *control;
+			ExtensionControl *control;
 			char	   *extname;
 			Datum		values[3];
 			bool		nulls[3];
@@ -1725,6 +1993,33 @@ pg_available_extensions(PG_FUNCTION_ARGS)
 		FreeDir(dir);
 	}
 
+    /* add in the extension we can install from a template */
+	templates = pg_extension_default_controls();
+
+    foreach(lc, templates)
+	{
+		char	*name = (char *)linitial(lfirst(lc));
+		char	*vers = (char *)lsecond(lfirst(lc));
+		char    *comm = (char *)lthird(lfirst(lc));
+		Datum	 values[3];
+		bool	 nulls[3];
+
+		memset(values, 0, sizeof(values));
+		memset(nulls, 0, sizeof(nulls));
+
+		/* name */
+		values[0] = DirectFunctionCall1(namein, CStringGetDatum(name));
+		/* default_version */
+		values[1] = CStringGetTextDatum(vers);
+		/* comment */
+		if (comm)
+			values[2] = CStringGetTextDatum(comm);
+		else
+			nulls[2] = true;
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
 	/* clean up and return the tuplestore */
 	tuplestore_donestoring(tupstore);
 
@@ -1793,7 +2088,7 @@ pg_available_extension_versions(PG_FUNCTION_ARGS)
 	{
 		while ((de = ReadDir(dir, location)) != NULL)
 		{
-			ExtensionControlFile *control;
+			ExtensionControl *control;
 			char	   *extname;
 
 			if (!is_extension_control_filename(de->d_name))
@@ -1817,6 +2112,8 @@ pg_available_extension_versions(PG_FUNCTION_ARGS)
 		FreeDir(dir);
 	}
 
+	get_available_versions_for_extension_templates(tupstore, tupdesc);
+
 	/* clean up and return the tuplestore */
 	tuplestore_donestoring(tupstore);
 
@@ -1824,11 +2121,74 @@ pg_available_extension_versions(PG_FUNCTION_ARGS)
 }
 
 /*
+ * Helper function to fill in the pg_available_extension_versions tuplestore.
+ */
+static void
+tuplestore_put_extension_control(ExtensionControl *control,
+								 Tuplestorestate *tupstore,
+								 TupleDesc tupdesc)
+{
+	Datum		values[7];
+	bool		nulls[7];
+
+	memset(values, 0, sizeof(values));
+	memset(nulls, 0, sizeof(nulls));
+
+	/* name */
+	values[0] = DirectFunctionCall1(namein,
+									CStringGetDatum(control->name));
+	/* version */
+	values[1] = CStringGetTextDatum(control->version);
+	/* superuser */
+	values[2] = BoolGetDatum(control->superuser);
+	/* relocatable */
+	values[3] = BoolGetDatum(control->relocatable);
+	/* schema */
+	if (control->schema == NULL)
+		nulls[4] = true;
+	else
+		values[4] = DirectFunctionCall1(namein,
+										CStringGetDatum(control->schema));
+	/* requires */
+	if (control->requires == NIL)
+		nulls[5] = true;
+	else
+	{
+		Datum	   *datums;
+		int			ndatums;
+		ArrayType  *a;
+		ListCell   *lc;
+
+		ndatums = list_length(control->requires);
+		datums = (Datum *) palloc(ndatums * sizeof(Datum));
+		ndatums = 0;
+		foreach(lc, control->requires)
+		{
+			char	   *curreq = (char *) lfirst(lc);
+
+			datums[ndatums++] =
+				DirectFunctionCall1(namein, CStringGetDatum(curreq));
+		}
+		a = construct_array(datums, ndatums,
+							NAMEOID,
+							NAMEDATALEN, false, 'c');
+		values[5] = PointerGetDatum(a);
+	}
+	/* comment */
+	if (control->comment == NULL)
+		nulls[6] = true;
+	else
+		values[6] = CStringGetTextDatum(control->comment);
+
+	tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+}
+
+/*
  * Inner loop for pg_available_extension_versions:
  *		read versions of one extension, add rows to tupstore
  */
 static void
-get_available_versions_for_extension(ExtensionControlFile *pcontrol,
+get_available_versions_for_extension(ExtensionControl *pcontrol,
 									 Tuplestorestate *tupstore,
 									 TupleDesc tupdesc)
 {
@@ -1842,10 +2202,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 	/* Note this will fail if script directory doesn't exist */
 	while ((de = ReadDir(dir, location)) != NULL)
 	{
-		ExtensionControlFile *control;
+		ExtensionControl *control;
 		char	   *vername;
-		Datum		values[7];
-		bool		nulls[7];
 
 		/* must be a .sql file ... */
 		if (!is_extension_script_filename(de->d_name))
@@ -1869,60 +2227,37 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 		 * Fetch parameters for specific version (pcontrol is not changed)
 		 */
 		control = read_extension_aux_control_file(pcontrol, vername);
+		control->version = pstrdup(vername);
 
-		memset(values, 0, sizeof(values));
-		memset(nulls, 0, sizeof(nulls));
+		tuplestore_put_extension_control(control, tupstore, tupdesc);
+	}
 
-		/* name */
-		values[0] = DirectFunctionCall1(namein,
-										CStringGetDatum(control->name));
-		/* version */
-		values[1] = CStringGetTextDatum(vername);
-		/* superuser */
-		values[2] = BoolGetDatum(control->superuser);
-		/* relocatable */
-		values[3] = BoolGetDatum(control->relocatable);
-		/* schema */
-		if (control->schema == NULL)
-			nulls[4] = true;
-		else
-			values[4] = DirectFunctionCall1(namein,
-											CStringGetDatum(control->schema));
-		/* requires */
-		if (control->requires == NIL)
-			nulls[5] = true;
-		else
-		{
-			Datum	   *datums;
-			int			ndatums;
-			ArrayType  *a;
-			ListCell   *lc;
-
-			ndatums = list_length(control->requires);
-			datums = (Datum *) palloc(ndatums * sizeof(Datum));
-			ndatums = 0;
-			foreach(lc, control->requires)
-			{
-				char	   *curreq = (char *) lfirst(lc);
+	FreeDir(dir);
+}
 
-				datums[ndatums++] =
-					DirectFunctionCall1(namein, CStringGetDatum(curreq));
-			}
-			a = construct_array(datums, ndatums,
-								NAMEOID,
-								NAMEDATALEN, false, 'c');
-			values[5] = PointerGetDatum(a);
-		}
-		/* comment */
-		if (control->comment == NULL)
-			nulls[6] = true;
-		else
-			values[6] = CStringGetTextDatum(control->comment);
+/*
+ * Inner loop for pg_available_extension_versions:
+ *		read versions of one extension from templates, add rows to tupstore
+ */
+static void
+get_available_versions_for_extension_templates(Tuplestorestate *tupstore,
+											   TupleDesc tupdesc)
+{
+	List				*controls;
+	ListCell			*lc;
 
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
+	controls = pg_extension_controls();
 
-	FreeDir(dir);
+	foreach(lc, controls)
+	{
+		ExtensionControl *control = (ExtensionControl *)lfirst(lc);
+
+		/* add-in the comment */
+		control->comment = GetComment(control->ctrlOid,
+									  ExtensionControlRelationId, 0);
+
+		tuplestore_put_extension_control(control, tupstore, tupdesc);
+	}
 }
 
 /*
@@ -1939,7 +2274,7 @@ pg_extension_update_paths(PG_FUNCTION_ARGS)
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	List	   *evi_list;
-	ExtensionControlFile *control;
+	ExtensionControl *control;
 	ListCell   *lc1;
 
 	/* Check extension name validity before any filesystem access */
@@ -1972,7 +2307,7 @@ pg_extension_update_paths(PG_FUNCTION_ARGS)
 	MemoryContextSwitchTo(oldcontext);
 
 	/* Read the extension's control file */
-	control = read_extension_control_file(NameStr(*extname));
+	control = read_extension_control(NameStr(*extname));
 
 	/* Extract the version update graph from the script directory */
 	evi_list = get_ext_ver_list(control);
@@ -2591,7 +2926,7 @@ ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
 	DefElem    *d_new_version = NULL;
 	char	   *versionName;
 	char	   *oldVersionName;
-	ExtensionControlFile *control;
+	ExtensionControl *control;
 	Oid			extensionOid;
 	Relation	extRel;
 	ScanKeyData key[1];
@@ -2657,7 +2992,7 @@ ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
 	 * any non-ASCII data, so there is no need to worry about encoding at this
 	 * point.
 	 */
-	control = read_extension_control_file(stmt->extname);
+	control = read_extension_control(stmt->extname);
 
 	/*
 	 * Read the statement option list
@@ -2732,7 +3067,7 @@ ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
  */
 static void
 ApplyExtensionUpdates(Oid extensionOid,
-					  ExtensionControlFile *pcontrol,
+					  ExtensionControl *pcontrol,
 					  const char *initialVersion,
 					  List *updateVersions)
 {
@@ -2742,7 +3077,7 @@ ApplyExtensionUpdates(Oid extensionOid,
 	foreach(lcv, updateVersions)
 	{
 		char	   *versionName = (char *) lfirst(lcv);
-		ExtensionControlFile *control;
+		ExtensionControl *control;
 		char	   *schemaName;
 		Oid			schemaOid;
 		List	   *requiredExtensions;
@@ -2761,7 +3096,7 @@ ApplyExtensionUpdates(Oid extensionOid,
 		/*
 		 * Fetch parameters for specific version (pcontrol is not changed)
 		 */
-		control = read_extension_aux_control_file(pcontrol, versionName);
+		control = read_extension_aux_control(pcontrol, versionName);
 
 		/* Find the pg_extension tuple */
 		extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
@@ -2862,6 +3197,9 @@ ApplyExtensionUpdates(Oid extensionOid,
 			recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
 		}
 
+		/* Record dependency on pg_extension_control, if created from a template */
+		record_control_dependency(extensionOid, control->ctrlOid);
+
 		InvokeObjectPostAlterHook(ExtensionRelationId, extensionOid, 0);
 
 		/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 22e82ba..662b0ec 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -219,7 +219,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
 		AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
 		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
-		AlterRoleStmt AlterRoleSetStmt
+		AlterRoleStmt AlterRoleSetStmt AlterExtTemplateStmt
 		AlterDefaultPrivilegesStmt DefACLAction
 		AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
@@ -228,11 +228,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
 		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
-		CreateUserStmt CreateUserMappingStmt CreateRoleStmt
+		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreateExtTemplateStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
 		DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
 		DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
-		DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
+		DropTemplateStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
 		DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
 		GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
 		LockStmt NotifyStmt ExplainableStmt PreparableStmt
@@ -266,6 +266,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				transaction_mode_item
 				create_extension_opt_item alter_extension_opt_item
 
+%type <list>    create_template_control create_template_control_plist
+%type <defelt>  create_template_control_item
+
 %type <ival>	opt_lock lock_type cast_context
 %type <ival>	vacuum_option_list vacuum_option_elem
 %type <boolean>	opt_force opt_or_replace
@@ -723,6 +726,7 @@ stmt :
 			| AlterCompositeTypeStmt
 			| AlterRoleSetStmt
 			| AlterRoleStmt
+			| AlterExtTemplateStmt
 			| AlterTSConfigurationStmt
 			| AlterTSDictionaryStmt
 			| AlterUserMappingStmt
@@ -761,6 +765,7 @@ stmt :
 			| CreateUserStmt
 			| CreateUserMappingStmt
 			| CreatedbStmt
+			| CreateExtTemplateStmt
 			| DeallocateStmt
 			| DeclareCursorStmt
 			| DefineStmt
@@ -784,6 +789,7 @@ stmt :
 			| DropUserStmt
 			| DropUserMappingStmt
 			| DropdbStmt
+			| DropTemplateStmt
 			| ExecuteStmt
 			| ExplainStmt
 			| FetchStmt
@@ -3571,6 +3577,263 @@ DropTableSpaceStmt: DROP TABLESPACE name
 /*****************************************************************************
  *
  *		QUERY:
+ *             CREATE TEMPLATE FOR EXTENSION name
+ *
+ *****************************************************************************/
+
+CreateExtTemplateStmt:
+            CREATE TEMPLATE FOR EXTENSION name VERSION_P NonReservedWord_or_Sconst
+            AS Sconst
+				{
+					CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+					n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+					n->extname = $5;
+					n->version = $7;
+					n->control = NIL;
+					n->script = $9;
+					n->if_not_exists = false;
+					n->default_version = false;
+					$$ = (Node *) n;
+				}
+          | CREATE TEMPLATE FOR EXTENSION name VERSION_P NonReservedWord_or_Sconst
+            WITH create_template_control AS Sconst
+				{
+					CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+					n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+					n->extname = $5;
+					n->version = $7;
+					n->control = $9;
+					n->script = $11;
+					n->if_not_exists = false;
+					n->default_version = false;
+					$$ = (Node *) n;
+				}
+            | CREATE TEMPLATE FOR EXTENSION name
+			  DEFAULT VERSION_P NonReservedWord_or_Sconst AS Sconst
+				{
+					CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+					n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+					n->extname = $5;
+					n->version = $8;
+					n->control = NIL;
+					n->script = $10;
+					n->if_not_exists = false;
+					n->default_version = true;
+					$$ = (Node *) n;
+				}
+            | CREATE TEMPLATE FOR EXTENSION name
+			  DEFAULT VERSION_P NonReservedWord_or_Sconst
+              WITH create_template_control AS Sconst
+				{
+					CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+					n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+					n->extname = $5;
+					n->version = $8;
+					n->control = $10;
+					n->script = $12;
+					n->if_not_exists = false;
+					n->default_version = true;
+					$$ = (Node *) n;
+				}
+            | CREATE TEMPLATE FOR EXTENSION name FOR UPDATE
+              FROM NonReservedWord_or_Sconst TO NonReservedWord_or_Sconst AS Sconst
+				{
+					CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+					n->tmpltype = TEMPLATE_UPDATE_EXTENSION;
+					n->extname = $5;
+					n->from = $9;
+					n->to = $11;
+					n->control = NIL;
+					n->script = $13;
+					n->if_not_exists = false;
+					$$ = (Node *) n;
+				}
+            | CREATE TEMPLATE FOR EXTENSION name FOR UPDATE
+              FROM NonReservedWord_or_Sconst TO NonReservedWord_or_Sconst
+              WITH create_template_control AS Sconst
+				{
+					CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+					n->tmpltype = TEMPLATE_UPDATE_EXTENSION;
+					n->extname = $5;
+					n->from = $9;
+					n->to = $11;
+					n->control = $13;
+					n->script = $15;
+					n->if_not_exists = false;
+					$$ = (Node *) n;
+				}
+		;
+
+/*****************************************************************************
+ *
+ *		QUERY:
+ *             ALTER TEMPLATE FOR EXTENSION name
+ *
+ * We only allow a single subcommand per command here.
+ *
+ *****************************************************************************/
+AlterExtTemplateStmt:
+			ALTER TEMPLATE FOR EXTENSION name
+			SET DEFAULT VERSION_P NonReservedWord_or_Sconst
+				{
+					AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+					n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+					n->cmdtype = AET_SET_DEFAULT;
+					n->extname = $5;
+					n->version = $9;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| ALTER TEMPLATE FOR EXTENSION name
+			  SET DEFAULT FULL VERSION_P NonReservedWord_or_Sconst
+				{
+					AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+					n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+					n->cmdtype = AET_SET_DEFAULT_FULL;
+					n->extname = $5;
+					n->version = $10;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| ALTER TEMPLATE FOR EXTENSION name VERSION_P NonReservedWord_or_Sconst
+			  AS Sconst
+				{
+					AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+					n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+					n->cmdtype = AET_SET_SCRIPT;
+					n->extname = $5;
+					n->version = $7;
+					n->script = $9;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| ALTER TEMPLATE FOR EXTENSION name VERSION_P NonReservedWord_or_Sconst
+			  WITH create_template_control
+				{
+					AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+					n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+					n->cmdtype = AET_UPDATE_CONTROL;
+					n->extname = $5;
+					n->version = $7;
+					n->control = $9;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+			| ALTER TEMPLATE FOR EXTENSION name FOR UPDATE
+			  FROM NonReservedWord_or_Sconst TO NonReservedWord_or_Sconst
+			  AS Sconst
+				{
+					AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+					n->tmpltype = TEMPLATE_UPDATE_EXTENSION;
+					n->cmdtype = AET_SET_SCRIPT;
+					n->extname = $5;
+					n->from = $9;
+					n->to = $11;
+					n->script = $13;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+		;
+
+/*****************************************************************************
+ *
+ *		QUERY:
+ *             DROP TEMPLATE FOR EXTENSION name
+ *
+ *****************************************************************************/
+DropTemplateStmt:
+			DROP TEMPLATE FOR EXTENSION name
+			VERSION_P NonReservedWord_or_Sconst opt_drop_behavior
+				{
+					DropStmt *n = makeNode(DropStmt);
+					n->removeType = OBJECT_EXTENSION_TEMPLATE;
+					n->objects = list_make1(list_make1(makeString($5)));
+					n->arguments = list_make1(list_make1(makeString($7)));
+					n->behavior = $8;
+					n->missing_ok = false;
+					n->concurrent = false;
+					$$ = (Node *)n;
+				}
+			| DROP TEMPLATE FOR EXTENSION name
+			  FOR UPDATE FROM NonReservedWord_or_Sconst
+              TO NonReservedWord_or_Sconst opt_drop_behavior
+				{
+					DropStmt *n = makeNode(DropStmt);
+					n->removeType = OBJECT_EXTENSION_UPTMPL;
+					n->objects = list_make1(list_make1(makeString($5)));
+					n->arguments = list_make1(list_make2(makeString($9),
+														 makeString($11)));
+					n->behavior = $12;
+					n->missing_ok = false;
+					n->concurrent = false;
+					$$ = (Node *)n;
+				}
+		;
+
+create_template_control:
+			'(' create_template_control_plist ')'		{ $$ = $2; }
+		;
+
+
+create_template_control_plist:
+			create_template_control_item
+				{ $$ = list_make1($1); }
+			| create_template_control_plist ',' create_template_control_item
+				{ $$ = lappend($1, $3); }
+			| /* EMPTY */
+				{ $$ = NIL; }
+		;
+
+create_template_control_item:
+			SCHEMA name
+				{
+					$$ = makeDefElem("schema", (Node *)makeString($2));
+				}
+			| COMMENT Sconst
+				{
+					$$ = makeDefElem("comment", (Node *)makeString($2));
+				}
+			| IDENT
+				{
+					/*
+					 * We handle identifiers that aren't parser keywords with
+					 * the following special-case codes, to avoid bloating the
+					 * size of the main parser.
+					 */
+					if (strcmp($1, "superuser") == 0)
+						$$ = makeDefElem("superuser", (Node *)makeInteger(TRUE));
+					else if (strcmp($1, "nosuperuser") == 0)
+						$$ = makeDefElem("superuser", (Node *)makeInteger(FALSE));
+					else if (strcmp($1, "relocatable") == 0)
+						$$ = makeDefElem("relocatable", (Node *)makeInteger(TRUE));
+					else if (strcmp($1, "norelocatable") == 0)
+						$$ = makeDefElem("relocatable", (Node *)makeInteger(FALSE));
+					else if (strcmp($1, "requires") == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("template option \"%s\" takes an argument", $1),
+									 parser_errposition(@1)));
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized template option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+			| IDENT Sconst
+				{
+					if (strcmp($1, "requires") == 0)
+						$$ = makeDefElem("requires", (Node *)makeString($2));
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized template option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+		;
+
+/*****************************************************************************
+ *
+ *		QUERY:
  *             CREATE EXTENSION extension
  *             [ WITH ] [ SCHEMA schema ] [ VERSION version ] [ FROM oldversion ]
  *
@@ -7273,6 +7536,15 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER TEMPLATE FOR EXTENSION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_EXTENSION_TEMPLATE;
+					n->subname = $5;
+					n->newname = $8;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 		;
 
 opt_column: COLUMN									{ $$ = COLUMN; }
@@ -7676,6 +7948,14 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
 					n->newowner = $7;
 					$$ = (Node *)n;
 				}
+			| ALTER TEMPLATE FOR EXTENSION name OWNER TO RoleId
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+					n->objectType = OBJECT_EXTENSION_TEMPLATE;
+					n->object = list_make1(makeString($5));
+					n->newowner = $8;
+					$$ = (Node *)n;
+				}
 		;
 
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c940897..224dd57 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -47,6 +47,7 @@
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/template.h"
 #include "commands/trigger.h"
 #include "commands/typecmds.h"
 #include "commands/user.h"
@@ -235,6 +236,8 @@ check_xact_readonly(Node *parsetree)
 		case T_CreateExtensionStmt:
 		case T_AlterExtensionStmt:
 		case T_AlterExtensionContentsStmt:
+		case T_CreateExtTemplateStmt:
+		case T_AlterExtTemplateStmt:
 		case T_CreateFdwStmt:
 		case T_AlterFdwStmt:
 		case T_CreateForeignServerStmt:
@@ -1179,6 +1182,14 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
 				break;
 
+			case T_CreateExtTemplateStmt:
+				CreateTemplate((CreateExtTemplateStmt *) parsetree);
+				break;
+
+			case T_AlterExtTemplateStmt:
+				AlterTemplate((AlterExtTemplateStmt *) parsetree);
+				break;
+
 			case T_CreateFdwStmt:
 				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 				break;
@@ -1674,6 +1685,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_MATVIEW:
 			tag = "ALTER MATERIALIZED VIEW";
 			break;
+		case OBJECT_EXTENSION_TEMPLATE:
+			tag = "ALTER TEMPLATE FOR EXTENSION";
+			break;
 		default:
 			tag = "???";
 			break;
@@ -1827,6 +1841,14 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER EXTENSION";
 			break;
 
+		case T_CreateExtTemplateStmt:
+			tag = "CREATE TEMPLATE FOR EXTENSION";
+			break;
+
+		case T_AlterExtTemplateStmt:
+			tag = "ALTER TEMPLATE FOR EXTENSION";
+			break;
+
 		case T_CreateFdwStmt:
 			tag = "CREATE FOREIGN DATA WRAPPER";
 			break;
@@ -1910,6 +1932,12 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_EXTENSION:
 					tag = "DROP EXTENSION";
 					break;
+				case OBJECT_EXTENSION_TEMPLATE:
+					tag = "DROP TEMPLATE FOR EXTENSION";
+					break;
+				case OBJECT_EXTENSION_UPTMPL:
+					tag = "DROP TEMPLATE FOR EXTENSION";
+					break;
 				case OBJECT_FUNCTION:
 					tag = "DROP FUNCTION";
 					break;
@@ -2506,6 +2534,11 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateExtTemplateStmt:
+		case T_AlterExtTemplateStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_CreateFdwStmt:
 		case T_AlterFdwStmt:
 		case T_CreateForeignServerStmt:
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 247ad92..9b8ac9e 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -84,6 +84,7 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	InhInfo    *inhinfo;
 	CollInfo   *collinfo;
 	int			numExtensions;
+	int			numExtensionTemplates;
 	int			numAggregates;
 	int			numInherits;
 	int			numRules;
@@ -121,6 +122,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	getOwnedSeqs(fout, tblinfo, numTables);
 
 	if (g_verbose)
+		write_msg(NULL, "reading extension templates\n");
+	getExtensionTemplates(fout, &numExtensionTemplates);
+
+	if (g_verbose)
 		write_msg(NULL, "reading extensions\n");
 	extinfo = getExtensions(fout, &numExtensions);
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 5204ceb..6e2ca0c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2895,6 +2895,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
 	if (strcmp(type, "COLLATION") == 0 ||
 		strcmp(type, "CONVERSION") == 0 ||
 		strcmp(type, "DOMAIN") == 0 ||
+		strcmp(type, "EXTENSION TEMPLATE") == 0 ||
 		strcmp(type, "TABLE") == 0 ||
 		strcmp(type, "TYPE") == 0 ||
 		strcmp(type, "FOREIGN TABLE") == 0 ||
@@ -2908,6 +2909,10 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
 		strcmp(type, "SERVER") == 0 ||
 		strcmp(type, "USER MAPPING") == 0)
 	{
+		/* use ALTER TEMPLATE FOR EXTENSION for extension templates */
+		if (strcmp(type, "EXTENSION TEMPLATE") == 0)
+			type = "TEMPLATE FOR EXTENSION";
+
 		/* We already know that search_path was set properly */
 		appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
 		return;
@@ -3092,6 +3097,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 			strcmp(te->desc, "CONVERSION") == 0 ||
 			strcmp(te->desc, "DATABASE") == 0 ||
 			strcmp(te->desc, "DOMAIN") == 0 ||
+			strcmp(te->desc, "EXTENSION TEMPLATE") == 0 ||
 			strcmp(te->desc, "FUNCTION") == 0 ||
 			strcmp(te->desc, "OPERATOR") == 0 ||
 			strcmp(te->desc, "OPERATOR CLASS") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e1ef55f..7ae895e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -171,6 +171,7 @@ static int	collectSecLabels(Archive *fout, SecLabelItem **items);
 static void dumpDumpableObject(Archive *fout, DumpableObject *dobj);
 static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo);
 static void dumpExtension(Archive *fout, ExtensionInfo *extinfo);
+static void dumpExtensionTemplate(Archive *fout, ExtensionTemplateInfo *extinfo);
 static void dumpType(Archive *fout, TypeInfo *tyinfo);
 static void dumpBaseType(Archive *fout, TypeInfo *tyinfo);
 static void dumpEnumType(Archive *fout, TypeInfo *tyinfo);
@@ -3014,6 +3015,134 @@ findNamespace(Archive *fout, Oid nsoid, Oid objoid)
 }
 
 /*
+ * getExtensionTemplates:
+ *	  read all extension template in the system catalogs and return them in the
+ * ExtensionTemplateInfo* structure
+ *
+ *	numExtensionTemplates is set to the number of extensions templates read in
+ */
+ExtensionTemplateInfo *
+getExtensionTemplates(Archive *fout, int *numExtensionTemplates)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query;
+	ExtensionTemplateInfo *exttmplinfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_extname;
+	int			i_rolname;
+	int			i_namespace;
+	int			i_isdefault;
+	int			i_relocatable;
+	int			i_superuser;
+	int			i_requires;
+	int			i_install;
+	int			i_version;
+	int			i_from;
+	int			i_to;
+	int			i_script;
+
+	/*
+	 * Before 9.3, there are no extension templates.
+	 */
+	if (fout->remoteVersion < 90300)
+	{
+		*numExtensionTemplates = 0;
+		return NULL;
+	}
+
+	query = createPQExpBuffer();
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/*
+	 * The main catalog object for an extension template is the
+	 * pg_extension_control entry, from which thanks to pg_depend we fetch
+	 * either the extension creation script or its upgrade script.
+	 */
+	appendPQExpBuffer(query, "select c.tableoid, c.oid, "
+					  "(%s ctlowner) AS rolname, "
+					  "c.ctlname, c.ctldefault, c.ctlrelocatable, "
+					  "c.ctlsuperuser, c.ctlnamespace, "
+					  "array_to_string(c.ctlrequires, ',') as requires, "
+					  "true as install, t.tplversion as version, "
+					  "null as uptfrom, null as uptto, t.tplscript as script "
+					  "from pg_extension_control c "
+					  "join pg_depend d "
+					  "on d.classid = 'pg_catalog.pg_extension_control'::regclass "
+					  "and d.objid = c.oid "
+					  "and d.refclassid = 'pg_catalog.pg_extension_template'::regclass "
+					  "join pg_extension_template t on t.oid = d.refobjid "
+					  "union all  "
+					  "select c.tableoid, c.oid, "
+					  "(%s ctlowner) AS rolname, "
+					  "c.ctlname, c.ctldefault, c.ctlrelocatable, "
+					  "c.ctlsuperuser, c.ctlnamespace, "
+					  "array_to_string(c.ctlrequires, ',') as requires, "
+					  "false as install, null as version, "
+					  "u.uptfrom, u.uptto, u.uptscript as script "
+					  "from pg_extension_control c "
+					  "join pg_depend d on d.classid = 'pg_catalog.pg_extension_control'::regclass "
+					  "and d.objid = c.oid "
+					  "and d.refclassid = 'pg_catalog.pg_extension_uptmpl'::regclass "
+					  "join pg_extension_uptmpl u on u.oid = d.refobjid ",
+					  username_subquery,
+					  username_subquery);
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	exttmplinfo = (ExtensionTemplateInfo *)
+		pg_malloc(ntups * sizeof(ExtensionTemplateInfo));
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_extname = PQfnumber(res, "ctlname");
+	i_rolname = PQfnumber(res, "rolname");
+	i_isdefault = PQfnumber(res, "ctldefault");
+	i_relocatable = PQfnumber(res, "ctlrelocatable");
+	i_namespace = PQfnumber(res, "ctlnamespace");
+	i_requires = PQfnumber(res, "requires");
+	i_superuser = PQfnumber(res, "ctlsuperuser");
+	i_install = PQfnumber(res, "install");
+	i_version = PQfnumber(res, "version");
+	i_from = PQfnumber(res, "uptfrom");
+	i_to = PQfnumber(res, "uptto");
+	i_script = PQfnumber(res, "script");
+
+	for (i = 0; i < ntups; i++)
+	{
+		exttmplinfo[i].dobj.objType = DO_EXTENSION_TEMPLATE;
+		exttmplinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+		exttmplinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&exttmplinfo[i].dobj);
+		exttmplinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_extname));
+		exttmplinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+		exttmplinfo[i].namespace = pg_strdup(PQgetvalue(res, i, i_namespace));
+		exttmplinfo[i].isdefault = *(PQgetvalue(res, i, i_isdefault)) == 't';
+		exttmplinfo[i].relocatable = *(PQgetvalue(res, i, i_relocatable)) == 't';
+		exttmplinfo[i].superuser = *(PQgetvalue(res, i, i_superuser)) == 't';
+		exttmplinfo[i].install = *(PQgetvalue(res, i, i_install)) == 't';
+		exttmplinfo[i].requires = pg_strdup(PQgetvalue(res, i, i_requires));
+		exttmplinfo[i].version = pg_strdup(PQgetvalue(res, i, i_version));
+		exttmplinfo[i].from = pg_strdup(PQgetvalue(res, i, i_from));
+		exttmplinfo[i].to = pg_strdup(PQgetvalue(res, i, i_to));
+		exttmplinfo[i].script = pg_strdup(PQgetvalue(res, i, i_script));
+	}
+
+	PQclear(res);
+	destroyPQExpBuffer(query);
+
+	*numExtensionTemplates = ntups;
+
+	return exttmplinfo;
+}
+
+/*
  * getExtensions:
  *	  read all extensions in the system catalogs and return them in the
  * ExtensionInfo* structure
@@ -7654,6 +7783,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_NAMESPACE:
 			dumpNamespace(fout, (NamespaceInfo *) dobj);
 			break;
+		case DO_EXTENSION_TEMPLATE:
+			dumpExtensionTemplate(fout, (ExtensionTemplateInfo *) dobj);
+			break;
 		case DO_EXTENSION:
 			dumpExtension(fout, (ExtensionInfo *) dobj);
 			break;
@@ -7829,6 +7961,112 @@ dumpNamespace(Archive *fout, NamespaceInfo *nspinfo)
 }
 
 /*
+ * dumpExtensionTemplate
+ *	  writes out to fout the queries to recreate an extension
+ */
+static void
+dumpExtensionTemplate(Archive *fout, ExtensionTemplateInfo *exttmplinfo)
+{
+	PQExpBuffer q;
+	PQExpBuffer delq;
+	PQExpBuffer labelq;
+	char	   *qextname,
+			   *qversion,
+			   *qfrom,
+			   *qto;
+	bool 		upgrade; /* install or upgrade script? */
+
+	/* Skip if not to be dumped */
+	if (!exttmplinfo->dobj.dump || dataOnly)
+		return;
+
+	q = createPQExpBuffer();
+	delq = createPQExpBuffer();
+	labelq = createPQExpBuffer();
+
+	qextname = pg_strdup(fmtId(exttmplinfo->dobj.name));
+	upgrade = !exttmplinfo->install;
+
+	if (upgrade)
+	{
+		qfrom = pg_strdup(fmtId(exttmplinfo->from));
+		qto = pg_strdup(fmtId(exttmplinfo->to));
+		qversion = NULL;
+
+		appendPQExpBuffer(delq, "DROP TEMPLATE FOR EXTENSION %s FOR UPDATE FROM %s TO %s;\n",
+						  qextname, qfrom, qto);
+	}
+	else
+	{
+		qversion = pg_strdup(fmtId(exttmplinfo->version));
+
+		appendPQExpBuffer(delq, "DROP TEMPLATE FOR EXTENSION %s VERSION %s;\n",
+						  qextname, qversion);
+	}
+
+	appendPQExpBuffer(q, "CREATE TEMPLATE FOR EXTENSION %s", qextname);
+
+	if (upgrade)
+		appendPQExpBuffer(q, " FOR UPDATE FROM %s TO %s", qfrom, qto);
+	else
+	{
+		if (exttmplinfo->isdefault)
+			appendPQExpBuffer(q, " DEFAULT");
+
+		appendPQExpBuffer(q, " VERSION %s", qversion);
+	}
+
+	/* control options  */
+	appendPQExpBuffer(q, "\n  WITH (schema %s, %ssuperuser, %srelocatable",
+					  fmtId(exttmplinfo->namespace),
+					  exttmplinfo->superuser ? "" : "no",
+					  exttmplinfo->relocatable ? "" : "no");
+
+	if (exttmplinfo->requires && strlen(exttmplinfo->requires))
+		appendPQExpBuffer(q, ", requires '%s'", exttmplinfo->requires);
+	appendPQExpBuffer(q, ")\n");
+
+	/* extension script (either install or upgrade script) */
+	appendPQExpBuffer(q, " AS\n$%s$%s$%s$;\n",
+					  qextname, exttmplinfo->script, qextname);
+
+	/*
+	 * When the default version is not a create script, we need an extra ALTER
+	 * statement here.
+	 */
+	if (!upgrade && exttmplinfo->isdefault)
+	{
+		appendPQExpBuffer(q, "\nALTER TEMPLATE FOR EXTENSION %s ", qextname);
+		appendPQExpBuffer(q, "SET DEFAULT VERSION %s;\n", qversion);
+	}
+
+	appendPQExpBuffer(labelq, "TEMPLATE FOR EXTENSION %s", qextname);
+
+	ArchiveEntry(fout, exttmplinfo->dobj.catId, exttmplinfo->dobj.dumpId,
+				 exttmplinfo->dobj.name,
+				 NULL, NULL,
+				 exttmplinfo->rolname,
+				 false, "EXTENSION TEMPLATE", SECTION_PRE_DATA,
+				 q->data, delq->data, NULL,
+				 NULL, 0,
+				 NULL, NULL);
+
+	/* Dump Extension Comments and Security Labels */
+	dumpComment(fout, labelq->data,
+				NULL, "",
+				exttmplinfo->dobj.catId, 0, exttmplinfo->dobj.dumpId);
+	dumpSecLabel(fout, labelq->data,
+				 NULL, "",
+				 exttmplinfo->dobj.catId, 0, exttmplinfo->dobj.dumpId);
+
+	free(qextname);
+
+	destroyPQExpBuffer(q);
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(labelq);
+}
+
+/*
  * dumpExtension
  *	  writes out to fout the queries to recreate an extension
  */
@@ -14900,6 +15138,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 		{
 			case DO_NAMESPACE:
 			case DO_EXTENSION:
+			case DO_EXTENSION_TEMPLATE:
 			case DO_TYPE:
 			case DO_SHELL_TYPE:
 			case DO_FUNC:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2c5971c..401c03a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -79,6 +79,7 @@ typedef enum
 	/* When modifying this enum, update priority tables in pg_dump_sort.c! */
 	DO_NAMESPACE,
 	DO_EXTENSION,
+	DO_EXTENSION_TEMPLATE,
 	DO_TYPE,
 	DO_SHELL_TYPE,
 	DO_FUNC,
@@ -145,6 +146,22 @@ typedef struct _extensionInfo
 	char	   *extcondition;
 } ExtensionInfo;
 
+typedef struct _extensionTemplateInfo
+{
+	DumpableObject dobj;
+	char	   *rolname;		/* name of owner, or empty string */
+	char	   *namespace;		/* schema containing extension's objects */
+	bool		isdefault;		/* from pg_extension_control */
+	bool		relocatable;	/* from pg_extension_control */
+	bool		superuser;		/* from pg_extension_control */
+	char	   *requires;		/* from pg_extension_control */
+	bool	    install;		/* true if install template (not upgrade) */
+	char	   *version;		/* from pg_extension_template */
+	char	   *from;			/* from pg_extension_uptmpl */
+	char	   *to;				/* from pg_extension_uptmpl */
+	char	   *script;			/* both from template or uptmpl */
+} ExtensionTemplateInfo;
+
 typedef struct _typeInfo
 {
 	DumpableObject dobj;
@@ -542,6 +559,8 @@ extern void sortDataAndIndexObjectsBySize(DumpableObject **objs, int numObjs);
  */
 extern NamespaceInfo *getNamespaces(Archive *fout, int *numNamespaces);
 extern ExtensionInfo *getExtensions(Archive *fout, int *numExtensions);
+extern ExtensionTemplateInfo *getExtensionTemplates(Archive *fout,
+												   int *numExtensionTemplates);
 extern TypeInfo *getTypes(Archive *fout, int *numTypes);
 extern FuncInfo *getFuncs(Archive *fout, int *numFuncs);
 extern AggInfo *getAggregates(Archive *fout, int *numAggregates);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 141e713..f9d6e83 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -38,6 +38,7 @@ static const int oldObjectTypePriority[] =
 {
 	1,							/* DO_NAMESPACE */
 	1,							/* DO_EXTENSION */
+	1,							/* DO_EXTENSION_TEMPLATE */
 	2,							/* DO_TYPE */
 	2,							/* DO_SHELL_TYPE */
 	2,							/* DO_FUNC */
@@ -86,6 +87,7 @@ static const int newObjectTypePriority[] =
 {
 	1,							/* DO_NAMESPACE */
 	4,							/* DO_EXTENSION */
+	4,							/* DO_EXTENSION_TEMPLATE */
 	5,							/* DO_TYPE */
 	5,							/* DO_SHELL_TYPE */
 	6,							/* DO_FUNC */
@@ -1178,6 +1180,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "SCHEMA %s  (ID %d OID %u)",
 					 obj->name, obj->dumpId, obj->catId.oid);
 			return;
+		case DO_EXTENSION_TEMPLATE:
+			snprintf(buf, bufsize,
+					 "EXTENSION TEMPLATE %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
+			return;
 		case DO_EXTENSION:
 			snprintf(buf, bufsize,
 					 "EXTENSION %s  (ID %d OID %u)",
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3aefbb5e..2dcaa15 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -146,6 +146,9 @@ typedef enum ObjectClass
 	OCLASS_USER_MAPPING,		/* pg_user_mapping */
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
+	OCLASS_EXTENSION_CONTROL,	/* pg_extension_control */
+	OCLASS_EXTENSION_TEMPLATE,	/* pg_extension_template */
+	OCLASS_EXTENSION_UPTMPL,	/* pg_extension_uptmpl */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 4860e98..05908c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -309,6 +309,23 @@ DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(o
 
 DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(extname name_ops));
 #define ExtensionNameIndexId 3081
+DECLARE_UNIQUE_INDEX(pg_extension_template_oid_index, 3180, on pg_extension_template using btree(oid oid_ops));
+#define ExtensionTemplateOidIndexId 3180
+
+DECLARE_UNIQUE_INDEX(pg_extension_template_name_version_index, 3181, on pg_extension_template using btree(tplname name_ops, tplversion text_ops));
+#define ExtensionTemplateNameVersionIndexId 3181
+
+DECLARE_UNIQUE_INDEX(pg_extension_uptmpl_oid_index, 3280, on pg_extension_uptmpl using btree(oid oid_ops));
+#define ExtensionUpTmplOidIndexId 3280
+
+DECLARE_UNIQUE_INDEX(pg_extension_uptmpl_name_from_to_index, 3281, on pg_extension_uptmpl using btree(uptname name_ops, uptfrom text_ops, uptto text_ops));
+#define ExtensionUpTpmlNameFromToIndexId 3281
+
+DECLARE_UNIQUE_INDEX(pg_extension_control_oid_index, 3380, on pg_extension_control using btree(oid oid_ops));
+#define ExtensionControlOidIndexId 3380
+
+DECLARE_UNIQUE_INDEX(pg_extension_control_name_version_index, 3381, on pg_extension_control using btree(ctlname name_ops, ctlversion text_ops));
+#define ExtensionControlNameVersionIndexId 3381
 
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index f795cff..d3bec52 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -48,6 +48,8 @@ DECLARE_TOAST(pg_rewrite, 2838, 2839);
 DECLARE_TOAST(pg_seclabel, 3598, 3599);
 DECLARE_TOAST(pg_statistic, 2840, 2841);
 DECLARE_TOAST(pg_trigger, 2336, 2337);
+DECLARE_TOAST(pg_extension_template, 3182, 3183);
+DECLARE_TOAST(pg_extension_uptmpl, 3184, 3185);
 
 /* shared catalogs */
 DECLARE_TOAST(pg_shdescription, 2846, 2847);
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index ee16bd5..7527d3c 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -18,7 +18,9 @@
 #include "nodes/parsenodes.h"
 #include "utils/relcache.h"
 
-extern Oid	ExecRenameStmt(RenameStmt *stmt);
+extern Oid ExecRenameStmt(RenameStmt *stmt);
+extern void AlterObjectRename_internal(Relation rel,
+									   Oid objectId, const char *new_name);
 
 extern Oid	ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
 extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index bf98168..e311504 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -26,15 +26,42 @@
 extern bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
+/*
+ * Internal data structure to hold the extension control information, that we
+ * get either from parsing a control file or from the pg_extension_control
+ * catalog when working from Extension Templates.
+ */
+typedef struct ExtensionControl
+{
+	Oid         ctrlOid;		 /* pg_control_extension oid, or invalidoid */
+	char	   *name;			/* name of the extension */
+	char	   *version;		/* version name of this extension's control */
+	char	   *directory;		/* directory for script files */
+	char	   *default_version;	/* default install target version, if any */
+	char	   *default_full_version;	/* default install source version, if any */
+	char	   *module_pathname;	/* string to substitute for module_pathname */
+	char	   *comment;		/* comment, if any */
+	char	   *schema;			/* target schema (allowed if !relocatable) */
+	bool		relocatable;	/* is alter extension set schema supported? */
+	bool		superuser;		/* must be superuser to install? */
+	int			encoding;		/* encoding of the script file, or -1 */
+	List	   *requires;		/* names of prerequisite extensions */
+	bool		is_template;	/* true if we're using catalog templates */
+} ExtensionControl;
+
+extern char *get_extension_control_filename(const char *extname);
+extern ExtensionControl *read_extension_control_file(const char *extname);
+extern void check_valid_extension_name(const char *extensionname);
 
 extern Oid	CreateExtension(CreateExtensionStmt *stmt);
 
 extern void RemoveExtensionById(Oid extId);
 
 extern Oid InsertExtensionTuple(const char *extName, Oid extOwner,
-					 Oid schemaOid, bool relocatable, const char *extVersion,
-					 Datum extConfig, Datum extCondition,
-					 List *requiredExtensions);
+								Oid schemaOid, bool relocatable,
+								const char *extVersion,
+								Datum extConfig, Datum extCondition,
+								List *requiredExtensions, Oid ctrlOid);
 
 extern Oid	ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 78368c6..d93d7ae 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -362,6 +362,8 @@ typedef enum NodeTag
 	T_CreateEventTrigStmt,
 	T_AlterEventTrigStmt,
 	T_RefreshMatViewStmt,
+	T_CreateExtTemplateStmt,
+	T_AlterExtTemplateStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 51fef68..a8fba05 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1167,6 +1167,8 @@ typedef enum ObjectType
 	OBJECT_DOMAIN,
 	OBJECT_EVENT_TRIGGER,
 	OBJECT_EXTENSION,
+	OBJECT_EXTENSION_TEMPLATE,
+	OBJECT_EXTENSION_UPTMPL,
 	OBJECT_FDW,
 	OBJECT_FOREIGN_SERVER,
 	OBJECT_FOREIGN_TABLE,
@@ -1678,6 +1680,49 @@ typedef struct AlterExtensionContentsStmt
 	List	   *objargs;		/* Arguments if needed (eg, for functions) */
 } AlterExtensionContentsStmt;
 
+typedef enum ExtTemplateType
+{
+	TEMPLATE_CREATE_EXTENSION,
+	TEMPLATE_UPDATE_EXTENSION
+} ExtTemplateType;
+
+typedef struct CreateExtTemplateStmt
+{
+	NodeTag		type;
+	ExtTemplateType tmpltype;
+	char	   *extname;		/* Extension's name */
+	char	   *version;		/* Version to create from the template */
+	char	   *from;			/*   In case of an update template, we update */
+	char	   *to;				/*   from version to version */
+	List	   *control;		/* List of DefElem nodes */
+	char	   *script;			/* Extension's install script */
+	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool        default_version; /* default version of this extension */
+} CreateExtTemplateStmt;
+
+typedef enum AlterExtTemplateType
+{
+	AET_SET_DEFAULT,
+	AET_SET_DEFAULT_FULL,
+	AET_SET_SCRIPT,
+	AET_UPDATE_CONTROL,
+} AlterExtTemplateType;
+
+typedef struct AlterExtTemplateStmt
+{
+	NodeTag		type;
+	ExtTemplateType tmpltype;
+	AlterExtTemplateType cmdtype; /* Type of command */
+	char	   *extname;				/* Extension's name */
+	char       *version;				/* Extension's version */
+	char	   *from;			/*   In case of an update template, we update */
+	char	   *to;				/*   from version to version */
+	List	   *control;		/* List of DefElem nodes */
+	char	   *script;			/* Extension's install script */
+	bool		missing_ok;		/* skip error if missing? */
+
+} AlterExtTemplateStmt;
+
 /* ----------------------
  *		Create/Alter FOREIGN DATA WRAPPER Statements
  * ----------------------
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 2116259..a722ab8 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -197,6 +197,9 @@ typedef enum AclObjectKind
 	ACL_KIND_FOREIGN_SERVER,	/* pg_foreign_server */
 	ACL_KIND_EVENT_TRIGGER,		/* pg_event_trigger */
 	ACL_KIND_EXTENSION,			/* pg_extension */
+	ACL_KIND_EXTCONTROL,		/* pg_extension_control */
+	ACL_KIND_EXTTEMPLATE,		/* pg_extension_template */
+	ACL_KIND_EXTUPTMPL,			/* pg_extension_uptmpl */
 	MAX_ACL_KIND				/* MUST BE LAST */
 } AclObjectKind;
 
@@ -325,6 +328,9 @@ extern bool pg_foreign_data_wrapper_ownercheck(Oid srv_oid, Oid roleid);
 extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
 extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
 extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
+extern bool pg_extension_control_ownercheck(Oid ext_oid, Oid roleid);
+extern bool pg_extension_template_ownercheck(Oid ext_template_oid, Oid roleid);
+extern bool pg_extension_uptmpl_ownercheck(Oid ext_uptmpl_oid, Oid roleid);
 extern bool has_createrole_privilege(Oid roleid);
 
 #endif   /* ACL_H */
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 432d39a..50ca749 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -104,6 +104,9 @@ SELECT relname, relhasindex
  pg_enum                 | t
  pg_event_trigger        | t
  pg_extension            | t
+ pg_extension_control    | t
+ pg_extension_template   | t
+ pg_extension_uptmpl     | t
  pg_foreign_data_wrapper | t
  pg_foreign_server       | t
  pg_foreign_table        | t
@@ -166,7 +169,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(158 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fd08e8d..a8fb08a 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -94,6 +94,7 @@ test: alter_generic misc psql async
 test: rules
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
+test: extension
 
 # ----------
 # Another group of parallel tests
