diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 13b28b1..2a9c7e8 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -21,9 +21,9 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable> ] [ VALIDATOR <replaceable>valfunction</replaceable> ]
+CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 </synopsis>
  </refsynopsisdiv>
 
@@ -37,21 +37,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
    defined in this new language.
   </para>
 
-  <note>
-   <para>
-    As of <productname>PostgreSQL</productname> 9.1, most procedural
-    languages have been made into <quote>extensions</quote>, and should
-    therefore be installed with <xref linkend="sql-createextension"/>
-    not <command>CREATE LANGUAGE</command>.  Direct use of
-    <command>CREATE LANGUAGE</command> should now be confined to
-    extension installation scripts.  If you have a <quote>bare</quote>
-    language in your database, perhaps as a result of an upgrade,
-    you can convert it to an extension using
-    <literal>CREATE EXTENSION <replaceable>langname</replaceable> FROM
-    unpackaged</literal>.
-   </para>
-  </note>
-
   <para>
    <command>CREATE LANGUAGE</command> effectively associates the
    language name with handler function(s) that are responsible for executing
@@ -60,53 +45,32 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>
 
   <para>
-   There are two forms of the <command>CREATE LANGUAGE</command> command.
-   In the first form, the user supplies just the name of the desired
-   language, and the <productname>PostgreSQL</productname> server consults
-   the <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
-   system catalog to determine the correct parameters.  In the second form,
-   the user supplies the language parameters along with the language name.
-   The second form can be used to create a language that is not defined in
-   <structname>pg_pltemplate</structname>, but this approach is considered obsolescent.
-  </para>
-
-  <para>
-   When the server finds an entry in the <structname>pg_pltemplate</structname> catalog
-   for the given language name, it will use the catalog data even if the
-   command includes language parameters.  This behavior simplifies loading of
-   old dump files, which are likely to contain out-of-date information
-   about language support functions.
-  </para>
-
-  <para>
-   Ordinarily, the user must have the
-   <productname>PostgreSQL</productname> superuser privilege to
-   register a new language.  However, the owner of a database can register
-   a new language within that database if the language is listed in
-   the <structname>pg_pltemplate</structname> catalog and is marked
-   as allowed to be created by database owners (<structfield>tmpldbacreate</structfield>
-   is true).  The default is that trusted languages can be created
-   by database owners, but this can be adjusted by superusers by modifying
-   the contents of <structname>pg_pltemplate</structname>.
-   The creator of a language becomes its owner and can later
-   drop it, rename it, or assign it to a new owner.
+   The form of <command>CREATE LANGUAGE</command> that does not supply
+   any handler function is obsolete.  For backwards compatibility with
+   old dump files, it is interpreted as <command>CREATE EXTENSION</command>.
+   That will work if the language has been packaged into an extension of
+   the same name, which is the conventional way to set up procedural
+   languages.
   </para>
 
   <para>
    <command>CREATE OR REPLACE LANGUAGE</command> will either create a
    new language, or replace an existing definition.  If the language
-   already exists, its parameters are updated according to the values
-   specified or taken from <structname>pg_pltemplate</structname>,
+   already exists, its parameters are updated according to the command,
    but the language's ownership and permissions settings do not change,
    and any existing functions written in the language are assumed to still
-   be valid.  In addition to the normal privilege requirements for creating
-   a language, the user must be superuser or owner of the existing language.
-   The <literal>REPLACE</literal> case is mainly meant to be used to
-   ensure that the language exists.  If the language has a
-   <structname>pg_pltemplate</structname> entry then <literal>REPLACE</literal>
-   will not actually change anything about an existing definition, except in
-   the unusual case where the <structname>pg_pltemplate</structname> entry
-   has been modified since the language was created.
+   be valid.
+  </para>
+
+  <para>
+   One must have the
+   <productname>PostgreSQL</productname> superuser privilege to
+   register a new language or change an existing language's parameters.
+   However, once the language is created it is valid to assign ownership of
+   it to a non-superuser, who may then drop it, change its permissions,
+   rename it, or assign it to a new owner.  (Do not, however, assign
+   ownership of the underlying C functions to a non-superuser; that would
+   create a privilege escalation path for that user.)
   </para>
  </refsect1>
 
