diff --git a/src/equicordplugins/waitForSlot/index.tsx b/src/equicordplugins/waitForSlot/index.tsx
new file mode 100644
index 000000000..a891b0f40
--- /dev/null
+++ b/src/equicordplugins/waitForSlot/index.tsx
@@ -0,0 +1,111 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2026 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { playAudio } from "@api/AudioPlayer";
+import { NavContextMenuPatchCallback } from "@api/ContextMenu";
+import { popNotice, showNotice } from "@api/Notices";
+import { definePluginSettings } from "@api/Settings";
+import { Devs, EquicordDevs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+import type { Channel } from "@vencord/discord-types";
+import { ChannelType } from "@vencord/discord-types/enums";
+import { ChannelActions, ChannelStore, Menu, VoiceStateStore } from "@webpack/common";
+
+let waitingChannelId: string | null = null;
+
+const NOTIFICATION_AUDIO_URL = "https://raw.githubusercontent.com/Equicord/Equibored/main/sounds/waitForSlot/notification.mp3";
+
+const settings = definePluginSettings({
+ autoJoin: {
+ type: OptionType.BOOLEAN,
+ description: "Join the channel immediately instead of showing a notice.",
+ default: false,
+ },
+ notificationSound: {
+ type: OptionType.BOOLEAN,
+ description: "Play a sound when a slot becomes available.",
+ default: true,
+ },
+});
+
+const ChannelContext: NavContextMenuPatchCallback = (children, { channel }) => {
+ if (channel?.type !== ChannelType.GUILD_VOICE || !channel.userLimit) return;
+ if (Object.keys(VoiceStateStore.getVoiceStatesForChannel(channel.id)).length < channel.userLimit) return;
+
+ const isWaiting = waitingChannelId === channel.id;
+
+ children.splice(-1, 0,
+
{ waitingChannelId = isWaiting ? null : channel.id; }}
+ />
+ );
+};
+
+function promptVoiceChannel(channel: Channel | null | undefined): boolean {
+ if (!channel || channel.type !== ChannelType.GUILD_VOICE || !channel.userLimit) return false;
+ if (Object.keys(VoiceStateStore.getVoiceStatesForChannel(channel.id)).length < channel.userLimit) return false;
+ if (waitingChannelId === channel.id) return true;
+
+ showNotice(`Voice channel ${channel.name} is full. Wait for a slot?`, "Wait", () => {
+ popNotice();
+ waitingChannelId = channel.id;
+ });
+
+ return true;
+}
+
+export default definePlugin({
+ name: "WaitForSlot",
+ description: "Automatically join a full voice channel when a slot opens.",
+ authors: [EquicordDevs.omaw, Devs.prism],
+ settings,
+ patches: [
+ {
+ find: "VoiceChannel, transitionTo: Channel does not have a guildId",
+ replacement: {
+ match: /(?<=\|\|\i\|\|)\i\.default\.selectVoiceChannel\((\i)\.id\)/,
+ replace: "$self.promptVoiceChannel($1)||$&"
+ }
+ }
+ ],
+
+ contextMenus: {
+ "channel-context": ChannelContext,
+ },
+ promptVoiceChannel,
+
+ flux: {
+ VOICE_STATE_UPDATES() {
+ if (!waitingChannelId) return;
+
+ const channel = ChannelStore.getChannel(waitingChannelId);
+ if (!channel?.userLimit) {
+ waitingChannelId = null;
+ return;
+ }
+
+ if (Object.keys(VoiceStateStore.getVoiceStatesForChannel(waitingChannelId)).length < channel.userLimit) {
+ const channelId = waitingChannelId;
+ waitingChannelId = null;
+ if (settings.store.notificationSound) { playAudio(NOTIFICATION_AUDIO_URL); }
+ if (settings.store.autoJoin) {
+ ChannelActions.selectVoiceChannel(channelId);
+ } else {
+ showNotice(`Hey, someone just left #${channel.name} and there's a spot for you now!`, "Join", () => {
+ popNotice();
+ ChannelActions.selectVoiceChannel(channelId);
+ });
+ }
+ }
+ },
+ },
+
+ stop() {
+ waitingChannelId = null;
+ },
+});