diff --git a/src/database/entities/server.rs b/src/database/entities/server.rs
index ff1d48e2ca9541be82406d4d931109937ea36d2d..c9d5b6c95d407a08353b29d85bef473a2d9e8f68 100644
--- a/src/database/entities/server.rs
+++ b/src/database/entities/server.rs
@@ -42,6 +42,8 @@ pub struct Server {
     pub owner: String,
 
     pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub description: Option<String>,
     pub channels: Vec<String>,
 
     #[serde(skip_serializing_if = "Option::is_none")]
diff --git a/src/notifications/events.rs b/src/notifications/events.rs
index a78a312d0f6bbc8752ba105540fbbf694aff4e35..06a19b5ff9ac674aeddd636b7ac41ace5943eb70 100644
--- a/src/notifications/events.rs
+++ b/src/notifications/events.rs
@@ -40,12 +40,14 @@ pub enum RemoveUserField {
 #[derive(Serialize, Deserialize, Debug)]
 pub enum RemoveChannelField {
     Icon,
+    Description,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
 pub enum RemoveServerField {
     Icon,
     Banner,
+    Description,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
diff --git a/src/routes/channels/edit_channel.rs b/src/routes/channels/edit_channel.rs
index ef428182ac94cd289e9ed95ef16440189f8bac0c..afafdbb61fc850197b8bd18e70121f959a3bb4b8 100644
--- a/src/routes/channels/edit_channel.rs
+++ b/src/routes/channels/edit_channel.rs
@@ -57,6 +57,9 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
                         unset.insert("icon", 1);
                         remove_icon = true;
                     }
+                    RemoveChannelField::Description => {
+                        unset.insert("description", 1);
+                    }
                 }
             }
 
diff --git a/src/routes/servers/server_create.rs b/src/routes/servers/server_create.rs
index cb2af669f33a7d8b79549eb7b468a8aefbc02498..1142cd599249ae5fd2072397c0548badd2fd3093 100644
--- a/src/routes/servers/server_create.rs
+++ b/src/routes/servers/server_create.rs
@@ -11,6 +11,8 @@ use validator::Validate;
 pub struct Data {
     #[validate(length(min = 1, max = 32))]
     name: String,
+    #[validate(length(min = 0, max = 1024))]
+    description: Option<String>,
     // Maximum length of 36 allows both ULIDs and UUIDs.
     #[validate(length(min = 1, max = 36))]
     nonce: String,
@@ -48,6 +50,7 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
         owner: user.id.clone(),
 
         name: info.name,
+        description: info.description,
         channels: vec![cid.clone()],
 
         icon: None,
diff --git a/src/routes/servers/server_edit.rs b/src/routes/servers/server_edit.rs
index 4fe9e9db4128b106556cbeb4f780636ff2d8a08b..de85a05fafede1b6e23d67c2298d4da16430975b 100644
--- a/src/routes/servers/server_edit.rs
+++ b/src/routes/servers/server_edit.rs
@@ -10,8 +10,9 @@ 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>,
+    #[validate(length(min = 0, max = 1024))]
+    description: Option<String>,
     icon: Option<String>,
     banner: Option<String>,
     remove: Option<RemoveServerField>,
@@ -53,6 +54,9 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
                 unset.insert("banner", 1);
                 remove_banner = true;
             }
+            RemoveServerField::Description => {
+                unset.insert("description", 1);
+            }
         }
     }
 
@@ -60,6 +64,10 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
         set.insert("name", name);
     }
 
+    if let Some(description) = &data.description {
+        set.insert("description", description);
+    }
+
     if let Some(attachment_id) = &data.icon {
         let attachment = File::find_and_use(&attachment_id, "icons", "object", &target.id).await?;
         set.insert(