@@ -218,12 +182,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
      </listitem>
     </varlistentry>
    </variablelist>
-
-  <para>
-   The <literal>TRUSTED</literal> option and the support function name(s) are
-   ignored if the server has an entry for the specified language
-   name in <structname>pg_pltemplate</structname>.
-  </para>
  </refsect1>
 
  <refsect1 id="sql-createlanguage-notes">
@@ -255,18 +213,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>
 
   <para>
-   The call handler function, the inline handler function (if any),
-   and the validator function (if any)
-   must already exist if the server does not have an entry for the language
-   in <structname>pg_pltemplate</structname>.  But when there is an entry,
-   the functions need not already exist;
-   they will be automatically defined if not present in the database.
-   (This might result in <command>CREATE LANGUAGE</command> failing, if the
-   shared library that implements the language is not available in
-   the installation.)
-  </para>
-
-  <para>
    In <productname>PostgreSQL</productname> versions before 7.3, it was
    necessary to declare handler functions as returning the placeholder
    type <type>opaque</type>, rather than <type>language_handler</type>.
@@ -281,23 +227,20 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   <title>Examples</title>
 
   <para>
-   The preferred way of creating any of the standard procedural languages
-   is just:
-<programlisting>
-CREATE LANGUAGE plperl;
-</programlisting>
-  </para>
-
-  <para>
-   For a language not known in the <structname>pg_pltemplate</structname> catalog, a
-   sequence such as this is needed:
+   A minimal sequence for creating a new procedural language is:
 <programlisting>
 CREATE FUNCTION plsample_call_handler() RETURNS language_handler
     AS '$libdir/plsample'
     LANGUAGE C;
 CREATE LANGUAGE plsample
     HANDLER plsample_call_handler;
-</programlisting></para>
+</programlisting>
+   Typically that would be written in an extension's creation script,
+   and users would do this to install the extension:
+<programlisting>
+CREATE EXTENSION plsample;
+</programlisting>
+  </para>
  </refsect1>
 
  <refsect1 id="sql-createlanguage-compat">
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 257b4fc..8fcd98b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2333,6 +2333,64 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 }
 
 /*
+ * Test whether the given extension exists (not whether it's installed)
+ *
+ * This checks for the existence of a matching control file in the extension
+ * directory.  That's not a bulletproof check, since the file might be
+ * invalid, but this is only used for hints so it doesn't have to be 100%
+ * right.
+ */
+bool
+extension_file_exists(const char *extensionName)
+{
+	bool		result = false;
+	char	   *location;
+	DIR		   *dir;
+	struct dirent *de;
+
+	location = get_extension_control_directory();
+	dir = AllocateDir(location);
+
+	/*
+	 * If the control directory doesn't exist, we want to silently return
+	 * false.  Any other error will be reported by ReadDir.
+	 */
+	if (dir == NULL && errno == ENOENT)
+	{
+		/* do nothing */
+	}
+	else
+	{
+		while ((de = ReadDir(dir, location)) != NULL)
+		{
+			char	   *extname;
+
+			if (!is_extension_control_filename(de->d_name))
+				continue;
+
+			/* extract extension name from 'name.control' filename */
+			extname = pstrdup(de->d_name);
+			*strrchr(extname, '.') = '\0';
+
+			/* ignore it if it's an auxiliary control file */
+			if (strstr(extname, "--"))
+				continue;
+
+			/* done if it matches request */
+			if (strcmp(extname, extensionName) == 0)
+			{
+				result = true;
+				break;
+			}
+		}
+
+		FreeDir(dir);
+	}
+
+	return result;
+}
+
+/*
  * Convert a list of extension names to a name[] Datum
  */
 static Datum
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 40f1f9a..aa408b8 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
 #include "commands/proclang.h"
 #include "executor/execdesc.h"
 #include "executor/executor.h"
@@ -991,7 +992,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("language \"%s\" does not exist", language),
-				 (PLTemplateExists(language) ?
+				 (extension_file_exists(language) ?
 				  errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));
 
 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
@@ -2225,7 +2226,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("language \"%s\" does not exist", language),
-				 (PLTemplateExists(language) ?
+				 (extension_file_exists(language) ?
 				  errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));
 
 	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 343cd1d..520a603 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -13,329 +13,110 @@
  */
 #include "postgres.h"
 
