diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 13b28b1..af9d115 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.
+   <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 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.
   </para>
 
   <para>
-   Ordinarily, the user must have the
+   One 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.
+   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>
 
   <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>,
-   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.
+   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>
  </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 339ba5b..a63a39f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2277,6 +2277,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 c31c57e..0f40c9e 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 cdff43d..9d72edb 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, NULL, 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, NULL, 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, NULL, 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 ad5be90..1bd5ed4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4304,14 +4304,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 6f10707..7923cdc 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -47,6 +47,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..c70f8ec 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-2020, 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 */
