diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 04064d399cb312a30b915d7c14867891fd1221bc..e3363f868a4604a26ad7d7c1913d42ef6c8bbcf3 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -46,6 +46,10 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
     ON TYPES
     TO { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
 
+GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
+    ON SCHEMAS
+    TO { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
 REVOKE [ GRANT OPTION FOR ]
     { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
     [, ...] | ALL [ PRIVILEGES ] }
@@ -71,6 +75,12 @@ REVOKE [ GRANT OPTION FOR ]
     ON TYPES
     FROM { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...]
     [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { USAGE | CREATE | ALL [ PRIVILEGES ] }
+    ON SCHEMAS
+    FROM { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -81,8 +91,9 @@ REVOKE [ GRANT OPTION FOR ]
    <command>ALTER DEFAULT PRIVILEGES</> allows you to set the privileges
    that will be applied to objects created in the future.  (It does not
    affect privileges assigned to already-existing objects.)  Currently,
-   only the privileges for tables (including views and foreign tables),
-   sequences, functions, and types (including domains) can be altered.
+   only the privileges for schemas, tables (including views and foreign
+   tables), sequences, functions, and types (including domains) can be
+   altered.
   </para>
 
   <para>
@@ -125,6 +136,8 @@ REVOKE [ GRANT OPTION FOR ]
       are altered for objects later created in that schema.
       If <literal>IN SCHEMA</> is omitted, the global default privileges
       are altered.
+      <literal>IN SCHEMA</> is not allowed when using <literal>ON SCHEMAS</>
+      as schemas can't be nested.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d01930f4a80d0fdb8da8e98baaa1d0d764160881..2d535c2aada6951c0a6a9aa8d06ce4a849758a5c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -959,6 +959,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
 			all_privileges = ACL_ALL_RIGHTS_TYPE;
 			errormsg = gettext_noop("invalid privilege type %s for type");
 			break;
+		case ACL_OBJECT_NAMESPACE:
+			all_privileges = ACL_ALL_RIGHTS_NAMESPACE;
+			errormsg = gettext_noop("invalid privilege type %s for schema");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) action->objtype);
@@ -1146,6 +1150,16 @@ SetDefaultACL(InternalDefaultACL *iacls)
 				this_privileges = ACL_ALL_RIGHTS_TYPE;
 			break;
 
+		case ACL_OBJECT_NAMESPACE:
+			if (OidIsValid(iacls->nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_GRANT_OPERATION),
+						 errmsg("cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS")));
+			objtype = DEFACLOBJ_NAMESPACE;
+			if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+				this_privileges = ACL_ALL_RIGHTS_NAMESPACE;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized objtype: %d",
 				 (int) iacls->objtype);
@@ -1369,6 +1383,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case DEFACLOBJ_TYPE:
 				iacls.objtype = ACL_OBJECT_TYPE;
 				break;
+			case DEFACLOBJ_NAMESPACE:
+				iacls.objtype = ACL_OBJECT_NAMESPACE;
+				break;
 			default:
 				/* Shouldn't get here */
 				elog(ERROR, "unexpected default ACL type: %d",
@@ -5259,6 +5276,10 @@ get_user_default_acl(GrantObjectType objtype, Oid ownerId, Oid nsp_oid)
 			defaclobjtype = DEFACLOBJ_TYPE;
 			break;
 
+		case ACL_OBJECT_NAMESPACE:
+			defaclobjtype = DEFACLOBJ_NAMESPACE;
+			break;
+
 		default:
 			return NULL;
 	}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 2948d64fa73a6ab7108e8d4b3730669d98777c0e..1eb7930901974959afec077c4f645a772a259746 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1843,11 +1843,14 @@ get_object_address_defacl(List *object, bool missing_ok)
 		case DEFACLOBJ_TYPE:
 			objtype_str = "types";
 			break;