-#include "access/genam.h"
-#include "access/htup_details.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
-#include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/proclang.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
-#include "parser/parser.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
 
-typedef struct
-{
-	bool		tmpltrusted;	/* trusted? */
-	bool		tmpldbacreate;	/* db owner allowed to create? */
-	char	   *tmplhandler;	/* name of handler function */
-	char	   *tmplinline;		/* name of anonymous-block handler, or NULL */
-	char	   *tmplvalidator;	/* name of validator function, or NULL */
-	char	   *tmpllibrary;	/* path of shared library */
-} PLTemplate;
-
-static ObjectAddress create_proc_lang(const char *languageName, bool replace,
-									  Oid languageOwner, Oid handlerOid, Oid inlineOid,
-									  Oid valOid, bool trusted);
-static PLTemplate *find_language_template(const char *languageName);
-
 /*
  * CREATE LANGUAGE
  */
 ObjectAddress
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
-	PLTemplate *pltemplate;
-	ObjectAddress tmpAddr;
+	const char *languageName = stmt->plname;
+	Oid			languageOwner = GetUserId();
 	Oid			handlerOid,
 				inlineOid,
 				valOid;
 	Oid			funcrettype;
 	Oid			funcargtypes[1];
+	Relation	rel;
+	TupleDesc	tupDesc;
+	Datum		values[Natts_pg_language];
+	bool		nulls[Natts_pg_language];
+	bool		replaces[Natts_pg_language];
+	NameData	langname;
+	HeapTuple	oldtup;
+	HeapTuple	tup;
+	Oid			langoid;
+	bool		is_update;
+	ObjectAddress myself,
+				referenced;
 
 	/*
-	 * If we have template information for the language, ignore the supplied
-	 * parameters (if any) and use the template information.
+	 * Check permission
 	 */
-	if ((pltemplate = find_language_template(stmt->plname)) != NULL)
-	{
-		List	   *funcname;
-
-		/*
-		 * Give a notice if we are ignoring supplied parameters.
-		 */
-		if (stmt->plhandler)
-			ereport(NOTICE,
-					(errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters")));
-
-		/*
-		 * Check permission
-		 */
-		if (!superuser())
-		{
-			if (!pltemplate->tmpldbacreate)
-				ereport(ERROR,
-						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-						 errmsg("must be superuser to create procedural language \"%s\"",
-								stmt->plname)));
-			if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
-				aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
-							   get_database_name(MyDatabaseId));
-		}
-
-		/*
-		 * Find or create the handler function, which we force to be in the
-		 * pg_catalog schema.  If already present, it must have the correct
-		 * return type.
-		 */
-		funcname = SystemFuncName(pltemplate->tmplhandler);
-		handlerOid = LookupFuncName(funcname, 0, funcargtypes, true);
-		if (OidIsValid(handlerOid))
-		{
-			funcrettype = get_func_rettype(handlerOid);
-			if (funcrettype != LANGUAGE_HANDLEROID)
-				ereport(ERROR,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("function %s must return type %s",
-								NameListToString(funcname), "language_handler")));
-		}
-		else
-		{
-			tmpAddr = ProcedureCreate(pltemplate->tmplhandler,
-									  PG_CATALOG_NAMESPACE,
-									  false,	/* replace */
-									  false,	/* returnsSet */
-									  LANGUAGE_HANDLEROID,
-									  BOOTSTRAP_SUPERUSERID,
-									  ClanguageId,
-									  F_FMGR_C_VALIDATOR,
-									  pltemplate->tmplhandler,
-									  pltemplate->tmpllibrary,
-									  PROKIND_FUNCTION,
-									  false,	/* security_definer */
-									  false,	/* isLeakProof */
-									  false,	/* isStrict */
-									  PROVOLATILE_VOLATILE,
-									  PROPARALLEL_UNSAFE,
-									  buildoidvector(funcargtypes, 0),
-									  PointerGetDatum(NULL),
-									  PointerGetDatum(NULL),
-									  PointerGetDatum(NULL),
-									  NIL,
-									  PointerGetDatum(NULL),
-									  PointerGetDatum(NULL),
-									  InvalidOid,
-									  1,
-									  0);
-			handlerOid = tmpAddr.objectId;
-		}
-
-		/*
-		 * Likewise for the anonymous block handler, if required; but we don't
-		 * care about its return type.
-		 */
-		if (pltemplate->tmplinline)
-		{
-			funcname = SystemFuncName(pltemplate->tmplinline);
-			funcargtypes[0] = INTERNALOID;
-			inlineOid = LookupFuncName(funcname, 1, funcargtypes, true);
-			if (!OidIsValid(inlineOid))
-			{
-				tmpAddr = ProcedureCreate(pltemplate->tmplinline,
-										  PG_CATALOG_NAMESPACE,
-										  false,	/* replace */
-										  false,	/* returnsSet */
-										  VOIDOID,
-										  BOOTSTRAP_SUPERUSERID,
-										  ClanguageId,
-										  F_FMGR_C_VALIDATOR,
-										  pltemplate->tmplinline,
-										  pltemplate->tmpllibrary,
-										  PROKIND_FUNCTION,
-										  false,	/* security_definer */
-										  false,	/* isLeakProof */
-										  true, /* isStrict */
-										  PROVOLATILE_VOLATILE,
-										  PROPARALLEL_UNSAFE,
-										  buildoidvector(funcargtypes, 1),
-										  PointerGetDatum(NULL),
-										  PointerGetDatum(NULL),
-										  PointerGetDatum(NULL),
-										  NIL,
-										  PointerGetDatum(NULL),
-										  PointerGetDatum(NULL),
-										  InvalidOid,
-										  1,
-										  0);
-				inlineOid = tmpAddr.objectId;
-			}
-		}
-		else
-			inlineOid = InvalidOid;
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("must be superuser to create custom procedural language")));
 
