diff --git a/apps/web/modules/bookings/components/event-meta/Duration.tsx b/apps/web/modules/bookings/components/event-meta/Duration.tsx
index 024e1b764d..0e174cc303 100644
--- a/apps/web/modules/bookings/components/event-meta/Duration.tsx
+++ b/apps/web/modules/bookings/components/event-meta/Duration.tsx
@@ -93,6 +93,13 @@ export const EventDuration = ({
return <>{getDurationFormatted(event.length, t)}>;
const durations = event?.metadata?.multipleDuration || [15, 30, 60, 90];
+ const hideDurationSelector = event?.metadata?.hideDurationSelectorInBookingPage;
+
+ // When duration selector is hidden, show only the selected/default duration as text
+ // URL params can still set the duration, but the user cannot change it via UI
+ if (hideDurationSelector) {
+ return <>{getDurationFormatted(selectedDuration || event.length, t)}>;
+ }
return selectedDuration ? (
diff --git a/apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx b/apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx
index c06675105e..f9e1924e69 100644
--- a/apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx
+++ b/apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx
@@ -15,7 +15,7 @@ import { slugify } from "@calcom/lib/slugify";
import turndown from "@calcom/lib/turndownService";
import classNames from "@calcom/ui/classNames";
import { Editor } from "@calcom/ui/components/editor";
-import { Label, Select, SettingsToggle, TextAreaField, TextField } from "@calcom/ui/components/form";
+import { CheckboxField, Label, Select, SettingsToggle, TextAreaField, TextField } from "@calcom/ui/components/form";
import { Skeleton } from "@calcom/ui/components/skeleton";
import Locations from "@calcom/web/modules/event-types/components/locations/Locations";
import { useState } from "react";
@@ -276,6 +276,21 @@ export const EventSetupTab = (
}}
/>
+
+ (
+ onChange(e.target.checked)}
+ description={t("hide_duration_selector_in_booking_page")}
+ disabled={lengthLockedProps.disabled}
+ />
+ )}
+ />
+
) : (
users.deleteAll());
+
+test.describe("Hide duration selector in booking page", () => {
+ test("duration selector is hidden when setting is enabled but URL params still work", async ({
+ page,
+ users,
+ }) => {
+ const user = await users.create();
+ await user.apiLogin();
+
+ // Get the multiple duration event type
+ const eventType = user.eventTypes.find((e) => e.slug === "multiple-duration");
+ expect(eventType).toBeDefined();
+
+ await test.step("enable hide duration selector setting", async () => {
+ await page.goto("/event-types");
+ await page.waitForSelector('[data-testid="event-types"]');
+ await page.getByTestId("event-types").locator('a[title="Multiple duration"]').click();
+ await page.waitForSelector('[data-testid="event-title"]');
+
+ // Find and check the hide duration selector checkbox
+ const checkbox = page.getByTestId("hide-duration-selector-checkbox");
+ await checkbox.check();
+
+ // Save the event type
+ await page.locator('[data-testid="update-eventtype"]').click();
+ await page.waitForResponse("/api/trpc/eventTypesHeavy/update?batch=1");
+ });
+
+ await test.step("verify duration selector is hidden on booking page", async () => {
+ await page.goto(`/${user.username}/multiple-duration`);
+
+ // Wait for the page to load
+ await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor({ state: "visible" });
+
+ // The duration selector buttons should NOT be visible
+ await expect(page.getByTestId("multiple-choice-30mins")).not.toBeVisible();
+ await expect(page.getByTestId("multiple-choice-60mins")).not.toBeVisible();
+ await expect(page.getByTestId("multiple-choice-90mins")).not.toBeVisible();
+ });
+
+ await test.step("verify URL param can still set duration when selector is hidden", async () => {
+ // Navigate with duration param
+ await page.goto(`/${user.username}/multiple-duration?duration=60`);
+
+ // Wait for the page to load
+ await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor({ state: "visible" });
+
+ // The duration selector should still be hidden
+ await expect(page.getByTestId("multiple-choice-60mins")).not.toBeVisible();
+
+ // Select a time slot and book
+ await selectFirstAvailableTimeSlotNextMonth(page);
+
+ await page.fill('[name="name"]', "Test User");
+ await page.fill('[name="email"]', "test@example.com");
+ await bookTimeSlot(page);
+
+ await expect(page.locator("[data-testid=success-page]")).toBeVisible();
+
+ // Verify the booking was made with the correct duration (60 mins = 1 hour)
+ // The booking title or details should reflect the 60 min duration
+ });
+ });
+
+ test("duration selector is visible when setting is disabled", async ({ page, users }) => {
+ const user = await users.create();
+ await user.apiLogin();
+
+ // The default multiple-duration event type should have the selector visible
+ await page.goto(`/${user.username}/multiple-duration`);
+
+ // Wait for the page to load
+ await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor({ state: "visible" });
+
+ // The duration selector buttons should be visible
+ await expect(page.getByTestId("multiple-choice-30mins")).toBeVisible();
+ await expect(page.getByTestId("multiple-choice-60mins")).toBeVisible();
+ await expect(page.getByTestId("multiple-choice-90mins")).toBeVisible();
+ });
+
+ test("default duration is shown when selector is hidden and no URL param", async ({ page, users }) => {
+ const user = await users.create();
+ await user.apiLogin();
+
+ // Get the multiple duration event type
+ const eventType = user.eventTypes.find((e) => e.slug === "multiple-duration");
+ expect(eventType).toBeDefined();
+
+ await test.step("enable hide duration selector setting", async () => {
+ await page.goto("/event-types");
+ await page.waitForSelector('[data-testid="event-types"]');
+ await page.getByTestId("event-types").locator('a[title="Multiple duration"]').click();
+ await page.waitForSelector('[data-testid="event-title"]');
+
+ // Find and check the hide duration selector checkbox
+ const checkbox = page.getByTestId("hide-duration-selector-checkbox");
+ await checkbox.check();
+
+ // Save the event type
+ await page.locator('[data-testid="update-eventtype"]').click();
+ await page.waitForResponse("/api/trpc/eventTypesHeavy/update?batch=1");
+ });
+
+ await test.step("verify default duration is displayed", async () => {
+ await page.goto(`/${user.username}/multiple-duration`);
+
+ // Wait for the page to load
+ await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor({ state: "visible" });
+
+ // The duration text should be visible (default is 30 mins)
+ // Since the selector is hidden, we should see the duration as text
+ await expect(page.locator('[data-testid="event-meta"]')).toContainText("30");
+ });
+ });
+});
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index c700e413d6..6f9dd92a8d 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -881,6 +881,7 @@
"available_durations": "Available durations",
"default_duration": "Default duration",
"default_duration_no_options": "Please choose available durations first",
+ "hide_duration_selector_in_booking_page": "Hide duration selector",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
"multiple_duration_timeUnit_short": "{{count}}$t({{unit}}_short)",
diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts
index 9dd03b80d5..60268124ed 100644
--- a/packages/prisma/zod-utils.ts
+++ b/packages/prisma/zod-utils.ts
@@ -218,6 +218,7 @@ const _eventTypeMetaDataSchemaWithoutApps = z.object({
smartContractAddress: z.string().optional(),
blockchainId: z.number().optional(),
multipleDuration: z.number().array().optional(),
+ hideDurationSelectorInBookingPage: z.boolean().optional(),
giphyThankYouPage: z.string().optional(),
additionalNotesRequired: z.boolean().optional(),
disableSuccessPage: z.boolean().optional(),