diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs
index f9c1605512cf71526fcd97d88c134a972ae65ea1..7833ccdd6c8ae708c53a8aeb1cb84567870bf5b7 100644
--- a/src/database/entities/user.rs
+++ b/src/database/entities/user.rs
@@ -1,4 +1,5 @@
 use futures::StreamExt;
+use mongodb::bson::Document;
 use mongodb::options::{Collation, FindOneOptions};
 use mongodb::{
     bson::{doc, from_document},
@@ -223,4 +224,31 @@ impl User {
 
         Ok(users)
     }
+
+    /// Utility function to get all the server IDs the user is in.
+    pub async fn fetch_server_ids(&self) -> Result<Vec<String>> {
+        Ok(get_collection("server_members")
+            .find(
+                doc! {
+                    "_id.user": &self.id
+                },
+                None,
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "find",
+                with: "server_members",
+            })?
+            .filter_map(async move |s| s.ok())
+            .collect::<Vec<Document>>()
+            .await
+            .into_iter()
+            .filter_map(|x| {
+                x.get_document("_id")
+                    .ok()
+                    .map(|i| i.get_str("server").ok().map(|x| x.to_string()))
+            })
+            .flatten()
+            .collect::<Vec<String>>())
+    }
 }
diff --git a/src/database/permissions/mod.rs b/src/database/permissions/mod.rs
index 376a440896e1dc5e257c3b0f64669bb490a52112..9219906ab710b75fd516dfa1034ccd6438edcceb 100644
--- a/src/database/permissions/mod.rs
+++ b/src/database/permissions/mod.rs
@@ -1,6 +1,7 @@
 pub use crate::database::*;
 
 pub mod channel;
+pub mod server;
 pub mod user;
 
 pub use user::get_relationship;
diff --git a/src/database/permissions/server.rs b/src/database/permissions/server.rs
index 94166d70b0758ed2a76085981c169a515d6e2b74..b2321006395770c1579a5c8a226a16c97319ef86 100644
--- a/src/database/permissions/server.rs
+++ b/src/database/permissions/server.rs
@@ -1,4 +1,3 @@
-use crate::database::*;
 use crate::util::result::Result;
 
 use super::PermissionCalculator;
@@ -10,6 +9,7 @@ use std::ops;
 #[repr(u32)]
 pub enum ServerPermission {
     View = 1,
+    ManageServer = 8,
 }
 
 impl_op_ex!(+ |a: &ServerPermission, b: &ServerPermission| -> u32 { *a as u32 | *b as u32 });
@@ -19,6 +19,7 @@ bitfield! {
     pub struct ServerPermissions(MSB0 [u32]);
     u32;
     pub get_view, _: 31;
+    pub get_manage_server, _: 28;
 }
 
 impl<'a> PermissionCalculator<'a> {
@@ -29,14 +30,14 @@ impl<'a> PermissionCalculator<'a> {
             unreachable!()
         };
 
-        if &self.perspective.id == server.owner {
+        if self.perspective.id == server.owner {
             Ok(u32::MAX)
         } else {
             Ok(ServerPermission::View as u32)
         }
     }
 
-    pub async fn for_server(self) -> Result<ChannelPermissions<[u32; 1]>> {
+    pub async fn for_server(self) -> Result<ServerPermissions<[u32; 1]>> {
         Ok(ServerPermissions([self.calculate_server().await?]))
     }
 }
diff --git a/src/notifications/payload.rs b/src/notifications/payload.rs
index 84daf3ee2547788101b7c3af99265cf39fcf7e3b..ac5c45395b27e9ddeafd2a1be9aa892eb5985a44 100644
--- a/src/notifications/payload.rs
+++ b/src/notifications/payload.rs
@@ -6,7 +6,7 @@ use crate::{
     util::result::{Error, Result},
 };
 use futures::StreamExt;
-use mongodb::bson::{doc, from_document, Document};
+use mongodb::bson::{doc, from_document};
 
 pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
     let mut user_ids: HashSet<String> = HashSet::new();
@@ -19,30 +19,7 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
         );
     }
 
-    let server_ids = get_collection("server_members")
-        .find(
-            doc! {
-                "_id.user": &user.id
-            },
-            None,
-        )
-        .await
-        .map_err(|_| Error::DatabaseError {
-            operation: "find",
-            with: "server_members",
-        })?
-        .filter_map(async move |s| s.ok())
-        .collect::<Vec<Document>>()
-        .await
-        .into_iter()
-        .filter_map(|x| {
-            x.get_document("_id")
-                .ok()
-                .map(|i| i.get_str("server").ok().map(|x| x.to_string()))
-        })
-        .flatten()
-        .collect::<Vec<String>>();
-
+    let server_ids = user.fetch_server_ids().await?;
     let mut cursor = get_collection("servers")
         .find(
             doc! {
diff --git a/src/notifications/subscriptions.rs b/src/notifications/subscriptions.rs
index df07afe35abef8f72454cf8f5dbf4510e3a99f40..fc770f727ef8bc84dafbb293c18e4029a36fb170 100644
--- a/src/notifications/subscriptions.rs
+++ b/src/notifications/subscriptions.rs
@@ -45,7 +45,14 @@ pub async fn generate_subscriptions(user: &User) -> Result<(), String> {
         }
     }
 
-    // ! FIXME: fetch memberships for servers
+    let server_ids = user
+        .fetch_server_ids()
+        .await
+        .map_err(|_| "Failed to fetch memberships.".to_string())?;
+
+    for id in server_ids {
+        hive.subscribe(user.id.clone(), id)?;
+    }
 
     Ok(())
 }