+	/*
+	 * Lookup the PL handler function and check that it is of the expected
+	 * return type
+	 */
+	Assert(stmt->plhandler);
+	handlerOid = LookupFuncName(stmt->plhandler, 0, funcargtypes, false);
+	funcrettype = get_func_rettype(handlerOid);
+	if (funcrettype != LANGUAGE_HANDLEROID)
+	{
 		/*
-		 * Likewise for the validator, if required; but we don't care about
-		 * its return type.
+		 * We allow OPAQUE just so we can load old dump files.  When we see a
+		 * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
+		 * (This is probably obsolete and removable?)
 		 */
-		if (pltemplate->tmplvalidator)
+		if (funcrettype == OPAQUEOID)
 		{
-			funcname = SystemFuncName(pltemplate->tmplvalidator);
-			funcargtypes[0] = OIDOID;
-			valOid = LookupFuncName(funcname, 1, funcargtypes, true);
-			if (!OidIsValid(valOid))
-			{
-				tmpAddr = ProcedureCreate(pltemplate->tmplvalidator,
-										  PG_CATALOG_NAMESPACE,
-										  false,	/* replace */
-										  false,	/* returnsSet */
-										  VOIDOID,
-										  BOOTSTRAP_SUPERUSERID,
-										  ClanguageId,
-										  F_FMGR_C_VALIDATOR,
-										  pltemplate->tmplvalidator,
-										  pltemplate->tmpllibrary,
-										  PROKIND_FUNCTION,
-										  false,	/* security_definer */
-										  false,	/* isLeakProof */
-										  true, /* isStrict */
-										  PROVOLATILE_VOLATILE,
-										  PROPARALLEL_UNSAFE,
-										  buildoidvector(funcargtypes, 1),
-										  PointerGetDatum(NULL),
-										  PointerGetDatum(NULL),
-										  PointerGetDatum(NULL),
-										  NIL,
-										  PointerGetDatum(NULL),
-										  PointerGetDatum(NULL),
-										  InvalidOid,
-										  1,
-										  0);
-				valOid = tmpAddr.objectId;
-			}
+			ereport(WARNING,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("changing return type of function %s from %s to %s",
+							NameListToString(stmt->plhandler),
+							"opaque", "language_handler")));
+			SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
 		}
 		else
-			valOid = InvalidOid;
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("function %s must return type %s",
+							NameListToString(stmt->plhandler), "language_handler")));
+	}
 
-		/* ok, create it */
-		return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-								handlerOid, inlineOid,
-								valOid, pltemplate->tmpltrusted);
+	/* validate the inline function */
+	if (stmt->plinline)
+	{
+		funcargtypes[0] = INTERNALOID;
+		inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
+		/* return value is ignored, so we don't check the type */
 	}
 	else