+		case DEFACLOBJ_NAMESPACE:
+			objtype_str = "schemas";
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				  errmsg("unrecognized default ACL object type %c", objtype),
-					 errhint("Valid object types are \"r\", \"S\", \"f\", and \"T\".")));
+					 errhint("Valid object types are \"r\", \"S\", \"f\", \"T\" and \"s\".")));
 	}
 
 	/*
@@ -3255,6 +3258,11 @@ getObjectDescription(const ObjectAddress *object)
 										 _("default privileges on new types belonging to role %s"),
 							   GetUserNameFromId(defacl->defaclrole, false));
 						break;
+					case DEFACLOBJ_NAMESPACE:
+						appendStringInfo(&buffer,
+										 _("default privileges on new schemas belonging to role %s"),
+							   GetUserNameFromId(defacl->defaclrole, false));
+						break;
 					default:
 						/* shouldn't get here */
 						appendStringInfo(&buffer,
@@ -4762,6 +4770,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 						appendStringInfoString(&buffer,
 											   " on types");
 						break;
+					case DEFACLOBJ_NAMESPACE:
+						appendStringInfoString(&buffer,
+											   " on schemas");
+						break;
 				}
 
 				if (objname)
diff --git a/src/backend/catalog/pg_namespace.c b/src/backend/catalog/pg_namespace.c
index 5672536d31c4bc6b968770936dee81503ea65936..613b963683d01e7682a421082a955e87f6258cf6 100644
--- a/src/backend/catalog/pg_namespace.c
+++ b/src/backend/catalog/pg_namespace.c
@@ -31,10 +31,11 @@
  * Create a namespace (schema) with the given name and owner OID.
  *
  * If isTemp is true, this schema is a per-backend schema for holding
- * temporary tables.  Currently, the only effect of that is to prevent it
- * from being linked as a member of any active extension.  (If someone
- * does CREATE TEMP TABLE in an extension script, we don't want the temp
- * schema to become part of the extension.)
+ * temporary tables.  Currently, it is used to prevent it from being
+ * linked as a member of any active extension.  (If someone does CREATE
+ * TEMP TABLE in an extension script, we don't want the temp schema to
+ * become part of the extension). And to avoid checking for default ACL
+ * for temp namespace (as it is not necessary).
  * ---------------
  */
 Oid
@@ -49,6 +50,7 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
 	TupleDesc	tupDesc;
 	ObjectAddress myself;
 	int			i;
+	Acl			*nspacl;
 
 	/* sanity checks */
 	if (!nspName)
@@ -60,6 +62,12 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
 				(errcode(ERRCODE_DUPLICATE_SCHEMA),
 				 errmsg("schema \"%s\" already exists", nspName)));
 
+	if (!isTemp)
+		nspacl = get_user_default_acl(ACL_OBJECT_NAMESPACE, ownerId,
+									  InvalidOid);
+	else
+		nspacl = NULL;
+
 	/* initialize nulls and values */
 	for (i = 0; i < Natts_pg_namespace; i++)
 	{
@@ -69,7 +77,10 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
 	namestrcpy(&nname, nspName);
 	values[Anum_pg_namespace_nspname - 1] = NameGetDatum(&nname);
 	values[Anum_pg_namespace_nspowner - 1] = ObjectIdGetDatum(ownerId);
-	nulls[Anum_pg_namespace_nspacl - 1] = true;
+	if (nspacl != NULL)
+		values[Anum_pg_namespace_nspacl - 1] = PointerGetDatum(nspacl);
+	else
+		nulls[Anum_pg_namespace_nspacl - 1] = true;
 
 	nspdesc = heap_open(NamespaceRelationId, RowExclusiveLock);
 	tupDesc = nspdesc->rd_att;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 19dd77d7877e90606026aa02a98c1bd1a846b922..20865c0ee000064e992ba6219e4e81d2e7e9253a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -668,7 +668,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROW ROWS RULE
 
-	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
+	SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SLOT SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P
@@ -7035,6 +7035,7 @@ defacl_privilege_target:
 			| FUNCTIONS		{ $$ = ACL_OBJECT_FUNCTION; }
 			| SEQUENCES		{ $$ = ACL_OBJECT_SEQUENCE; }
 			| TYPES_P		{ $$ = ACL_OBJECT_TYPE; }
+			| SCHEMAS		{ $$ = ACL_OBJECT_NAMESPACE; }
 		;
 
 
@@ -14713,6 +14714,7 @@ unreserved_keyword:
 			| RULE
 			| SAVEPOINT
 			| SCHEMA
+			| SCHEMAS
 			| SCROLL
 			| SEARCH
 			| SECOND_P
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index b41f2b91258f13944a52f9803e626f1d03789a1e..c74153acce209b2c9384a07c676a8d64e3e1fd9e 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -520,7 +520,9 @@ do { \
 		CONVERT_PRIV('X', "EXECUTE");
 	else if (strcmp(type, "LANGUAGE") == 0)
 		CONVERT_PRIV('U', "USAGE");
-	else if (strcmp(type, "SCHEMA") == 0)
+	else if (strcmp(type, "SCHEMA") == 0 ||
+			 strcmp(type, "SCHEMAS") == 0
+			)
 	{
 		CONVERT_PRIV('C', "CREATE");
 		CONVERT_PRIV('U', "USAGE");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba34cc163e97a1534499c5864a50289c88c97c68..262f5539bc6d3d45ff65c080a18eb50f6b82519a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -14295,6 +14295,9 @@ dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_TYPE:
 			type = "TYPES";
 			break;
+		case DEFACLOBJ_NAMESPACE:
+			type = "SCHEMAS";
+			break;
 		default:
 			/* shouldn't get here */
 			exit_horribly(NULL,
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index bcf675208b472a8a30d30f921e60796722b6e950..b0f3e5e34709ea212eeeeb156276ccd7207b62d9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1028,7 +1028,7 @@ listDefaultACLs(const char *pattern)
 	printfPQExpBuffer(&buf,
 			   "SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS \"%s\",\n"
 					  "  n.nspname AS \"%s\",\n"
-					  "  CASE d.defaclobjtype WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' END AS \"%s\",\n"
+					  "  CASE d.defaclobjtype WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' END AS \"%s\",\n"
 					  "  ",
 					  gettext_noop("Owner"),
 					  gettext_noop("Schema"),
@@ -1040,6 +1040,8 @@ listDefaultACLs(const char *pattern)
 					  gettext_noop("function"),
 					  DEFACLOBJ_TYPE,
 					  gettext_noop("type"),
+					  DEFACLOBJ_NAMESPACE,
+					  gettext_noop("schema"),
 					  gettext_noop("Type"));
 
 	printACLColumn(&buf, "d.defaclacl");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f7494065de81cd180545157b5cab874ad2ea98cb..dc2794d48a6998393ade02540f100485e0352186 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2796,7 +2796,7 @@ psql_completion(const char *text, int start, int end)
 		 * to the kinds of objects supported.
 		 */
 		if (HeadMatches3("ALTER","DEFAULT","PRIVILEGES"))
-			COMPLETE_WITH_LIST4("TABLES", "SEQUENCES", "FUNCTIONS", "TYPES");
+			COMPLETE_WITH_LIST5("TABLES", "SEQUENCES", "FUNCTIONS", "TYPES", "SCHEMAS");
 		else
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 42fb224f9d3ba4a52184086bde9eca4d4f235f7d..78bbeb64feb2686f9366e42368d38bf8a38498d6 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -70,5 +70,6 @@ typedef FormData_pg_default_acl *Form_pg_default_acl;
 #define DEFACLOBJ_SEQUENCE		'S'		/* sequence */
 #define DEFACLOBJ_FUNCTION		'f'		/* function */
 #define DEFACLOBJ_TYPE			'T'		/* type */
+#define DEFACLOBJ_NAMESPACE		'n'		/* namespace */
 
 #endif   /* PG_DEFAULT_ACL_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6cd36c7fe3014a63ca479768239fc81c8735679b..cd21a789d57293e1c1f9d4092e4e0a9df777653d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -344,6 +344,7 @@ PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
+PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
 PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index f3499807596aee12babd30c2aa3907a0076929f1..c6e7031beff34f761b0127be9eefb5ff9e5ee1c3 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -1356,6 +1356,64 @@ SELECT has_table_privilege('regress_user1', 'testns.acltest1', 'INSERT'); -- no
 (1 row)
 
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_user1 REVOKE EXECUTE ON FUNCTIONS FROM public;
+ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON SCHEMAS TO regress_user2; -- error
+ERROR:  cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS
+ALTER DEFAULT PRIVILEGES GRANT USAGE ON SCHEMAS TO regress_user2;
+CREATE SCHEMA testns2;
+SELECT has_schema_privilege('regress_user2', 'testns2', 'USAGE'); -- yes
+ has_schema_privilege 
+----------------------
+ t
+(1 row)
+
+SELECT has_schema_privilege('regress_user2', 'testns2', 'CREATE'); -- no
+ has_schema_privilege 
+----------------------
+ f
+(1 row)
+
+ALTER DEFAULT PRIVILEGES REVOKE USAGE ON SCHEMAS FROM regress_user2;
+CREATE SCHEMA testns3;
+SELECT has_schema_privilege('regress_user2', 'testns3', 'USAGE'); -- no
+ has_schema_privilege 
+----------------------
+ f
+(1 row)
+
+SELECT has_schema_privilege('regress_user2', 'testns3', 'CREATE'); -- no
+ has_schema_privilege 
+----------------------
+ f
+(1 row)
+
+ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_user2;
+CREATE SCHEMA testns4;
+SELECT has_schema_privilege('regress_user2', 'testns4', 'USAGE'); -- yes
+ has_schema_privilege 
+----------------------
+ t
+(1 row)
+
+SELECT has_schema_privilege('regress_user2', 'testns4', 'CREATE'); -- yes
+ has_schema_privilege 
+----------------------
+ t
+(1 row)
+
+ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_user2;
+CREATE SCHEMA testns5;
+SELECT has_schema_privilege('regress_user2', 'testns5', 'USAGE'); -- no
+ has_schema_privilege 
+----------------------
+ f
+(1 row)
+
+SELECT has_schema_privilege('regress_user2', 'testns5', 'CREATE'); -- no
+ has_schema_privilege 
+----------------------
+ f
+(1 row)
+
 SET ROLE regress_user1;
 CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql;
 SELECT has_function_privilege('regress_user2', 'testns.foo()', 'EXECUTE'); -- no
@@ -1403,6 +1461,10 @@ SELECT count(*)
 
 DROP SCHEMA testns CASCADE;
 NOTICE:  drop cascades to table testns.acltest1
+DROP SCHEMA testns2 CASCADE;
+DROP SCHEMA testns3 CASCADE;
+DROP SCHEMA testns4 CASCADE;
+DROP SCHEMA testns5 CASCADE;
 SELECT d.*     -- check that entries went away
   FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid
   WHERE nspname IS NULL AND defaclnamespace != 0;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 166e903012b2a71a367e0ee1c65f040840e95188..38215954dadd9389d1d738b951ad1171f61e9035 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -816,6 +816,36 @@ SELECT has_table_privilege('regress_user1', 'testns.acltest1', 'INSERT'); -- no
 
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_user1 REVOKE EXECUTE ON FUNCTIONS FROM public;
 
+ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON SCHEMAS TO regress_user2; -- error
+
+ALTER DEFAULT PRIVILEGES GRANT USAGE ON SCHEMAS TO regress_user2;
+
+CREATE SCHEMA testns2;
+
+SELECT has_schema_privilege('regress_user2', 'testns2', 'USAGE'); -- yes
+SELECT has_schema_privilege('regress_user2', 'testns2', 'CREATE'); -- no
+
+ALTER DEFAULT PRIVILEGES REVOKE USAGE ON SCHEMAS FROM regress_user2;
+
+CREATE SCHEMA testns3;
+
+SELECT has_schema_privilege('regress_user2', 'testns3', 'USAGE'); -- no
+SELECT has_schema_privilege('regress_user2', 'testns3', 'CREATE'); -- no
+
+ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_user2;
+
+CREATE SCHEMA testns4;
+
+SELECT has_schema_privilege('regress_user2', 'testns4', 'USAGE'); -- yes
+SELECT has_schema_privilege('regress_user2', 'testns4', 'CREATE'); -- yes
+
+ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_user2;
+
+CREATE SCHEMA testns5;
+
+SELECT has_schema_privilege('regress_user2', 'testns5', 'USAGE'); -- no
+SELECT has_schema_privilege('regress_user2', 'testns5', 'CREATE'); -- no
+
 SET ROLE regress_user1;
 
 CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql;
@@ -853,6 +883,10 @@ SELECT count(*)
   WHERE nspname = 'testns';
 
 DROP SCHEMA testns CASCADE;
+DROP SCHEMA testns2 CASCADE;
+DROP SCHEMA testns3 CASCADE;
+DROP SCHEMA testns4 CASCADE;
+DROP SCHEMA testns5 CASCADE;
 
 SELECT d.*     -- check that entries went away
   FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid