diff --git a/client/components/header/index.tsx b/client/components/header/index.tsx
index c1d58440..7b5efdef 100644
--- a/client/components/header/index.tsx
+++ b/client/components/header/index.tsx
@@ -88,12 +88,12 @@ const Header = () => {
 					value: "yours",
 					href: "/mine"
 				},
-				// {
-				//     name: 'settings',
-				//     icon: ,
-				//     value: 'settings',
-				//     href: '/settings'
-				// },
+				{
+					name: 'settings',
+					icon: ,
+					value: 'settings',
+					href: '/settings'
+				},
 				{
 					name: "sign out",
 					icon: ,
diff --git a/client/components/settings/index.tsx b/client/components/settings/index.tsx
new file mode 100644
index 00000000..802cb1ea
--- /dev/null
+++ b/client/components/settings/index.tsx
@@ -0,0 +1,23 @@
+import { Fieldset, Text, Divider, Note, Input, Textarea, Button } from "@geist-ui/core"
+import Password from "./sections/password"
+import Profile from "./sections/profile"
+import SettingsGroup from "./settings-group"
+
+const SettingsPage = () => {
+    return (
)
+}
+
+export default SettingsPage
\ No newline at end of file
diff --git a/client/components/settings/sections/password.tsx b/client/components/settings/sections/password.tsx
new file mode 100644
index 00000000..4f46a3c1
--- /dev/null
+++ b/client/components/settings/sections/password.tsx
@@ -0,0 +1,97 @@
+import { Input, Button, useToasts } from "@geist-ui/core"
+import Cookies from "js-cookie"
+import { useState } from "react"
+
+const Password = () => {
+    const [password, setPassword] = useState('')
+    const [newPassword, setNewPassword] = useState('')
+    const [confirmPassword, setConfirmPassword] = useState('')
+
+    const { setToast } = useToasts()
+
+    const handlePasswordChange = (e: React.ChangeEvent) => {
+        setPassword(e.target.value)
+    }
+
+    const handleNewPasswordChange = (e: React.ChangeEvent) => {
+        setNewPassword(e.target.value)
+    }
+
+    const handleConfirmPasswordChange = (e: React.ChangeEvent) => {
+        setConfirmPassword(e.target.value)
+    }
+
+    const onSubmit = async (e: React.FormEvent) => {
+        e.preventDefault()
+        if (!password || !newPassword || !confirmPassword) {
+            setToast({
+                text: "Please fill out all fields",
+                type: "error",
+            })
+        }
+
+        if (newPassword !== confirmPassword) {
+            setToast({
+                text: "New password and confirm password do not match",
+                type: "error",
+            })
+        }
+
+        const res = await fetch("/server-api/auth/change-password", {
+            method: "PUT",
+            headers: {
+                "Content-Type": "application/json",
+                "Authorization": `Bearer ${Cookies.get("drift-token")}`,
+            },
+            body: JSON.stringify({
+                oldPassword: password,
+                newPassword,
+            }),
+        })
+
+
+        if (res.status === 200) {
+            setToast({
+                text: "Password updated successfully",
+                type: "success",
+            })
+            setPassword('')
+            setNewPassword('')
+            setConfirmPassword('')
+        } else {
+            const data = await res.json()
+
+            setToast({
+                text: data.error ?? "Failed to update password",
+                type: "error",
+            })
+        }
+    }
+
+    return (
+        )
+}
+
+export default Password
\ No newline at end of file
diff --git a/client/components/settings/sections/profile.tsx b/client/components/settings/sections/profile.tsx
new file mode 100644
index 00000000..ed95851a
--- /dev/null
+++ b/client/components/settings/sections/profile.tsx
@@ -0,0 +1,100 @@
+import { Note, Input, Textarea, Button, useToasts } from "@geist-ui/core"
+import useUserData from "@lib/hooks/use-user-data"
+import Cookies from "js-cookie"
+import { useEffect, useState } from "react"
+
+const Profile = () => {
+    const user = useUserData()
+    const [name, setName] = useState()
+    const [email, setEmail] = useState()
+    const [bio, setBio] = useState()
+
+    useEffect(() => {
+        console.log(user)
+        if (user?.displayName) setName(user.displayName)
+        if (user?.email) setEmail(user.email)
+        if (user?.bio) setBio(user.bio)
+    }, [user])
+
+    const { setToast } = useToasts()
+
+    const handleNameChange = (e: React.ChangeEvent) => {
+        setName(e.target.value)
+    }
+
+    const handleEmailChange = (e: React.ChangeEvent) => {
+        setEmail(e.target.value)
+    }
+
+    const handleBioChange = (e: React.ChangeEvent) => {
+        setBio(e.target.value)
+    }
+
+    const onSubmit = async (e: React.FormEvent) => {
+        e.preventDefault()
+        if (!name && !email && !bio) {
+            setToast({
+                text: "Please fill out at least one field",
+                type: "error",
+            })
+            return
+        }
+
+        const data = {
+            displayName: name,
+            email,
+            bio,
+        }
+
+        const res = await fetch("/server-api/user/profile", {
+            method: "PUT",
+            headers: {
+                "Content-Type": "application/json",
+                "Authorization": `Bearer ${Cookies.get("drift-token")}`,
+            },
+            body: JSON.stringify(data),
+        })
+
+        if (res.status === 200) {
+            setToast({
+                text: "Profile updated",
+                type: "success",
+            })
+        } else {
+            setToast({
+                text: "Something went wrong updating your profile",
+                type: "error",
+            })
+        }
+    }
+
+    return (<>
+        
+            This information will be publicly available on your profile
+        
+        >)
+}
+
+export default Profile
\ No newline at end of file
diff --git a/client/components/settings/settings-group/index.tsx b/client/components/settings/settings-group/index.tsx
new file mode 100644
index 00000000..adf9affd
--- /dev/null
+++ b/client/components/settings/settings-group/index.tsx
@@ -0,0 +1,24 @@
+import { Fieldset, Text, Divider } from "@geist-ui/core"
+import styles from './settings-group.module.css'
+
+type Props = {
+    title: string,
+    children: React.ReactNode | React.ReactNode[],
+}
+
+const SettingsGroup = ({
+    title,
+    children,
+}: Props) => {
+    return 
+}
+
+export default SettingsGroup
diff --git a/client/components/settings/settings-group/settings-group.module.css b/client/components/settings/settings-group/settings-group.module.css
new file mode 100644
index 00000000..bb12c0b4
--- /dev/null
+++ b/client/components/settings/settings-group/settings-group.module.css
@@ -0,0 +1,4 @@
+.content form label {
+	display: block;
+	margin-bottom: 5px;
+}
diff --git a/client/lib/hooks/use-user-data.ts b/client/lib/hooks/use-user-data.ts
index ce0c9e00..455c26fc 100644
--- a/client/lib/hooks/use-user-data.ts
+++ b/client/lib/hooks/use-user-data.ts
@@ -19,7 +19,7 @@ const useUserData = () => {
 	useEffect(() => {
 		if (authToken) {
 			const fetchUser = async () => {
-				const response = await fetch(`/server-api/users/self`, {
+				const response = await fetch(`/server-api/user/self`, {
 					headers: {
 						Authorization: `Bearer ${authToken}`
 					}
diff --git a/client/lib/types.d.ts b/client/lib/types.d.ts
index 3e3006f1..e7d5ec7a 100644
--- a/client/lib/types.d.ts
+++ b/client/lib/types.d.ts
@@ -34,4 +34,7 @@ type User = {
 	posts?: Post[]
 	role: "admin" | "user" | ""
 	createdAt: string
+	displayName?: string
+	bio?: string
+	email?: string
 }
diff --git a/client/pages/settings.tsx b/client/pages/settings.tsx
new file mode 100644
index 00000000..ce40051e
--- /dev/null
+++ b/client/pages/settings.tsx
@@ -0,0 +1,15 @@
+import { Button, Divider, Text, Fieldset, Input, Page, Note, Textarea } from "@geist-ui/core"
+import PageSeo from "@components/page-seo"
+import styles from "@styles/Home.module.css"
+import SettingsPage from "@components/settings"
+
+const Settings = () => (
+    
+        
+        
+            
+        
+    
+)
+
+export default Settings
diff --git a/server/src/app.ts b/server/src/app.ts
index 635b3d20..cbb60c92 100644
--- a/server/src/app.ts
+++ b/server/src/app.ts
@@ -1,7 +1,7 @@
 import * as express from "express"
 import * as bodyParser from "body-parser"
 import * as errorhandler from "strong-error-handler"
-import { posts, users, auth, files, admin, health } from "@routes/index"
+import { posts, user, auth, files, admin, health } from "@routes/index"
 import { errors } from "celebrate"
 import secretKey from "@lib/middleware/secret-key"
 import markdown from "@lib/render-markdown"
@@ -14,7 +14,7 @@ app.use(bodyParser.json({ limit: "5mb" }))
 
 app.use("/auth", auth)
 app.use("/posts", posts)
-app.use("/users", users)
+app.use("/user", user)
 app.use("/files", files)
 app.use("/admin", admin)
 app.use("/health", health)
diff --git a/server/src/lib/models/User.ts b/server/src/lib/models/User.ts
index b4653137..06f42c6f 100644
--- a/server/src/lib/models/User.ts
+++ b/server/src/lib/models/User.ts
@@ -61,4 +61,13 @@ export class User extends Model {
 
 	@Column
 	role!: string
+
+	@Column
+	email?: string
+
+	@Column
+	displayName?: string
+
+	@Column
+	bio?: string
 }
diff --git a/server/src/migrations/09_add_more_user_settings.ts b/server/src/migrations/09_add_more_user_settings.ts
new file mode 100644
index 00000000..4624306e
--- /dev/null
+++ b/server/src/migrations/09_add_more_user_settings.ts
@@ -0,0 +1,26 @@
+"use strict"
+import { DataTypes } from "sequelize"
+import type { Migration } from "../database"
+
+export const up: Migration = async ({ context: queryInterface }) =>
+    Promise.all([
+        queryInterface.addColumn("users", "email", {
+            type: DataTypes.STRING,
+            allowNull: true
+        }),
+        queryInterface.addColumn("users", "displayName", {
+            type: DataTypes.STRING,
+            allowNull: true,
+        }),
+        queryInterface.addColumn("users", "bio", {
+            type: DataTypes.STRING,
+            allowNull: true,
+        }),
+    ])
+
+export const down: Migration = async ({ context: queryInterface }) =>
+    Promise.all([
+        queryInterface.removeColumn("users", "email"),
+        queryInterface.removeColumn("users", "displayName"),
+        queryInterface.removeColumn("users", "bio"),
+    ])
diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts
index f25c3557..742adcd2 100644
--- a/server/src/routes/auth.ts
+++ b/server/src/routes/auth.ts
@@ -4,7 +4,7 @@ import { User } from "@lib/models/User"
 import { AuthToken } from "@lib/models/AuthToken"
 import { sign, verify } from "jsonwebtoken"
 import config from "@lib/config"
-import jwt from "@lib/middleware/jwt"
+import jwt, { UserJwtRequest } from "@lib/middleware/jwt"
 import { celebrate, Joi } from "celebrate"
 import secretKey from "@lib/middleware/secret-key"
 
@@ -194,3 +194,38 @@ auth.post("/signout", secretKey, async (req, res, next) => {
 		next(e)
 	}
 })
+
+auth.put("/change-password",
+	jwt,
+	celebrate({
+		body: {
+			oldPassword: Joi.string().required().min(6).max(128),
+			newPassword: Joi.string().required().min(6).max(128)
+		}
+	}),
+	async (req: UserJwtRequest, res, next) => {
+		try {
+			const user = await User.findOne({ where: { id: req.user?.id } })
+			if (!user) {
+				return res.sendStatus(401)
+			}
+
+			const password_valid = await compare(req.body.oldPassword, user.password)
+			if (!password_valid) {
+				res.status(401).json({
+					error: "Old password is incorrect"
+				})
+			}
+
+			const salt = await genSalt(10)
+			user.password = await hash(req.body.newPassword, salt)
+			user.save()
+
+			res.status(200).json({
+				message: "Password changed"
+			})
+		} catch (e) {
+			next(e)
+		}
+	}
+)
diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts
index 3b5e7211..72605d17 100644
--- a/server/src/routes/index.ts
+++ b/server/src/routes/index.ts
@@ -1,6 +1,6 @@
 export { auth } from "./auth"
 export { posts } from "./posts"
-export { users } from "./users"
+export { user } from "./user"
 export { files } from "./files"
 export { admin } from "./admin"
 export { health } from "./health"
diff --git a/server/src/routes/user.ts b/server/src/routes/user.ts
new file mode 100644
index 00000000..29ea6c28
--- /dev/null
+++ b/server/src/routes/user.ts
@@ -0,0 +1,76 @@
+import { Router } from "express"
+import jwt, { UserJwtRequest } from "@lib/middleware/jwt"
+import { User } from "@lib/models/User"
+import { celebrate, Joi } from "celebrate"
+
+export const user = Router()
+
+user.get("/self", jwt, async (req: UserJwtRequest, res, next) => {
+	const error = () =>
+		res.status(401).json({
+			message: "Unauthorized"
+		})
+
+	try {
+		if (!req.user) {
+			return error()
+		}
+
+		const user = await User.findByPk(req.user?.id, {
+			attributes: {
+				exclude: ["password"]
+			}
+		})
+		if (!user) {
+			return error()
+		}
+		res.json(user)
+	} catch (error) {
+		next(error)
+	}
+})
+
+user.put("/profile",
+	jwt,
+	celebrate({
+		body: {
+			displayName: Joi.string().optional().allow(""),
+			bio: Joi.string().optional().allow(""),
+			email: Joi.string().optional().email().allow(""),
+		}
+	}),
+	async (req: UserJwtRequest, res, next) => {
+		const error = () =>
+			res.status(401).json({
+				message: "Unauthorized"
+			})
+
+		try {
+			if (!req.user) {
+				return error()
+			}
+
+			const user = await User.findByPk(req.user?.id)
+			if (!user) {
+				return error()
+			}
+			console.log(req.body)
+			const { displayName, bio, email } = req.body
+			const toUpdate = {} as any
+			if (displayName) {
+				toUpdate.displayName = displayName
+			}
+			if (bio) {
+				toUpdate.bio = bio
+			}
+			if (email) {
+				toUpdate.email = email
+			}
+
+			await user.update(toUpdate)
+			res.json(user)
+		} catch (error) {
+			next(error)
+		}
+	}
+)
diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts
deleted file mode 100644
index a74a443b..00000000
--- a/server/src/routes/users.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Router } from "express"
-import jwt, { UserJwtRequest } from "@lib/middleware/jwt"
-import { User } from "@lib/models/User"
-
-export const users = Router()
-
-users.get("/self", jwt, async (req: UserJwtRequest, res, next) => {
-	const error = () =>
-		res.status(401).json({
-			message: "Unauthorized"
-		})
-
-	try {
-		if (!req.user) {
-			return error()
-		}
-
-		const user = await User.findByPk(req.user?.id, {
-			attributes: {
-				exclude: ["password"]
-			}
-		})
-		if (!user) {
-			return error()
-		}
-
-		res.json(user)
-	} catch (error) {
-		next(error)
-	}
-})