-	{
-		/*
-		 * No template, so use the provided information.  If there's no
-		 * handler clause, the user is trying to rely on a template that we
-		 * don't have, so complain accordingly.
-		 */
-		if (!stmt->plhandler)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_OBJECT),
-					 errmsg("unsupported language \"%s\"",
-							stmt->plname),
-					 errhint("The supported languages are listed in the pg_pltemplate system catalog.")));
+		inlineOid = InvalidOid;
 
-		/*
-		 * Check permission
-		 */
-		if (!superuser())
-			ereport(ERROR,
-					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-					 errmsg("must be superuser to create custom procedural language")));
-
-		/*
-		 * Lookup the PL handler function and check that it is of the expected
-		 * return type
-		 */
-		handlerOid = LookupFuncName(stmt->plhandler, 0, funcargtypes, false);
-		funcrettype = get_func_rettype(handlerOid);
-		if (funcrettype != LANGUAGE_HANDLEROID)
-		{
-			/*
-			 * We allow OPAQUE just so we can load old dump files.  When we
-			 * see a handler function declared OPAQUE, change it to
-			 * LANGUAGE_HANDLER.  (This is probably obsolete and removable?)
-			 */
-			if (funcrettype == OPAQUEOID)
-			{
-				ereport(WARNING,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("changing return type of function %s from %s to %s",
-								NameListToString(stmt->plhandler),
-								"opaque", "language_handler")));
-				SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
-			}
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("function %s must return type %s",
-								NameListToString(stmt->plhandler), "language_handler")));
-		}
-
-		/* validate the inline function */
-		if (stmt->plinline)
-		{
-			funcargtypes[0] = INTERNALOID;
-			inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
-			/* return value is ignored, so we don't check the type */
-		}
-		else
-			inlineOid = InvalidOid;
-
-		/* validate the validator function */
-		if (stmt->plvalidator)
-		{
-			funcargtypes[0] = OIDOID;
-			valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
-			/* return value is ignored, so we don't check the type */
-		}
-		else
-			valOid = InvalidOid;
-
-		/* ok, create it */
-		return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-								handlerOid, inlineOid,
-								valOid, stmt->pltrusted);
+	/* validate the validator function */
+	if (stmt->plvalidator)
+	{
+		funcargtypes[0] = OIDOID;
+		valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
+		/* return value is ignored, so we don't check the type */
 	}
-}
-
-/*
- * Guts of language creation.
- */
-static ObjectAddress
-create_proc_lang(const char *languageName, bool replace,
-				 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-				 Oid valOid, bool trusted)
-{
-	Relation	rel;
-	TupleDesc	tupDesc;
-	Datum		values[Natts_pg_language];
-	bool		nulls[Natts_pg_language];
-	bool		replaces[Natts_pg_language];
-	NameData	langname;
-	HeapTuple	oldtup;
-	HeapTuple	tup;
-	Oid			langoid;
-	bool		is_update;
-	ObjectAddress myself,
-				referenced;
+	else
+		valOid = InvalidOid;
 
+	/* ok to create it */
 	rel = table_open(LanguageRelationId, RowExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
@@ -348,7 +129,7 @@ create_proc_lang(const char *languageName, bool replace,
 	values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname);
 	values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner);
 	values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
-	values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted);
+	values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted);
 	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
 	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
 	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
@@ -362,13 +143,17 @@ create_proc_lang(const char *languageName, bool replace,
 		Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup);
 
 		/* There is one; okay to replace it? */
-		if (!replace)
+		if (!stmt->replace)
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("language \"%s\" already exists", languageName)));
+
+		/* This is currently pointless, since we already checked superuser */
+#ifdef NOT_USED
 		if (!pg_language_ownercheck(oldform->oid, languageOwner))
 			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
 						   languageName);
