Skip to content

feat: custom org domains usage#27798

Open
Amit91848 wants to merge 12 commits intofeat/custom-org-domains-uifrom
feat/custom-org-domains-rebased
Open

feat: custom org domains usage#27798
Amit91848 wants to merge 12 commits intofeat/custom-org-domains-uifrom
feat/custom-org-domains-rebased

Conversation

@Amit91848
Copy link
Member

@Amit91848 Amit91848 commented Feb 9, 2026

What does this PR do?

  • Fixes #XXXX (GitHub issue number)
  • Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.

Image Demo (if applicable):

  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
  • What are the minimal test data to have?
  • What is expected (happy path) to have (input and output)?
  • Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings
  • My PR is too large (>500 lines or >10 files) and should be split into smaller PRs

Updates since last revision

Addressed Cubic AI review feedback (confidence ≥ 9/10 only):

  • E2E test locators: Replaced page.getByText(...) with page.getByTestId(...) in dynamic-booking-pages.e2e.ts custom domain test
  • Startup crash prevention: Wrapped ALLOWED_HOSTNAMES JSON.parse in try-catch in getNextjsOrgRewriteConfig.ts
  • Removed debug console.log statements:
    • getNextjsOrgRewriteConfig.ts (module-scope log firing on every load)
    • MemberList.tsx (logging orgBranding data)
    • orgDomains.ts (replaced with structured log.debug)

Items for reviewer attention

  • The E2E test now uses page.getByTestId(attendee-name-${user.name}) — verify that dynamic booking users appear as attendees (not only as hosts) on the success page, since the test ID attendee-name-* is on the attendee rendering path.
  • ALLOWED_HOSTNAMES parse failure now silently falls back to [] with a console.error. Consider whether this should be a hard failure in production.

Skipped Cubic issues (confidence < 9/10)

File Issue Confidence
embed-code-generator.e2e.ts customDomain cleanup 8
reschedule.e2e.ts customDomain cleanup 8
getPublicEvent.ts Missing isOrganization: true filter 7
common.json Duplicate translation keys 8
next.config.ts Missing embed route rewrites for custom domains 6
organization-redirection.e2e.ts include vs select in test 8

Summary by cubic

Extends custom domain support end-to-end so booking, team, user, forms, and embed pages render and link on verified org domains. Propagates custom domains through API v2 and web metadata so canonical links, embeds, and public URLs resolve on the org domain.

  • New Features

    • API v2: event-type URLs now use the org's custom domain when available; profile/users payloads include organization.customDomain for link building.
    • Web: booking and team pages pass isCustomDomain to metadata builders; team event-type pages compute base origin from the org's customDomain.
    • Org settings: domain management with availability checks, verify/refresh, edit, and remove.
  • Refactors

    • orgDomainConfig now exposes customDomain; getOrgFullOrigin/getBookerBaseUrlSync accept { protocol, customDomain } and are used across booking, team, forms, avatars, and URL builders.
    • Users repository and queries return organization.customDomain for movedToProfile/profiles; callers updated to consume it.

Written for commit 24e9901. Summary will update on new commits.


Link to Devin run: https://app.devin.ai/sessions/862ae4549bc44a239e9f0f4b6ff1ee9c
Requested by: unknown ()

@github-actions
Copy link
Contributor

github-actions bot commented Feb 9, 2026

Hey there and thank you for opening this pull request! 👋🏼

We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted.

Details:

No release type found in pull request title "Feat/custom org domains rebased". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:
 - feat: A new feature
 - fix: A bug fix
 - docs: Documentation only changes
 - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
 - refactor: A code change that neither fixes a bug nor adds a feature
 - perf: A code change that improves performance
 - test: Adding missing tests or correcting existing tests
 - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
 - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
 - chore: Other changes that don't modify src or test files
 - revert: Reverts a previous commit

@Amit91848 Amit91848 changed the title Feat/custom org domains rebased feat: custom org domains usage Feb 9, 2026
Copy link
Member Author