diff --git a/src/routes/channels/edit_channel.rs b/src/routes/channels/edit_channel.rs
index 5b2fbd25c1beef8c79b9423439cfeba15b73a78d..5acf4b0c0c9f4097383029defd0651efd8ead66b 100644
--- a/src/routes/channels/edit_channel.rs
+++ b/src/routes/channels/edit_channel.rs
@@ -69,7 +69,7 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
 
             if let Some(attachment_id) = &data.icon {
                 let attachment =
-                    File::find_and_use(&attachment_id, "icons", "object", &user.id).await?;
+                    File::find_and_use(&attachment_id, "icons", "object", target.id()).await?;
                 set.insert(
                     "icon",
                     to_document(&attachment).map_err(|_| Error::DatabaseError {
diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs
index 74d3b848cd7c3dfd4e09cda0ebd628dbddc4eed7..a6d64cd770dc078b526996187c44da1e80b91049 100644
--- a/src/routes/servers/mod.rs
+++ b/src/routes/servers/mod.rs
@@ -2,7 +2,8 @@ use rocket::Route;
 
 mod server_create;
 mod server_delete;
+mod server_edit;
 
 pub fn routes() -> Vec<Route> {
-    routes![server_create::req, server_delete::req]
+    routes![server_create::req, server_delete::req, server_edit::req]
 }
diff --git a/src/routes/servers/server_delete.rs b/src/routes/servers/server_delete.rs
index 91d8ec7f467932dbb2c3352bf4657463391a7a48..983fcaa9b4c0ed0591e18a486204f934535e564c 100644
--- a/src/routes/servers/server_delete.rs
+++ b/src/routes/servers/server_delete.rs
@@ -1,5 +1,5 @@
-use crate::util::result::{Error, Result};
 use crate::database::*;
+use crate::util::result::{Error, Result};
 
 use mongodb::bson::doc;
 
@@ -15,5 +15,8 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
         Err(Error::MissingPermission)?
     }*/
 
+    // ! FIXME: either delete server if owner
+    // ! OR leave server if member
+
     target.delete().await
 }
diff --git a/src/routes/servers/server_edit.rs b/src/routes/servers/server_edit.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4fe9e9db4128b106556cbeb4f780636ff2d8a08b
--- /dev/null
+++ b/src/routes/servers/server_edit.rs
@@ -0,0 +1,131 @@
+use crate::notifications::events::ClientboundNotification;
+use crate::util::result::{Error, Result};
+use crate::{database::*, notifications::events::RemoveServerField};
+
+use mongodb::bson::{doc, to_document};
+use rocket_contrib::json::Json;
+use serde::{Deserialize, Serialize};
+use validator::Validate;
+
+#[derive(Validate, Serialize, Deserialize)]
+pub struct Data {
+    #[validate(length(min = 1, max = 32))]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    name: Option<String>,
+    icon: Option<String>,
+    banner: Option<String>,
+    remove: Option<RemoveServerField>,
+}
+
+#[patch("/<target>", data = "<data>")]
+pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
+    let data = data.into_inner();
+    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()
+    {
+        return Ok(());
+    }
+
+    let target = target.fetch_server().await?;
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&target)
+        .for_server()
+        .await?;
+
+    if !perm.get_manage_server() {
+        Err(Error::MissingPermission)?
+    }
+
+    let mut set = doc! {};
+    let mut unset = doc! {};
+
+    let mut remove_icon = false;
+    let mut remove_banner = false;
+    if let Some(remove) = &data.remove {
+        match remove {
+            RemoveServerField::Icon => {
+                unset.insert("icon", 1);
+                remove_icon = true;
+            }
+            RemoveServerField::Banner => {
+                unset.insert("banner", 1);
+                remove_banner = true;
+            }
+        }
+    }
+
+    if let Some(name) = &data.name {
+        set.insert("name", name);
+    }
+
+    if let Some(attachment_id) = &data.icon {
+        let attachment = File::find_and_use(&attachment_id, "icons", "object", &target.id).await?;
+        set.insert(
+            "icon",
+            to_document(&attachment).map_err(|_| Error::DatabaseError {
+                operation: "to_document",
+                with: "attachment",
+            })?,
+        );
+
+        remove_icon = true;
+    }
+
+    if let Some(attachment_id) = &data.banner {
+        let attachment =
+            File::find_and_use(&attachment_id, "banners", "server", &target.id).await?;
+        set.insert(
+            "banner",
+            to_document(&attachment).map_err(|_| Error::DatabaseError {
+                operation: "to_document",
+                with: "attachment",
+            })?,
+        );
+
+        remove_banner = true;
+    }
+
+    let mut operations = doc! {};
+    if set.len() > 0 {
+        operations.insert("$set", &set);
+    }
+
+    if unset.len() > 0 {
+        operations.insert("$unset", unset);
+    }
+
+    if operations.len() > 0 {
+        get_collection("servers")
+            .update_one(doc! { "_id": &target.id }, operations, None)
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "update_one",
+                with: "server",
+            })?;
+    }
+
+    ClientboundNotification::ServerUpdate {
+        id: target.id.clone(),
+        data: json!(set),
+        clear: data.remove,
+    }
+    .publish(target.id.clone());
+
+    let Server { icon, banner, .. } = target;
+
+    if remove_icon {
+        if let Some(old_icon) = icon {
+            old_icon.delete().await?;
+        }
+    }
+
+    if remove_banner {
+        if let Some(old_banner) = banner {
+            old_banner.delete().await?;
+        }
+    }
+
+    Ok(())
+}