+#endif
 
 		/*
 		 * Do not change existing oid, ownership or permissions.  Note
@@ -451,83 +236,6 @@ create_proc_lang(const char *languageName, bool replace,
 }
 
 /*
- * Look to see if we have template information for the given language name.
- */
-static PLTemplate *
-find_language_template(const char *languageName)
-{
-	PLTemplate *result;
-	Relation	rel;
-	SysScanDesc scan;
-	ScanKeyData key;
-	HeapTuple	tup;
-
-	rel = table_open(PLTemplateRelationId, AccessShareLock);
-
-	ScanKeyInit(&key,
-				Anum_pg_pltemplate_tmplname,
-				BTEqualStrategyNumber, F_NAMEEQ,
-				CStringGetDatum(languageName));
-	scan = systable_beginscan(rel, PLTemplateNameIndexId, true,
-							  NULL, 1, &key);
-
-	tup = systable_getnext(scan);
-	if (HeapTupleIsValid(tup))
-	{
-		Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup);
-		Datum		datum;
-		bool		isnull;
-
-		result = (PLTemplate *) palloc0(sizeof(PLTemplate));
-		result->tmpltrusted = tmpl->tmpltrusted;
-		result->tmpldbacreate = tmpl->tmpldbacreate;
-
-		/* Remaining fields are variable-width so we need heap_getattr */
-		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler,
-							 RelationGetDescr(rel), &isnull);
-		if (!isnull)
-			result->tmplhandler = TextDatumGetCString(datum);
-
-		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline,
-							 RelationGetDescr(rel), &isnull);
-		if (!isnull)
-			result->tmplinline = TextDatumGetCString(datum);
-
-		datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator,
-							 RelationGetDescr(rel), &isnull);
-		if (!isnull)
-			result->tmplvalidator = TextDatumGetCString(datum);
-
-		datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
-							 RelationGetDescr(rel), &isnull);
-		if (!isnull)
-			result->tmpllibrary = TextDatumGetCString(datum);
-
-		/* Ignore template if handler or library info is missing */
-		if (!result->tmplhandler || !result->tmpllibrary)
-			result = NULL;
-	}
-	else
-		result = NULL;
-
-	systable_endscan(scan);
-
-	table_close(rel, AccessShareLock);
-
-	return result;
-}
-
-
-/*
- * This just returns true if we have a valid template for a given language
- */
-bool
-PLTemplateExists(const char *languageName)
-{
-	return (find_language_template(languageName) != NULL);
-}
-
-/*
  * Guts of language dropping.
  */
 void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb36..d09b4a5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4273,14 +4273,17 @@ NumericOnly_list:	NumericOnly						{ $$ = list_make1($1); }
 CreatePLangStmt:
 			CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
 			{
-				CreatePLangStmt *n = makeNode(CreatePLangStmt);
-				n->replace = $2;
-				n->plname = $6;
-				/* parameters are all to be supplied by system */
-				n->plhandler = NIL;
-				n->plinline = NIL;
-				n->plvalidator = NIL;
-				n->pltrusted = false;
+				/*
+				 * We now interpret parameterless CREATE LANGUAGE as
+				 * CREATE EXTENSION.  "OR REPLACE" is silently translated
+				 * to "IF NOT EXISTS", which isn't quite the same, but
+				 * seems more useful than throwing an error.  We just
+				 * ignore TRUSTED, as the previous code would have too.
+				 */
+				CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+				n->if_not_exists = $2;
+				n->extname = $6;
+				n->options = NIL;
 				$$ = (Node *)n;
 			}
 			| CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 3a155eaf..b4e3c65 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -51,6 +51,7 @@ extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *
 
 extern Oid	get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
+extern bool extension_file_exists(const char *extensionName);
 
 extern ObjectAddress AlterExtensionNamespace(const char *extensionName, const char *newschema,
 											 Oid *oldschema);
diff --git a/src/include/commands/proclang.h b/src/include/commands/proclang.h
index 9a4bc75..d7b0ab5 100644
--- a/src/include/commands/proclang.h
+++ b/src/include/commands/proclang.h
@@ -1,11 +1,12 @@
-/*
- * src/include/commands/proclang.h
- *
- *-------------------------------------------------------------------------
+/*-------------------------------------------------------------------------
  *
  * proclang.h
  *	  prototypes for proclang.c.
  *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/proclang.h
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +18,7 @@
 
 extern ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt);
 extern void DropProceduralLanguageById(Oid langOid);
-extern bool PLTemplateExists(const char *languageName);
+
 extern Oid	get_language_oid(const char *langname, bool missing_ok);
 
 #endif							/* PROCLANG_H */
