diff --git a/apps/web/src/actions/admin/registration-actions.ts b/apps/web/src/actions/admin/registration-actions.ts
index 9388bbba..8a953100 100644
--- a/apps/web/src/actions/admin/registration-actions.ts
+++ b/apps/web/src/actions/admin/registration-actions.ts
@@ -9,6 +9,10 @@ const defaultRegistrationToggleSchema = z.object({
 	enabled: z.boolean(),
 });
 
+const defaultRSVPLimitSchema = z.object({
+	rsvpLimit: z.number()
+});
+
 export const toggleRegistrationEnabled = adminAction(
 	defaultRegistrationToggleSchema,
 	async ({ enabled }, { user, userId }) => {
@@ -44,3 +48,12 @@ export const toggleRSVPs = adminAction(
 		return { success: true, statusSet: enabled };
 	},
 );
+
+export const setRSVPLimit = adminAction(
+	defaultRSVPLimitSchema,
+	async ({ rsvpLimit }) => {
+		await kv.set("config:registration:maxRSVPs", rsvpLimit);
+		revalidatePath("/admin/toggles/registration");
+		return { success: true, statusSet: rsvpLimit };
+	}
+);
diff --git a/apps/web/src/app/admin/toggles/registration/page.tsx b/apps/web/src/app/admin/toggles/registration/page.tsx
index 3825236b..2b990ed6 100644
--- a/apps/web/src/app/admin/toggles/registration/page.tsx
+++ b/apps/web/src/app/admin/toggles/registration/page.tsx
@@ -1,6 +1,7 @@
 import { RegistrationToggles } from "@/components/admin/toggles/RegistrationSettings";
 import { kv } from "@vercel/kv";
-import { parseRedisBoolean } from "@/lib/utils/server/redis";
+import { parseRedisBoolean, parseRedisNumber } from "@/lib/utils/server/redis";
+import c from "config";
 
 export default async function Page() {
 	const pipe = kv.pipeline();
@@ -8,14 +9,14 @@ export default async function Page() {
 	pipe.get("config:registration:secretRegistrationEnabled");
 	// const result = await pipe.exec();
 
-	const [
-		defaultRegistrationEnabled,
-		defaultSecretRegistrationEnabled,
-		defaultRSVPsEnabled,
-	]: (string | null)[] = await kv.mget(
+	const [defaultRegistrationEnabled, defaultSecretRegistrationEnabled, defaultRSVPsEnabled, defaultRSVPLimit]: (
+		| string
+		| null
+	)[] = await kv.mget(
 		"config:registration:registrationEnabled",
 		"config:registration:secretRegistrationEnabled",
 		"config:registration:allowRSVPs",
+		"config:registration:maxRSVPs"
 	);
 
 	return (
@@ -38,6 +39,10 @@ export default async function Page() {
 					defaultRSVPsEnabled,
 					true,
 				)}
+				defaultRSVPLimit={parseRedisNumber(
+					defaultRSVPLimit,
+					c.rsvpDefaultLimit
+				)}
 			/>
 		</div>
 	);
diff --git a/apps/web/src/app/rsvp/page.tsx b/apps/web/src/app/rsvp/page.tsx
index 0bd5d2cd..320a7ea4 100644
--- a/apps/web/src/app/rsvp/page.tsx
+++ b/apps/web/src/app/rsvp/page.tsx
@@ -2,13 +2,13 @@ import ConfirmDialogue from "@/components/rsvp/ConfirmDialogue";
 import c from "config";
 import { auth } from "@clerk/nextjs";
 import { redirect } from "next/navigation";
-import { db } from "db";
+import { count, db } from "db";
 import { eq } from "db/drizzle";
 import { users } from "db/schema";
 import ClientToast from "@/components/shared/ClientToast";
 import { SignedOut, RedirectToSignIn } from "@clerk/nextjs";
 import { kv } from "@vercel/kv";
-import { parseRedisBoolean } from "@/lib/utils/server/redis";
+import { parseRedisBoolean, parseRedisNumber } from "@/lib/utils/server/redis";
 import Link from "next/link";
 import { Button } from "@/components/shadcn/ui/button";
 import { CheckCircleIcon } from "lucide-react";
@@ -38,15 +38,19 @@ export default async function RsvpPage({
 	}
 
 	const rsvpEnabled = await kv.get("config:registration:allowRSVPs");
+	const rsvpLimit = parseRedisNumber(await kv.get("config:registration:maxRSVPs"), c.rsvpDefaultLimit);
+	const rsvpUserCount = await db
+		.select({ count: count() })
+		.from(users)
+		.where(eq(users.rsvp, true))
+		.limit(rsvpLimit)
+		.then((result) => result[0].count);
 
 	// TODO: fix type jank here
-	if (
-		parseRedisBoolean(
-			rsvpEnabled as string | boolean | null | undefined,
-			true,
-		) === true ||
-		user.rsvp === true
-	) {
+	const isRsvpPossible = parseRedisBoolean(rsvpEnabled as string | boolean | null | undefined, true) === true &&
+		rsvpUserCount < rsvpLimit;
+
+	if (isRsvpPossible || user.rsvp === true) {
 		return (
 			<>
 				<ClientToast />
diff --git a/apps/web/src/components/admin/toggles/RegistrationSettings.tsx b/apps/web/src/components/admin/toggles/RegistrationSettings.tsx
index 180fdd12..55af38bf 100644
--- a/apps/web/src/components/admin/toggles/RegistrationSettings.tsx
+++ b/apps/web/src/components/admin/toggles/RegistrationSettings.tsx
@@ -11,18 +11,22 @@ import {
 	toggleRegistrationMessageEnabled,
 	toggleSecretRegistrationEnabled,
 	toggleRSVPs,
+	setRSVPLimit,
 } from "@/actions/admin/registration-actions";
+import { UpdateItemWithConfirmation } from "./UpdateItemWithConfirmation";
 
 interface RegistrationTogglesProps {
 	defaultRegistrationEnabled: boolean;
 	defaultSecretRegistrationEnabled: boolean;
 	defaultRSVPsEnabled: boolean;
+	defaultRSVPLimit: number
 }
 
 export function RegistrationToggles({
 	defaultSecretRegistrationEnabled,
 	defaultRegistrationEnabled,
 	defaultRSVPsEnabled,
+	defaultRSVPLimit
 }: RegistrationTogglesProps) {
 	const {
 		execute: executeToggleSecretRegistrationEnabled,
@@ -57,6 +61,17 @@ export function RegistrationToggles({
 		},
 	);
 
+	const {
+		execute: executeSetRSVPLimit,
+		optimisticData: SetRSVPLimitOptimisticData
+	} = useOptimisticAction(
+		setRSVPLimit,
+		{ success: true, statusSet: defaultRSVPLimit },
+		(_, { rsvpLimit }) => {
+			return { statusSet: rsvpLimit, success: true }
+		}
+	);
+
 	return (
 		<>
 			<div className="rounded-lg border-2 border-muted px-5 py-10">
@@ -116,6 +131,18 @@ export function RegistrationToggles({
 							}}
 						/>
 					</div>
+					<div className="flex items-center py-4 border-t-muted border-t border-b">
+						<p className="text-sm font-bold mr-auto">RSVP Limit</p>
+						<UpdateItemWithConfirmation 
+							defaultValue={SetRSVPLimitOptimisticData.statusSet}
+							enabled={toggleRSVPsOptimisticData.statusSet}
+							type="number"
+							onSubmit={(newLimit) => {
+								toast.success(`Limit on the number of users who can RSVP changed to ${newLimit}`)
+								executeSetRSVPLimit({ rsvpLimit: newLimit })
+							}}
+						/>
+					</div>
 				</div>
 			</div>
 		</>
diff --git a/apps/web/src/components/admin/toggles/UpdateItemWithConfirmation.tsx b/apps/web/src/components/admin/toggles/UpdateItemWithConfirmation.tsx
new file mode 100644
index 00000000..8cd29384
--- /dev/null
+++ b/apps/web/src/components/admin/toggles/UpdateItemWithConfirmation.tsx
@@ -0,0 +1,66 @@
+import { useState } from "react";
+import { Input } from "@/components/shadcn/ui/input";
+import { Button } from "@/components/shadcn/ui/button";
+
+interface UpdateItemWithConfirmationBaseProps<T extends number | string> {
+	defaultValue: T;
+	enabled: boolean;
+	onSubmit: (value: T) => void;
+}
+
+type UpdateItemWithConfirmationProps =
+	| ({ type: "string" } & UpdateItemWithConfirmationBaseProps<string>)
+	| ({ type: "number" } & UpdateItemWithConfirmationBaseProps<number>);
+
+export function UpdateItemWithConfirmation({
+	type,
+	defaultValue,
+	onSubmit,
+	enabled,
+}: UpdateItemWithConfirmationProps) {
+	const [valueUpdated, setValueUpdated] = useState(false);
+	const [value, setValue] = useState(defaultValue.toString());
+
+	return (
+		<div className="flex items-center gap-2 max-h-8">
+			<Input
+				className="sm:w-40 w-24 text-center text-md font-bold"
+				value={value}
+				disabled={!enabled}
+				onChange={({ target: { value: updated } }) => {
+					// Ignore the change if the value is a non numeric character.
+					if (type === "number" && /[^0-9]/.test(updated)) {
+						setValue(value);
+						return;
+					}
+
+					setValue(updated);
+
+					/* Avoid allowing the user to update the default value to itself.
+					 * Also disallow the user from sending a zero length input. */
+					setValueUpdated(
+						updated !== defaultValue.toString() && updated.length !== 0
+					);
+				}}
+			/>
+			<Button
+				className="text-sm font-bold"
+				type="button"
+				variant="default"
+				title="Apply Changes"
+				disabled={!valueUpdated || !enabled}
+				onClick={() => {
+					if (type === "number") {
+						onSubmit(parseInt(value));
+					} else {
+						onSubmit(value);
+					}
+
+					setValueUpdated(false);
+				}}
+			>
+				Apply
+			</Button>
+		</div>
+	);
+}
diff --git a/apps/web/src/lib/utils/server/redis.ts b/apps/web/src/lib/utils/server/redis.ts
index a011944f..d25f55c5 100644
--- a/apps/web/src/lib/utils/server/redis.ts
+++ b/apps/web/src/lib/utils/server/redis.ts
@@ -31,3 +31,11 @@ export function parseRedisBoolean(
 	if (typeof value === "boolean") return value;
 	return defaultValue !== undefined ? defaultValue : false;
 }
+
+export function parseRedisNumber(value: string | null, defaultValue: number) {
+	if (value && !isNaN(parseInt(value))) {
+		return parseInt(value);
+	} else {
+		return defaultValue;
+	}
+}
diff --git a/packages/config/hackkit.config.ts b/packages/config/hackkit.config.ts
index 07ab1575..72bf4610 100644
--- a/packages/config/hackkit.config.ts
+++ b/packages/config/hackkit.config.ts
@@ -8,6 +8,7 @@ export default {
 	botName: "HackKit",
 	botParticipantRole: "Participant",
 	hackathonTimezone: "America/Chicago",
+	rsvpDefaultLimit: 500,
 	localUniversityName: "The University of Texas at San Antonio",
 	localUniversityShortIDName: "ABC123",
 	localUniversityShortIDMaxLength: 6,