Amit91848 commented Feb 9, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@Amit91848 Amit91848 force-pushed the feat/custom-org-domains-rebased branch from bdaef55 to e64a30f Compare February 9, 2026 16:56
@Amit91848 Amit91848 force-pushed the feat/custom-org-domains-rebased branch from e64a30f to e29b951 Compare February 10, 2026 13:01
@Amit91848 Amit91848 marked this pull request as ready for review February 10, 2026 17:55
@Amit91848 Amit91848 requested review from a team as code owners February 10, 2026 17:55
@graphite-app graphite-app bot added the core area: core, team members only label Feb 10, 2026
@Amit91848 Amit91848 added ready-for-e2e run-ci Approve CI to run for external contributors and removed ready-for-e2e labels Feb 10, 2026
@Amit91848 Amit91848 changed the base branch from feat/custom-org-domains-ui to main February 10, 2026 18:29
@Amit91848 Amit91848 changed the base branch from main to feat/custom-org-domains-ui February 10, 2026 18:30
@github-actions github-actions bot added ❗️ migrations contains migration files ❗️ .env changes contains changes to env variables labels Feb 13, 2026
@Amit91848 Amit91848 changed the base branch from feat/custom-org-domains-ui to main February 13, 2026 12:46
@Amit91848 Amit91848 changed the base branch from main to feat/custom-org-domains-ui February 13, 2026 12:47
@Amit91848
Copy link
Member Author

@cubic-dev-ai

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 13, 2026

@cubic-dev-ai

@Amit91848 I have started the AI code review. It will take a few minutes to complete.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 issues found across 73 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/playwright/dynamic-booking-pages.e2e.ts">

<violation number="1" location="apps/web/playwright/dynamic-booking-pages.e2e.ts:234">
P1: Rule violated: **E2E Tests Best Practices**

E2E Tests Best Practices: avoid text locators. These new expectations use `page.getByText(...)` instead of `page.getByTestId(...)`, which violates the rule’s requirement to prefer stable test IDs.</violation>
</file>

<file name="apps/web/playwright/embed-code-generator.e2e.ts">

<violation number="1" location="apps/web/playwright/embed-code-generator.e2e.ts:326">
P1: Rule violated: **E2E Tests Best Practices**

This new test creates a customDomain record but never deletes it, leaving DB state mutated after the test. E2E tests must clean up all created records to avoid side effects and maintain consistent parallel runs (E2E Tests Best Practices: reset DB state after tests).</violation>
</file>

<file name="apps/web/playwright/reschedule.e2e.ts">

<violation number="1" location="apps/web/playwright/reschedule.e2e.ts:440">
P1: Rule violated: **E2E Tests Best Practices**

E2E tests must reset DB state after running. This test inserts a `customDomain` record but never deletes it, leaving side effects that can affect parallel runs and repeatability.</violation>
</file>

<file name="packages/features/eventtypes/lib/getPublicEvent.ts">

<violation number="1" location="packages/features/eventtypes/lib/getPublicEvent.ts:332">
P2: Organization lookup by slug/customDomain should filter to organizations (`isOrganization: true`) to avoid matching a non-org team with the same slug/custom domain.

(Based on your team's feedback about validating team.isOrganization on org slug lookups.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/web/modules/ee/teams/components/MemberList.tsx">

<violation number="1" location="apps/web/modules/ee/teams/components/MemberList.tsx:188">
P2: Remove the console.log debug statement before merging to avoid logging org branding data in production.</violation>
</file>

<file name="apps/web/getNextjsOrgRewriteConfig.ts">

<violation number="1" location="apps/web/getNextjsOrgRewriteConfig.ts:7">
P1: Unguarded `JSON.parse` on an env var at module scope will crash the application at startup if `ALLOWED_HOSTNAMES` is misconfigured (e.g., missing quotes around values). Wrap in a try-catch with a meaningful error message, or parse as a simple comma-separated list instead.

(Based on your team's feedback about centralizing env-derived configuration and avoiding inline parsing.) [FEEDBACK_USED]</violation>

<violation number="2" location="apps/web/getNextjsOrgRewriteConfig.ts:103">
P2: Debug `console.log` left at module scope will fire unconditionally in production on every module load (build time, SSR, etc.). This should be removed or at minimum guarded behind a debug/development check.</violation>
</file>

<file name="apps/web/playwright/organization/organization-redirection.e2e.ts">

<violation number="1" location="apps/web/playwright/organization/organization-redirection.e2e.ts:271">
P3: Avoid `include` here and select only the fields used. `include: { hashedLink: true }` pulls all hashedLink columns unnecessarily.</violation>
</file>

<file name="packages/features/ee/organizations/lib/orgDomains.ts">

<violation number="1" location="packages/features/ee/organizations/lib/orgDomains.ts:158">
P2: Debug `console.log` left in production code. This runs on every request hitting a custom domain. Use the existing `log` (structured logger) already imported in this file, or remove it entirely.</violation>
</file>

<file name="apps/web/public/static/locales/en/common.json">

<violation number="1" location="apps/web/public/static/locales/en/common.json:9">
P2: Duplicate translation keys were added here; in JSON the later entry wins, so the newly added strings are silently overwritten and the file becomes ambiguous to maintain. Keep only one set of keys and update the existing entries instead of duplicating them.</violation>
</file>

<file name="apps/web/next.config.ts">

<violation number="1" location="apps/web/next.config.ts:378">
P2: Custom domain rewrites omit embed routes, so custom-domain embed pages won’t be rewritten to org-aware paths (unlike org-domain rewrites). Add custom-domain equivalents for `/embed` and `/:user/:type/embed` to keep embed flows working on custom domains.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

name: "TestOrg",
});
customDomainSlug = `booking-${Math.random().toString(36).substring(7)}.testorg.com`;
await prisma.customDomain.create({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rule violated: E2E Tests Best Practices

This new test creates a customDomain record but never deletes it, leaving DB state mutated after the test. E2E tests must clean up all created records to avoid side effects and maintain consistent parallel runs (E2E Tests Best Practices: reset DB state after tests).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/playwright/embed-code-generator.e2e.ts, line 326:

<comment>This new test creates a customDomain record but never deletes it, leaving DB state mutated after the test. E2E tests must clean up all created records to avoid side effects and maintain consistent parallel runs (E2E Tests Best Practices: reset DB state after tests).</comment>

<file context>
@@ -316,6 +315,62 @@ test.describe("Embed Code Generator Tests", () => {
+        name: "TestOrg",
+      });
+      customDomainSlug = `booking-${Math.random().toString(36).substring(7)}.testorg.com`;
+      await prisma.customDomain.create({
+        data: { teamId: org.id, slug: customDomainSlug, verified: true },
+      });
</file context>
Fix with Cubic

name: "TestOrg",
});
const customDomainSlug = `booking-${Math.random().toString(36).substring(7)}.testorg.com`;
await prisma.customDomain.create({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rule violated: E2E Tests Best Practices

E2E tests must reset DB state after running. This test inserts a customDomain record but never deletes it, leaving side effects that can affect parallel runs and repeatability.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/playwright/reschedule.e2e.ts, line 440:

<comment>E2E tests must reset DB state after running. This test inserts a `customDomain` record but never deletes it, leaving side effects that can affect parallel runs and repeatability.</comment>

<file context>
@@ -434,12 +432,50 @@ test.describe("Reschedule Tests", async () => {
+        name: "TestOrg",
+      });
+      const customDomainSlug = `booking-${Math.random().toString(36).substring(7)}.testorg.com`;
+      await prisma.customDomain.create({
+        data: { teamId: org.id, slug: customDomainSlug, verified: true },
+      });
</file context>
Fix with Cubic

orgDetails = await prisma.team.findFirstOrThrow({
where: {
slug: org,
OR: [
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Organization lookup by slug/customDomain should filter to organizations (isOrganization: true) to avoid matching a non-org team with the same slug/custom domain.

(Based on your team's feedback about validating team.isOrganization on org slug lookups.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/features/eventtypes/lib/getPublicEvent.ts, line 332:

<comment>Organization lookup by slug/customDomain should filter to organizations (`isOrganization: true`) to avoid matching a non-org team with the same slug/custom domain.

(Based on your team's feedback about validating team.isOrganization on org slug lookups.) </comment>

<file context>
@@ -325,33 +325,45 @@ export const getPublicEvent = async (
       orgDetails = await prisma.team.findFirstOrThrow({
         where: {
-          slug: org,
+          OR: [
+            { slug: org },
+            { customDomain: { slug: org, verified: true } },
</file context>
Suggested change
OR: [
isOrganization: true,
OR: [
Fix with Cubic

"custom_domain_removed": "Custom domain removed successfully",
"remove_custom_domain": "Remove custom domain",
"remove_custom_domain_description": "Are you sure you want to remove {{domain}}? This will make your booking pages unavailable at this domain.",
"no_custom_domain_configured": "No custom domain configured yet",
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Duplicate translation keys were added here; in JSON the later entry wins, so the newly added strings are silently overwritten and the file becomes ambiguous to maintain. Keep only one set of keys and update the existing entries instead of duplicating them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/public/static/locales/en/common.json, line 9:

<comment>Duplicate translation keys were added here; in JSON the later entry wins, so the newly added strings are silently overwritten and the file becomes ambiguous to maintain. Keep only one set of keys and update the existing entries instead of duplicating them.</comment>

<file context>
@@ -6,6 +6,19 @@
   "custom_domain_removed": "Custom domain removed successfully",
   "remove_custom_domain": "Remove custom domain",
   "remove_custom_domain_description": "Are you sure you want to remove {{domain}}? This will make your booking pages unavailable at this domain.",
+  "no_custom_domain_configured": "No custom domain configured yet",
+  "enter_domain_to_check": "Enter a valid domain to check availability",
+  "checking_availability": "Checking availability...",
</file context>
Fix with Cubic

]
: []),
// Custom domain rewrites - rewrite to /org/{hostname}/...
...(customDomainRewriteConfig
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom domain rewrites omit embed routes, so custom-domain embed pages won’t be rewritten to org-aware paths (unlike org-domain rewrites). Add custom-domain equivalents for /embed and /:user/:type/embed to keep embed flows working on custom domains.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/next.config.ts, line 378:

<comment>Custom domain rewrites omit embed routes, so custom-domain embed pages won’t be rewritten to org-aware paths (unlike org-domain rewrites). Add custom-domain equivalents for `/embed` and `/:user/:type/embed` to keep embed flows working on custom domains.</comment>

<file context>
@@ -336,6 +374,29 @@ const nextConfig = (phase: string): NextConfig => {
             ]
           : []),
+        // Custom domain rewrites - rewrite to /org/{hostname}/...
+        ...(customDomainRewriteConfig
+          ? [
+              customDomainMatcherConfig.root
</file context>
Fix with Cubic

create: [{ link: generateHashedLink(eventType.id) }],
},
},
include: { hashedLink: true },
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Avoid include here and select only the fields used. include: { hashedLink: true } pulls all hashedLink columns unnecessarily.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/playwright/organization/organization-redirection.e2e.ts, line 271:

<comment>Avoid `include` here and select only the fields used. `include: { hashedLink: true }` pulls all hashedLink columns unnecessarily.</comment>

<file context>
@@ -206,3 +206,97 @@ test.describe("Unpublished Organization Redirection", () => {
+          create: [{ link: generateHashedLink(eventType.id) }],
+        },
+      },
+      include: { hashedLink: true },
+    });
+
</file context>
Fix with Cubic

@github-actions
Copy link
Contributor

Devin AI is addressing Cubic AI's review feedback

A Devin session has been created to address the issues identified by Cubic AI.

View Devin Session

- Replace page.getByText with page.getByTestId for E2E test locators in dynamic-booking-pages
- Wrap ALLOWED_HOSTNAMES JSON.parse in try-catch to prevent startup crashes
- Remove debug console.log from getNextjsOrgRewriteConfig.ts module scope
- Remove debug console.log from MemberList.tsx
- Replace console.log with structured logger in orgDomains.ts

Co-Authored-By: unknown <>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only ❗️ .env changes contains changes to env variables ❗️ migrations contains migration files ready-for-e2e run-ci Approve CI to run for external contributors size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant