diff --git a/src/database/permissions/server.rs b/src/database/permissions/server.rs
index 611d770c91b62fdcb7168cfbac97848764da124f..d7af889682d7455d2044f8b667f7f030cc2b37be 100644
--- a/src/database/permissions/server.rs
+++ b/src/database/permissions/server.rs
@@ -37,7 +37,7 @@ bitfield! {
     pub struct ServerPermissions(MSB0 [u32]);
     u32;
     pub get_view, _: 31;
-    pub get_manage_members, _: 30;
+    pub get_manage_roles, _: 30;
     pub get_manage_channels, _: 29;
     pub get_manage_server, _: 28;
     pub get_kick_members, _: 27;
diff --git a/src/notifications/events.rs b/src/notifications/events.rs
index 7d4c5677203fa5a74535a4335b88ef0f45c05e1a..4490d9047ff6bcabcd0f8ed14716a9c80ddb0230 100644
--- a/src/notifications/events.rs
+++ b/src/notifications/events.rs
@@ -129,6 +129,15 @@ pub enum ClientboundNotification {
         id: String,
         user: String,
     },
+    ServerRoleUpdate {
+        id: String,
+        role_id: String,
+        data: JsonValue
+    },
+    ServerRoleDelete {
+        id: String,
+        role_id: String
+    },
 
     UserUpdate {
         id: String,
diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs
index 8eadba743bc58ce650da8fd848bb9a977db08a2c..29f75f9aed47dd8f744f2878c1909dcdc38814cf 100644
--- a/src/routes/channels/mod.rs
+++ b/src/routes/channels/mod.rs
@@ -16,6 +16,8 @@ mod message_fetch;
 mod message_query;
 mod message_query_stale;
 mod message_send;
+mod permissions_set;
+mod permissions_set_default;
 
 pub fn routes() -> Vec<Route> {
     routes![
@@ -35,5 +37,7 @@ pub fn routes() -> Vec<Route> {
         group_add_member::req,
         group_remove_member::req,
         voice_join::req,
+        permissions_set::req,
+        permissions_set_default::req,
     ]
 }
diff --git a/src/routes/channels/permissions_set.rs b/src/routes/channels/permissions_set.rs
new file mode 100644
index 0000000000000000000000000000000000000000..34984a99259c60e0ffc116c4b85a1cf0e40ed91e
--- /dev/null
+++ b/src/routes/channels/permissions_set.rs
@@ -0,0 +1,69 @@
+use mongodb::bson::doc;
+use rocket_contrib::json::Json;
+use serde::{Serialize, Deserialize};
+use validator::Contains;
+
+use crate::database::*;
+use crate::database::permissions::channel::ChannelPermission;
+use crate::notifications::events::ClientboundNotification;
+use crate::util::result::{Error, Result};
+
+#[derive(Serialize, Deserialize)]
+pub struct Data {
+    permissions: u32
+}
+
+#[put("/<target>/permissions/<role>", data = "<data>")]
+pub async fn req(user: User, target: Ref, role: String, data: Json<Data>) -> Result<()> {
+    let target = target.fetch_channel().await?;
+
+    match target {
+        Channel::TextChannel { id, server, mut role_permissions, .. }
+        | Channel::VoiceChannel { id, server, mut role_permissions, .. } => {
+            let target = Ref::from_unchecked(server).fetch_server().await?;
+            let perm = permissions::PermissionCalculator::new(&user)
+                .with_server(&target)
+                .for_server()
+                .await?;
+        
+            if !perm.get_manage_roles() {
+                return Err(Error::MissingPermission);
+            }
+
+            if !target.roles.has_element(&role) {
+                return Err(Error::NotFound);
+            }
+
+            let permissions: u32 = ChannelPermission::View as u32 | data.permissions;
+            
+            get_collection("channels")
+            .update_one(
+                doc! { "_id": &id },
+                doc! {
+                    "$set": {
+                        "role_permissions.".to_owned() + &role: permissions as i32
+                    }
+                },
+                None
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "update_one",
+                with: "channel"
+            })?;
+            
+            role_permissions.insert(role, permissions as i32);
+            ClientboundNotification::ChannelUpdate {
+                id: id.clone(),
+                data: json!({
+                    "role_permissions": role_permissions
+                }),
+                clear: None
+            }
+            .publish(id);
+
+            Ok(())
+        }
+        _ => Err(Error::InvalidOperation)
+    }
+}
diff --git a/src/routes/channels/permissions_set_default.rs b/src/routes/channels/permissions_set_default.rs
new file mode 100644
index 0000000000000000000000000000000000000000..49c8e30f198b5b3bdb2e4f91e9ca05001f230bf8
--- /dev/null
+++ b/src/routes/channels/permissions_set_default.rs
@@ -0,0 +1,97 @@
+use mongodb::bson::doc;
+use rocket_contrib::json::Json;
+use serde::{Serialize, Deserialize};
+
+use crate::database::*;
+use crate::database::permissions::channel::{ ChannelPermission, DEFAULT_PERMISSION_DM };
+use crate::notifications::events::ClientboundNotification;
+use crate::util::result::{Error, Result};
+
+#[derive(Serialize, Deserialize)]
+pub struct Data {
+    permissions: u32
+}
+
+#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
+pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
+    let target = target.fetch_channel().await?;
+
+    match target {
+        Channel::Group { id, owner, .. } => {
+            if user.id == owner {
+                let permissions: u32 = ChannelPermission::View as u32 | (data.permissions & *DEFAULT_PERMISSION_DM);
+                
+                get_collection("channels")
+                    .update_one(
+                        doc! { "_id": &id },
+                        doc! {
+                            "$set": {
+                                "permissions": permissions as i32
+                            }
+                        },
+                        None
+                    )
+                    .await
+                    .map_err(|_| Error::DatabaseError {
+                        operation: "update_one",
+                        with: "channel"
+                    })?;
+                
+                ClientboundNotification::ChannelUpdate {
+                    id: id.clone(),
+                    data: json!({
+                        "permissions": permissions as i32
+                    }),
+                    clear: None
+                }
+                .publish(id);
+                
+                Ok(())
+            } else {
+                Err(Error::MissingPermission)
+            }
+        }
+        Channel::TextChannel { id, server, .. }
+        | Channel::VoiceChannel { id, server, .. } => {
+            let target = Ref::from_unchecked(server).fetch_server().await?;
+            let perm = permissions::PermissionCalculator::new(&user)
+                .with_server(&target)
+                .for_server()
+                .await?;
+        
+            if !perm.get_manage_roles() {
+                return Err(Error::MissingPermission);
+            }
+
+            let permissions: u32 = ChannelPermission::View as u32 | data.permissions;
+            
+            get_collection("channels")
+                .update_one(
+                    doc! { "_id": &id },
+                    doc! {
+                        "$set": {
+                            "default_permissions": permissions as i32
+                        }
+                    },
+                    None
+                )
+                .await
+                .map_err(|_| Error::DatabaseError {
+                    operation: "update_one",
+                    with: "channel"
+                })?;
+            
+            ClientboundNotification::ChannelUpdate {
+                id: id.clone(),
+                data: json!({
+                    "default_permissions": permissions as i32
+                }),
+                clear: None
+            }
+            .publish(id);
+
+            Ok(())
+        }
+        _ => Err(Error::InvalidOperation)
+    }
+}
diff --git a/src/routes/servers/ban_list.rs b/src/routes/servers/ban_list.rs
index 62e8c82ed36c30e69c14736fa726c18737a2506f..5127a7aa4d164a2eec7a3ffb7bdc6a8f146a4d63 100644
--- a/src/routes/servers/ban_list.rs
+++ b/src/routes/servers/ban_list.rs
@@ -14,8 +14,8 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
         .for_server()
         .await?;
 
-    if !perm.get_manage_members() {
-        Err(Error::MissingPermission)?
+    if !perm.get_ban_members() {
+        return Err(Error::MissingPermission);
     }
 
     let mut cursor = get_collection("server_bans")
diff --git a/src/routes/servers/member_remove.rs b/src/routes/servers/member_remove.rs
index 5e76b2b6f9b14ea993f1dcc202e3f5623c3f1676..8fce3a141d3958657bb56b53632ce807b93a6238 100644
--- a/src/routes/servers/member_remove.rs
+++ b/src/routes/servers/member_remove.rs
@@ -12,7 +12,7 @@ pub async fn req(user: User, target: Ref, member: String) -> Result<()> {
         .for_server()
         .await?;
 
-    if !perm.get_manage_members() {
+    if !perm.get_kick_members() {
         return Err(Error::MissingPermission);
     }
 
diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs
index d9d700fc3e9195fcbddf226a63913547ad0b4d34..2f03c3578825a0eb9ae90d190f69b34ccf744172 100644
--- a/src/routes/servers/mod.rs
+++ b/src/routes/servers/mod.rs
@@ -18,6 +18,9 @@ mod ban_remove;
 
 mod invites_fetch;
 
+mod roles_create;
+mod roles_delete;
+
 pub fn routes() -> Vec<Route> {
     routes![
         server_create::req,
@@ -32,6 +35,8 @@ pub fn routes() -> Vec<Route> {
         ban_create::req,
         ban_remove::req,
         ban_list::req,
-        invites_fetch::req
+        invites_fetch::req,
+        roles_create::req,
+        roles_delete::req
     ]
 }
diff --git a/src/routes/servers/roles_create.rs b/src/routes/servers/roles_create.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6453642a5a0d0a905ca826e7c6f9b28953ae5c45
--- /dev/null
+++ b/src/routes/servers/roles_create.rs
@@ -0,0 +1,75 @@
+use crate::database::*;
+use crate::notifications::events::ClientboundNotification;
+use crate::util::result::{Error, Result};
+
+use ulid::Ulid;
+use mongodb::bson::doc;
+use validator::Validate;
+use serde::{Serialize, Deserialize};
+use rocket_contrib::json::{Json, JsonValue};
+
+#[derive(Validate, Serialize, Deserialize)]
+pub struct Data {
+    #[validate(length(min = 1, max = 32))]
+    name: String
+}
+
+#[post("/<target>/roles", data = "<data>")]
+pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<JsonValue> {
+    let data = data.into_inner();
+    data.validate()
+        .map_err(|error| Error::FailedValidation { error })?;
+    
+    let target = target.fetch_server().await?;
+
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&target)
+        .for_server()
+        .await?;
+
+    if !perm.get_manage_roles() {
+        Err(Error::MissingPermission)?
+    }
+
+    let id = Ulid::new().to_string();
+    let perm_tuple = (
+        *permissions::server::DEFAULT_PERMISSION as i32,
+        *permissions::channel::DEFAULT_PERMISSION_SERVER as i32
+    );
+
+    get_collection("servers")
+        .update_one(
+            doc! {
+                "_id": &id
+            },
+            doc! {
+                "$set": {
+                    "roles.".to_owned() + &id: {
+                        "name": &data.name,
+                        "permissions": [
+                            &perm_tuple.0,
+                            &perm_tuple.1
+                        ]
+                    } 
+                }
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "update_one",
+            with: "servers"
+        })?;
+    
+    ClientboundNotification::ServerRoleUpdate {
+        id: target.id.clone(),
+        role_id: id.clone(),
+        data: json!({
+            "name": data.name,
+            "permissions": &perm_tuple
+        })
+    }
+    .publish(target.id);
+
+    Ok(json!({ "id": id, "permissions": perm_tuple }))
+}
diff --git a/src/routes/servers/roles_delete.rs b/src/routes/servers/roles_delete.rs
new file mode 100644
index 0000000000000000000000000000000000000000..baab78aaaf4f864f9a34ff7d50053e5d1ca07b4f
--- /dev/null
+++ b/src/routes/servers/roles_delete.rs
@@ -0,0 +1,48 @@
+use crate::database::*;
+use crate::notifications::events::ClientboundNotification;
+use crate::util::result::{Error, Result};
+
+use mongodb::bson::doc;
+
+#[delete("/<target>/roles/<role_id>")]
+pub async fn req(user: User, target: Ref, role_id: String) -> Result<()> {
+    let target = target.fetch_server().await?;
+
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&target)
+        .for_server()
+        .await?;
+
+    if !perm.get_manage_roles() {
+        Err(Error::MissingPermission)?
+    }
+
+    get_collection("servers")
+        .update_one(
+            doc! {
+                "_id": &role_id
+            },
+            doc! {
+                "$unset": {
+                    "roles.".to_owned() + &role_id: 1
+                }
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "update_one",
+            with: "servers"
+        })?;
+    
+    // remove role from members
+    // remove role from channels
+    
+    ClientboundNotification::ServerRoleDelete {
+        id: target.id.clone(),
+        role_id
+    }
+    .publish(target.id);
+
+    Ok(())
+}
diff --git a/src/routes/servers/server_edit.rs b/src/routes/servers/server_edit.rs
index de85a05fafede1b6e23d67c2298d4da16430975b..c3753963b1e9527eb4d83f2fa2dfe4586971cd21 100644
--- a/src/routes/servers/server_edit.rs
+++ b/src/routes/servers/server_edit.rs
@@ -15,6 +15,8 @@ pub struct Data {
     description: Option<String>,
     icon: Option<String>,
     banner: Option<String>,
+    categories: Option<Vec<Category>>,
+    system_messages: Option<SystemMessageChannels>,
     remove: Option<RemoveServerField>,
 }
 
@@ -24,7 +26,7 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
     data.validate()
         .map_err(|error| Error::FailedValidation { error })?;
 
-    if data.name.is_none() && data.icon.is_none() && data.banner.is_none() && data.remove.is_none()
+    if data.name.is_none() && data.icon.is_none() && data.banner.is_none() && data.remove.is_none() && data.categories.is_none() && data.system_messages.is_none()
     {
         return Ok(());
     }
@@ -95,6 +97,14 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
         remove_banner = true;
     }
 
+    if let Some(categories) = &data.categories {
+        set.insert("categories", to_document(&categories).map_err(|_| Error::DatabaseError { operation: "to_document", with: "categories" })?);
+    }
+
+    if let Some(system_messages) = &data.system_messages {
+        set.insert("system_messages", to_document(&system_messages).map_err(|_| Error::DatabaseError { operation: "to_document", with: "system_messages" })?);
+    }
+
     let mut operations = doc! {};
     if set.len() > 0 {
         operations.insert("$set", &set);
diff --git a/src/util/result.rs b/src/util/result.rs
index 218a5369fda0655fc1f37af79637bbc92d6d2df1..17d3dd4d19be315d1accff8a19c5d42a1d495dac 100644
--- a/src/util/result.rs
+++ b/src/util/result.rs
@@ -41,6 +41,7 @@ pub enum Error {
 
     // ? Server related errors.
     UnknownServer,
+    InvalidRole,
     Banned,
 
     // ? General errors.
@@ -94,6 +95,7 @@ impl<'r> Responder<'r, 'static> for Error {
             Error::NotInGroup => Status::NotFound,
 
             Error::UnknownServer => Status::NotFound,
+            Error::InvalidRole => Status::NotFound,
             Error::Banned => Status::Forbidden,
 
             Error::FailedValidation { .. } => Status::UnprocessableEntity,