Repository: formbricks/formbricks (2025-08-06T06:30:02Z)

Issue:
[FEATURE] Allow customizing link preview
## Problem
The survey name is usually used for internal reference, not for the public.

## Solution
Allow a custom meta title, meta description and meta image

### Added Functionality
- We introduce a `Share settings` category with the first item `Link settings`
- In the Link Settings, we allow users to set meta title, meta description, and OG image
- If the survey is available in multiple languages, we allow a custom title per language

## UI
### Status Quo
<img width="1040" height="756" alt="Image" src="https://github.com/user-attachments/assets/90cbcd8c-2fac-4a1c-a176-5c4d269e5816" />

### Update Share Modal
<img width="1171" height="867" alt="Image" src="https://github.com/user-attachments/assets/493e1b87-3e55-4bde-8701-caebddd95df7" />

Here is the Figma file: [Link](https://www.figma.com/design/0Pzfoc0QjX481WBjqIZVgr/Formbricks-UI?node-id=4299-11187&t=mbADqqZm52jgDVJ3-1)

### UX Notes
- If a survey does not have multi-language active, we hide the Select at the top
- We prefill the defaults we currently use for `title` and `description`
- The "Save" button is default disabled, unless data in the form changes. On Save we change the data.
- Input type "File" might not yet be part of our UI Component Lib. @mattinannt pls decide on how it should be added (either as part of the Input component like in shadcn or if we want to handle it differently)
- If saved successfully, show Success toast `Settings saved successfully.` If not, show the error in an error toast.

### Technical Notes
- Please use [Nextjs metadata functionality](https://nextjs.org/docs/app/getting-started/metadata-and-og-images)
- Please check if it's also possible to use Next.js image compression here to make sure we serve web-optimized images in the metadata
- Add a `metadata` JSON object to the `Survey` model containing these information.
- Please show the defaults in the placeholder for all languages. The defaults stay like they are today (`including the `| Formbricks` in the title), while the custom overwrites will overwrite the full title, description and image without any Formbricks branding.

### Additional Info
- The functionality is available on all plans and license-options.
- Use SSR to render the `<head>` dynamically
- Set the link without lang parameter as canonical link
- Test how this would work with a survey that does not have multi-lang at first, gets published with custom meta tags and then gets multi-lang activated.


Discussion:

Patch:
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.tsx
index 3038ec05dd5c..7c172d29d57b 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.tsx
@@ -4,17 +4,31 @@ import { AnonymousLinksTab } from "@/app/(app)/environments/[environmentId]/surv
 import { AppTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/app-tab";
 import { DynamicPopupTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/dynamic-popup-tab";
 import { EmailTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/email-tab";
+import { LinkSettingsTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/link-settings-tab";
 import { PersonalLinksTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/personal-links-tab";
 import { QRCodeTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/qr-code-tab";
 import { SocialMediaTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/social-media-tab";
 import { TabContainer } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/tab-container";
 import { WebsiteEmbedTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/website-embed-tab";
-import { ShareViewType } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/types/share";
+import {
+  LinkTabsType,
+  ShareSettingsType,
+  ShareViaType,
+} from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/types/share";
 import { getSurveyUrl } from "@/modules/analysis/utils";
 import { Dialog, DialogContent, DialogTitle } from "@/modules/ui/components/dialog";
 import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
 import { useTranslate } from "@tolgee/react";
-import { Code2Icon, LinkIcon, MailIcon, QrCodeIcon, Share2Icon, SquareStack, UserIcon } from "lucide-react";
+import {
+  Code2Icon,
+  LinkIcon,
+  MailIcon,
+  QrCodeIcon,
+  Settings,
+  Share2Icon,
+  SquareStack,
+  UserIcon,
+} from "lucide-react";
 import { useEffect, useMemo, useState } from "react";
 import { TSegment } from "@formbricks/types/segment";
 import { TSurvey } from "@formbricks/types/surveys/types";
@@ -55,17 +69,19 @@ export const ShareSurveyModal = ({
   const { email } = user;
   const { t } = useTranslate();
   const linkTabs: {
-    id: ShareViewType;
+    id: ShareViaType | ShareSettingsType;
+    type: LinkTabsType;
     label: string;
     icon: React.ElementType;
     title: string;
     description: string;
-    componentType: React.ComponentType<any>;
-    componentProps: any;
+    componentType: React.ComponentType<unknown>;
+    componentProps: unknown;
   }[] = useMemo(
     () => [
       {
-        id: ShareViewType.ANON_LINKS,
+        id: ShareViaType.ANON_LINKS,
+        type: LinkTabsType.SHARE_VIA,
         label: t("environments.surveys.share.anonymous_links.nav_title"),
         icon: LinkIcon,
         title: t("environments.surveys.share.anonymous_links.nav_title"),
@@ -81,7 +97,8 @@ export const ShareSurveyModal = ({
         },
       },
       {
-        id: ShareViewType.PERSONAL_LINKS,
+        id: ShareViaType.PERSONAL_LINKS,
+        type: LinkTabsType.SHARE_VIA,
         label: t("environments.surveys.share.personal_links.nav_title"),
         icon: UserIcon,
         title: t("environments.surveys.share.personal_links.nav_title"),
@@ -96,7 +113,8 @@ export const ShareSurveyModal = ({
         },
       },
       {
-        id: ShareViewType.WEBSITE_EMBED,
+        id: ShareViaType.WEBSITE_EMBED,
+        type: LinkTabsType.SHARE_VIA,
         label: t("environments.surveys.share.embed_on_website.nav_title"),
         icon: Code2Icon,
         title: t("environments.surveys.share.embed_on_website.nav_title"),
@@ -105,7 +123,8 @@ export const ShareSurveyModal = ({
         componentProps: { surveyUrl },
       },
       {
-        id: ShareViewType.EMAIL,
+        id: ShareViaType.EMAIL,
+        type: LinkTabsType.SHARE_VIA,
         label: t("environments.surveys.share.send_email.nav_title"),
         icon: MailIcon,
         title: t("environments.surveys.share.send_email.nav_title"),
@@ -114,7 +133,8 @@ export const ShareSurveyModal = ({
         componentProps: { surveyId: survey.id, email },
       },
       {
-        id: ShareViewType.SOCIAL_MEDIA,
+        id: ShareViaType.SOCIAL_MEDIA,
+        type: LinkTabsType.SHARE_VIA,
         label: t("environments.surveys.share.social_media.title"),
         icon: Share2Icon,
         title: t("environments.surveys.share.social_media.title"),
@@ -123,7 +143,8 @@ export const ShareSurveyModal = ({
         componentProps: { surveyUrl, surveyTitle: survey.name },
       },
       {
-        id: ShareViewType.QR_CODE,
+        id: ShareViaType.QR_CODE,
+        type: LinkTabsType.SHARE_VIA,
         label: t("environments.surveys.summary.qr_code"),
         icon: QrCodeIcon,
         title: t("environments.surveys.summary.qr_code"),
@@ -132,7 +153,8 @@ export const ShareSurveyModal = ({
         componentProps: { surveyUrl },
       },
       {
-        id: ShareViewType.DYNAMIC_POPUP,
+        id: ShareViaType.DYNAMIC_POPUP,
+        type: LinkTabsType.SHARE_VIA,
         label: t("environments.surveys.share.dynamic_popup.nav_title"),
         icon: SquareStack,
         title: t("environments.surveys.share.dynamic_popup.nav_title"),
@@ -140,6 +162,16 @@ export const ShareSurveyModal = ({
         componentType: DynamicPopupTab,
         componentProps: { environmentId, surveyId: survey.id },
       },
+      {
+        id: ShareSettingsType.LINK_SETTINGS,
+        type: LinkTabsType.SHARE_SETTING,
+        label: t("environments.surveys.share.link_settings.title"),
+        icon: Settings,
+        title: t("environments.surveys.share.link_settings.title"),
+        description: t("environments.surveys.share.link_settings.description"),
+        componentType: LinkSettingsTab,
+        componentProps: { isReadOnly, locale: user.locale },
+      },
     ],
     [
       t,
@@ -156,8 +188,8 @@ export const ShareSurveyModal = ({
     ]
   );
 
-  const [activeId, setActiveId] = useState(
-    survey.type === "link" ? ShareViewType.ANON_LINKS : ShareViewType.APP
+  const [activeId, setActiveId] = useState<ShareViaType | ShareSettingsType>(
+    survey.type === "link" ? ShareViaType.ANON_LINKS : ShareViaType.APP
   );
 
   useEffect(() => {
@@ -170,7 +202,7 @@ export const ShareSurveyModal = ({
     setOpen(open);
     if (!open) {
       setShowView("start");
-      setActiveId(ShareViewType.ANON_LINKS);
+      setActiveId(ShareViaType.ANON_LINKS);
     }
   };
 
@@ -178,7 +210,7 @@ export const ShareSurveyModal = ({
     setShowView(view);
   };
 
-  const handleEmbedViewWithTab = (tabId: ShareViewType) => {
+  const handleEmbedViewWithTab = (tabId: ShareViaType | ShareSettingsType) => {
     setShowView("share");
     setActiveId(tabId);
   };
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/link-settings-tab.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/link-settings-tab.tsx
new file mode 100644
index 000000000000..4497fbdafd6a
--- /dev/null
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/link-settings-tab.tsx
@@ -0,0 +1,257 @@
+"use client";
+
+import { useSurvey } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/context/survey-context";
+import { createI18nString, extractLanguageCodes, getEnabledLanguages } from "@/lib/i18n/utils";
+import { updateSurveyAction } from "@/modules/survey/editor/actions";
+import { Button } from "@/modules/ui/components/button";
+import { FileInput } from "@/modules/ui/components/file-input";
+import {
+  FormControl,
+  FormDescription,
+  FormError,
+  FormField,
+  FormItem,
+  FormLabel,
+  FormProvider,
+} from "@/modules/ui/components/form";
+import { Input } from "@/modules/ui/components/input";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/modules/ui/components/select";
+import { useTranslate } from "@tolgee/react";
+import { useMemo, useState } from "react";
+import { useForm } from "react-hook-form";
+import toast from "react-hot-toast";
+import { getLanguageLabel } from "@formbricks/i18n-utils/src/utils";
+import { TI18nString, TSurvey, TSurveyMetadata } from "@formbricks/types/surveys/types";
+
+interface LinkSettingsTabProps {
+  isReadOnly: boolean;
+  locale: string;
+}
+
+interface LinkSettingsFormData {
+  title: TI18nString;
+  description: TI18nString;
+  ogImage?: string;
+}
+
+export const LinkSettingsTab = ({ isReadOnly, locale }: LinkSettingsTabProps) => {
+  const { t } = useTranslate();
+  const { survey } = useSurvey();
+  const enabledLanguages = getEnabledLanguages(survey.languages);
+  const hasMultipleLanguages = enabledLanguages.length > 1;
+
+  // Set default language - use 'default' if no multi-language is set up
+  const defaultLanguageCode = hasMultipleLanguages
+    ? survey.languages.find((lang) => lang.default)?.language.code || "default"
+    : "default";
+
+  const languageCodes = useMemo(() => extractLanguageCodes(survey.languages), [survey.languages]);
+
+  const [selectedLanguageCode, setSelectedLanguageCode] = useState(defaultLanguageCode);
+  const [isSaving, setIsSaving] = useState(false);
+
+  // Current language code for metadata storage
+  const currentLangCode = selectedLanguageCode === defaultLanguageCode ? "default" : selectedLanguageCode;
+
+  // Initialize form with current values - memoize to prevent re-initialization
+  const initialFormData = useMemo(() => {
+    const metadata = survey.metadata || {
+      title: createI18nString("", languageCodes),
+      description: createI18nString("", languageCodes),
+      ogImage: undefined,
+    };
+
+    return {
+      title: metadata.title || createI18nString("", languageCodes),
+      description: metadata.description || createI18nString("", languageCodes),
+      ogImage: metadata.ogImage || "",
+    };
+  }, [survey.metadata, languageCodes]);
+
+  const form = useForm<LinkSettingsFormData>({
+    defaultValues: initialFormData,
+  });
+
+  const {
+    handleSubmit,
+    setValue,
+    reset,
+    formState: { isDirty },
+  } = form;
+
+  const handleFileUpload = (urls: string[] | undefined) => {
+    if (urls && urls.length > 0) {
+      setValue("ogImage", urls[0], { shouldDirty: true });
+    } else {
+      // Handle removal of the image
+      setValue("ogImage", "", { shouldDirty: true });
+    }
+  };
+
+  const onSubmit = async (data: LinkSettingsFormData) => {
+    if (isSaving || isReadOnly) return;
+
+    setIsSaving(true);
+
+    // Get current linkMetadata
+    const currentSurveyMetadata = survey.metadata || {
+      title: createI18nString("", languageCodes),
+      description: createI18nString("", languageCodes),
+      ogImage: undefined,
+    };
+
+    // Merge form data with existing linkMetadata
+    const updatedTitle: TI18nString = {
+      ...currentSurveyMetadata.title,
+      ...data.title,
+    };
+
+    const updatedDescription: TI18nString = {
+      ...currentSurveyMetadata.description,
+      ...data.description,
+    };
+
+    const updatedSurveyMetadata: TSurveyMetadata = {
+      title: updatedTitle,
+      description: updatedDescription,
+      ogImage: data.ogImage || undefined,
+    };
+
+    const updatedSurvey: TSurvey = {
+      ...survey,
+      metadata: updatedSurveyMetadata,
+    };
+
+    const result = await updateSurveyAction(updatedSurvey);
+
+    if (result?.data) {
+      toast.success(t("environments.surveys.edit.settings_saved_successfully"));
+      reset(data);
+    } else {
+      toast.error(t("common.something_went_wrong_please_try_again"));
+    }
+
+    setIsSaving(false);
+  };
+
+  const inputFields: {
+    name: "title" | "description";
+    label: string;
+    description: string;
+    placeholder: string;
+  }[] = [
+    {
+      name: "title",
+      label: t("environments.surveys.share.link_settings.link_title"),
+      description: t("environments.surveys.share.link_settings.link_title_description"),
+      placeholder: t("environments.surveys.share.link_settings.link_title_placeholder"),
+    },
+    {
+      name: "description",
+      label: t("environments.surveys.share.link_settings.link_description"),
+      description: t("environments.surveys.share.link_settings.link_description_description"),
+      placeholder: t("environments.surveys.share.link_settings.link_description_placeholder"),
+    },
+  ];
+
+  return (
+    <div className="px-1">
+      <FormProvider {...form}>
+        <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
+          {/* Language Selection - only show if survey has multiple languages */}
+          {hasMultipleLanguages && (
+            <FormItem>
+              <FormLabel>{t("common.language")}</FormLabel>
+              <FormControl>
+                <Select value={selectedLanguageCode} onValueChange={setSelectedLanguageCode}>
+                  <SelectTrigger className="bg-white">
+                    <SelectValue />
+                  </SelectTrigger>
+                  <SelectContent>
+                    {enabledLanguages.map((lang) => (
+                      <SelectItem key={lang.language.code} value={lang.language.code} className="bg-white">
+                        {getLanguageLabel(lang.language.code, locale)}
+                      </SelectItem>
+                    ))}
+                  </SelectContent>
+                </Select>
+              </FormControl>
+              <FormDescription>
+                {t("environments.surveys.share.link_settings.language_help_text")}
+              </FormDescription>
+            </FormItem>
+          )}
+
+          {inputFields.map((inputField) => {
+            return (
+              <FormField
+                key={inputField.name}
+                control={form.control}
+                name={inputField.name}
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>{inputField.label}</FormLabel>
+                    <FormControl>
+                      <Input
+                        value={field.value[currentLangCode] || ""}
+                        className="bg-white"
+                        onChange={(e) => {
+                          const updatedValue = {
+                            ...field.value,
+                            [currentLangCode]: e.target.value,
+                          };
+                          field.onChange(updatedValue);
+                        }}
+                        placeholder={inputField.placeholder}
+                        disabled={isReadOnly}
+                      />
+                    </FormControl>
+                    <FormDescription>{inputField.description}</FormDescription>
+                    <FormError />
+                  </FormItem>
+                )}
+              />
+            );
+          })}
+
+          {/* Preview Image */}
+          <FormField
+            control={form.control}
+            name="ogImage"
+            render={({ field }) => (
+              <FormItem>
+                <FormLabel>{t("environments.surveys.share.link_settings.preview_image")}</FormLabel>
+                <FormControl>
+                  <FileInput
+                    id={`og-image-upload-${survey.id}`}
+                    allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
+                    environmentId={survey.environmentId}
+                    onFileUpload={handleFileUpload}
+                    fileUrl={field.value}
+                    maxSizeInMB={5}
+                    disabled={isReadOnly}
+                  />
+                </FormControl>
+                <FormDescription>
+                  {t("environments.surveys.share.link_settings.preview_image_description")}
+                </FormDescription>
+                <FormError />
+              </FormItem>
+            )}
+          />
+
+          {/* Save Button */}
+          <Button type="submit" disabled={isSaving || isReadOnly || !isDirty}>
+            {isSaving ? t("common.saving") : t("common.save")}
+          </Button>
+        </form>
+      </FormProvider>
+    </div>
+  );
+};
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.tsx
index 57efdf064f62..1c7f51e7fcbf 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.tsx
@@ -1,7 +1,11 @@
 "use client";
 
 import { TabContainer } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/tab-container";
-import { ShareViewType } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/types/share";
+import {
+  LinkTabsType,
+  ShareSettingsType,
+  ShareViaType,
+} from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/types/share";
 import { cn } from "@/lib/cn";
 import { Button } from "@/modules/ui/components/button";
 import {
@@ -22,7 +26,8 @@ import { useEffect, useState } from "react";
 
 interface ShareViewProps {
   tabs: Array<{
-    id: ShareViewType;
+    id: ShareViaType | ShareSettingsType;
+    type: LinkTabsType;
     label: string;
     icon: React.ElementType;
     componentType: React.ComponentType<any>;
@@ -30,8 +35,8 @@ interface ShareViewProps {
     title: string;
     description?: string;
   }>;
-  activeId: ShareViewType;
-  setActiveId: React.Dispatch<React.SetStateAction<ShareViewType>>;
+  activeId: ShareViaType | ShareSettingsType;
+  setActiveId: React.Dispatch<React.SetStateAction<ShareViaType | ShareSettingsType>>;
 }
 
 export const ShareView = ({ tabs, activeId, setActiveId }: ShareViewProps) => {
@@ -63,6 +68,21 @@ export const ShareView = ({ tabs, activeId, setActiveId }: ShareViewProps) => {
     );
   };
 
+  const shareSettingsTabs = tabs.filter((tab) => tab.type === LinkTabsType.SHARE_SETTING);
+  const shareViaTabs = tabs.filter((tab) => tab.type === LinkTabsType.SHARE_VIA);
+  const sideBarGroups = [
+    {
+      id: LinkTabsType.SHARE_VIA,
+      label: t("environments.surveys.share.share_view_title"),
+      tabs: shareViaTabs,
+    },
+    {
+      id: LinkTabsType.SHARE_SETTING,
+      label: t("environments.surveys.share.share_settings_title"),
+      tabs: shareSettingsTabs,
+    },
+  ];
+
   return (
     <div className="h-full">
       <div className={`flex h-full lg:grid lg:grid-cols-4`}>
@@ -76,32 +96,34 @@ export const ShareView = ({ tabs, activeId, setActiveId }: ShareViewProps) => {
           }>
           <Sidebar className="relative h-full p-0" variant="inset" collapsible="icon">
             <SidebarContent className="h-full rounded-l-lg border-r border-slate-200 bg-white p-4">
-              <SidebarGroup className="p-0">
-                <SidebarGroupLabel>
-                  <Small className="text-xs text-slate-500">
-                    {t("environments.surveys.share.share_view_title")}
-                  </Small>
-                </SidebarGroupLabel>
-                <SidebarGroupContent>
-                  <SidebarMenu className="flex flex-col gap-1">
-                    {tabs.map((tab) => (
-                      <SidebarMenuItem key={tab.id}>
-                        <SidebarMenuButton
-                          onClick={() => setActiveId(tab.id)}
-                          className={cn(
-                            "flex w-full justify-start rounded-md p-2 text-slate-600 hover:bg-slate-100 hover:text-slate-900",
-                            tab.id === activeId ? "bg-slate-100 font-medium text-slate-900" : "text-slate-700"
-                          )}
-                          tooltip={tab.label}
-                          isActive={tab.id === activeId}>
-                          <tab.icon className="h-4 w-4 text-slate-700" />
-                          <span>{tab.label}</span>
-                        </SidebarMenuButton>
-                      </SidebarMenuItem>
-                    ))}
-                  </SidebarMenu>
-                </SidebarGroupContent>
-              </SidebarGroup>
+              {sideBarGroups.map((group) => (
+                <SidebarGroup className="p-0" key={group.id}>
+                  <SidebarGroupLabel>
+                    <Small className="text-xs text-slate-500">{group.label}</Small>
+                  </SidebarGroupLabel>
+                  <SidebarGroupContent>
+                    <SidebarMenu className="flex flex-col gap-1">
+                      {group.tabs.map((tab) => (
+                        <SidebarMenuItem key={tab.id}>
+                          <SidebarMenuButton
+                            onClick={() => setActiveId(tab.id)}
+                            className={cn(
+                              "flex w-full justify-start rounded-md p-2 text-slate-600 hover:bg-slate-100 hover:text-slate-900",
+                              tab.id === activeId
+                                ? "bg-slate-100 font-medium text-slate-900"
+                                : "text-slate-700"
+                            )}
+                            tooltip={tab.label}
+                            isActive={tab.id === activeId}>
+                            <tab.icon className="h-4 w-4 text-slate-700" />
+                            <span>{tab.label}</span>
+                          </SidebarMenuButton>
+                        </SidebarMenuItem>
+                      ))}
+                    </SidebarMenu>
+                  </SidebarGroupContent>
+                </SidebarGroup>
+              ))}
             </SidebarContent>
           </Sidebar>
         </SidebarProvider>
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/types/share.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/types/share.ts
index 94bccb0b7db5..0dc59e2bda32 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/types/share.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/types/share.ts
@@ -1,4 +1,4 @@
-export enum ShareViewType {
+export enum ShareViaType {
   ANON_LINKS = "anon-links",
   PERSONAL_LINKS = "personal-links",
   EMAIL = "email",
@@ -9,3 +9,12 @@ export enum ShareViewType {
   SOCIAL_MEDIA = "social-media",
   QR_CODE = "qr-code",
 }
+
+export enum ShareSettingsType {
+  LINK_SETTINGS = "link-settings",
+}
+
+export enum LinkTabsType {
+  SHARE_VIA = "share_via",
+  SHARE_SETTING = "share_setting",
+}
diff --git a/apps/web/app/lib/templates.ts b/apps/web/app/lib/templates.ts
index 75022dda4046..890dec3dcccb 100644
--- a/apps/web/app/lib/templates.ts
+++ b/apps/web/app/lib/templates.ts
@@ -3703,5 +3703,6 @@ export const previewSurvey = (projectName: string, t: TFnType) => {
     showLanguageSwitch: false,
     followUps: [],
     isBackButtonHidden: false,
+    metadata: {},
   } as TSurvey;
 };
diff --git a/apps/web/lib/survey/__mock__/survey.mock.ts b/apps/web/lib/survey/__mock__/survey.mock.ts
index f4d681ae2c3b..0223ad1939c9 100644
--- a/apps/web/lib/survey/__mock__/survey.mock.ts
+++ b/apps/web/lib/survey/__mock__/survey.mock.ts
@@ -267,12 +267,14 @@ export const mockSyncSurveyOutput: SurveyMock = {
   showLanguageSwitch: null,
   thankYouCard: null,
   verifyEmail: null,
+  metadata: {},
 };
 
 export const mockSurveyOutput: SurveyMock = {
   type: "link",
   status: "inProgress",
   displayOption: "respondMultiple",
+  metadata: {},
   triggers: [{ actionClass: mockActionClass }],
   projectOverwrites: null,
   recaptcha: null,
@@ -317,6 +319,7 @@ export const updateSurveyInput: TSurvey = {
   showLanguageSwitch: null,
   variables: [],
   followUps: [],
+  metadata: {},
   ...commonMockProperties,
   ...baseSurveyProperties,
 };
diff --git a/apps/web/lib/survey/service.ts b/apps/web/lib/survey/service.ts
index 580540193768..8a911d636757 100644
--- a/apps/web/lib/survey/service.ts
+++ b/apps/web/lib/survey/service.ts
@@ -60,6 +60,7 @@ export const selectSurvey = {
   pin: true,
   showLanguageSwitch: true,
   recaptcha: true,
+  metadata: true,
   languages: {
     select: {
       default: true,
diff --git a/apps/web/locales/de-DE.json b/apps/web/locales/de-DE.json
index ef08b65432ea..c39014f3f2a7 100644
--- a/apps/web/locales/de-DE.json
+++ b/apps/web/locales/de-DE.json
@@ -335,6 +335,7 @@
     "sales": "Vertrieb",
     "save": "Speichern",
     "save_changes": "Änderungen speichern",
+    "saving": "Speichern",
     "scheduled": "Geplant",
     "search": "Suchen",
     "security": "Sicherheit",
@@ -1542,6 +1543,7 @@
         "send_survey_to_audience_who_match": "Umfrage an das Publikum senden, das übereinstimmt...",
         "send_your_respondents_to_a_page_of_your_choice": "Schicke deine Befragten auf eine Seite deiner Wahl.",
         "set_the_global_placement_in_the_look_feel_settings": "Stelle die globale Platzierung in den Look & Feel-Einstellungen ein.",
+        "settings_saved_successfully": "Einstellungen erfolgreich gespeichert",
         "seven_points": "7 Punkte",
         "show_advanced_settings": "Erweiterte Einstellungen anzeigen",
         "show_button": "Button anzeigen",
@@ -1708,6 +1710,19 @@
           "embed_mode_description": "Bette deine Umfrage mit einem minimalistischen Design ein, ohne Karten und Hintergrund.",
           "nav_title": "Auf Website einbetten"
         },
+        "link_settings": {
+          "description": "Vergebe Links einen benutzerdefinierten Titel, eine Beschreibung und ein Bild zum Teilen in der Öffentlichkeit.",
+          "language_help_text": "Die Meta-Daten werden basierend auf dem `lang` Wert in der URL geladen.",
+          "link_description": "Linkbeschreibung",
+          "link_description_description": "Beschreibung mit 55-200 Zeichen funktionieren am besten.",
+          "link_description_placeholder": "Hilf uns, indem du deine Gedanken teilst.",
+          "link_title": "Linktitel",
+          "link_title_description": "Kurze Titel funktionieren am besten als Meta-Titel.",
+          "link_title_placeholder": "Kundenfeedback-Umfrage",
+          "preview_image": "Vorschaubild",
+          "preview_image_description": "Querformatige Bilder mit kleiner Dateigröße (<4MB) funktionieren am besten.",
+          "title": "Link-Einstellungen"
+        },
         "personal_links": {
           "create_and_manage_segments": "Erstellen und verwalten Sie Ihre Segmente unter Kontakte > Segmente",
           "description": "Erstellen Sie persönliche Links für ein Segment und ordnen Sie Umfrageantworten jedem Kontakt zu.",
@@ -1739,6 +1754,7 @@
           "send_preview": "Vorschau senden",
           "send_preview_email": "Vorschau-E-Mail senden"
         },
+        "share_settings_title": "Freigabeeinstellungen",
         "share_view_title": "Teilen über",
         "social_media": {
           "description": "Erhalte Rückmeldungen von deinen Kontakten auf verschiedenen sozialen Medien.",
diff --git a/apps/web/locales/en-US.json b/apps/web/locales/en-US.json
index a67b4185b4fc..17efae654526 100644
--- a/apps/web/locales/en-US.json
+++ b/apps/web/locales/en-US.json
@@ -335,6 +335,7 @@
     "sales": "Sales",
     "save": "Save",
     "save_changes": "Save changes",
+    "saving": "Saving",
     "scheduled": "Scheduled",
     "search": "Search",
     "security": "Security",
@@ -1542,6 +1543,7 @@
         "send_survey_to_audience_who_match": "Send survey to audience who match...",
         "send_your_respondents_to_a_page_of_your_choice": "Send your respondents to a page of your choice.",
         "set_the_global_placement_in_the_look_feel_settings": "Set the global placement in the Look & Feel settings.",
+        "settings_saved_successfully": "Settings saved successfully.",
         "seven_points": "7 points",
         "show_advanced_settings": "Show Advanced settings",
         "show_button": "Show Button",
@@ -1708,6 +1710,19 @@
           "embed_mode_description": "Embed your survey with a minimalist design, discarding padding and background.",
           "nav_title": "Website embed"
         },
+        "link_settings": {
+          "description": "Give links a custom title, description and image for sharing in public.",
+          "language_help_text": "The meta data is loaded based on the `lang` value in the URL.",
+          "link_description": "Link description",
+          "link_description_description": "Descriptions between 55-200 characters perform best.",
+          "link_description_placeholder": "Help us improve by sharing your thoughts.",
+          "link_title": "Link title",
+          "link_title_description": "Short titles perform best as Meta Titles.",
+          "link_title_placeholder": "Customer Feedback Survey",
+          "preview_image": "Preview image",
+          "preview_image_description": "Landscape images with small file sizes (<4MB) perform best.",
+          "title": "Link settings"
+        },
         "personal_links": {
           "create_and_manage_segments": "Create and manage your Segments under Contacts > Segments",
           "description": "Generate personal links for a segment and match survey responses to each contact.",
@@ -1739,6 +1754,7 @@
           "send_preview": "Send preview",
           "send_preview_email": "Send preview email"
         },
+        "share_settings_title": "Share settings",
         "share_view_title": "Share via",
         "social_media": {
           "description": "Get responses from your contacts on various social media networks.",
diff --git a/apps/web/locales/fr-FR.json b/apps/web/locales/fr-FR.json
index 0402fa48bfda..db6c01cb4405 100644
--- a/apps/web/locales/fr-FR.json
+++ b/apps/web/locales/fr-FR.json
@@ -335,6 +335,7 @@
     "sales": "Ventes",
     "save": "Enregistrer",
     "save_changes": "Enregistrer les modifications",
+    "saving": "Sauvegarder",
     "scheduled": "Programmé",
     "search": "Recherche",
     "security": "Sécurité",
@@ -1542,6 +1543,7 @@
         "send_survey_to_audience_who_match": "Envoyer l'enquête au public qui correspond...",
         "send_your_respondents_to_a_page_of_your_choice": "Envoyez vos répondants vers une page de votre choix.",
         "set_the_global_placement_in_the_look_feel_settings": "Définissez le placement global dans les paramètres d'apparence.",
+        "settings_saved_successfully": "Paramètres enregistrés avec succès",
         "seven_points": "7 points",
         "show_advanced_settings": "Afficher les paramètres avancés",
         "show_button": "Afficher le bouton",
@@ -1708,6 +1710,19 @@
           "embed_mode_description": "Intégrez votre enquête avec un design minimaliste, en supprimant les marges et l'arrière-plan.",
           "nav_title": "Incorporer sur le site web"
         },
+        "link_settings": {
+          "description": "Donnez aux liens un titre, une description et une image personnalisés pour le partage en public.",
+          "language_help_text": "Les métadonnées sont chargées en fonction de la valeur « lang » dans l'URL.",
+          "link_description": "Description du lien",
+          "link_description_description": "« Les descriptions entre 55 et 200 caractères donnent les meilleurs résultats. »",
+          "link_description_placeholder": "Aidez-nous à nous améliorer en partageant vos pensées.",
+          "link_title": "Titre du lien",
+          "link_title_description": "Les titres courts fonctionnent mieux comme titres méta.",
+          "link_title_placeholder": "Sondage de Retour Clients",
+          "preview_image": "Aperçu de l'image",
+          "preview_image_description": "Les images en paysage avec de petites tailles de fichier (<4MB) fonctionnent le mieux.",
+          "title": "Paramètres de lien"
+        },
         "personal_links": {
           "create_and_manage_segments": "Créez et gérez vos Segments sous Contacts > Segments",
           "description": "Générez des liens personnels pour un segment et associez les réponses du sondage à chaque contact.",
@@ -1739,6 +1754,7 @@
           "send_preview": "Envoyer un aperçu",
           "send_preview_email": "Envoyer un e-mail d'aperçu"
         },
+        "share_settings_title": "Partager les paramètres",
         "share_view_title": "Partager par",
         "social_media": {
           "description": "Obtenez des réponses de vos contacts sur divers réseaux sociaux.",
diff --git a/apps/web/locales/pt-BR.json b/apps/web/locales/pt-BR.json
index d74e4e381424..1d0772ef4280 100644
--- a/apps/web/locales/pt-BR.json
+++ b/apps/web/locales/pt-BR.json
@@ -335,6 +335,7 @@
     "sales": "vendas",
     "save": "Salvar",
     "save_changes": "Salvar alterações",
+    "saving": "Salvando",
     "scheduled": "agendado",
     "search": "Buscar",
     "security": "Segurança",
@@ -1542,6 +1543,7 @@
         "send_survey_to_audience_who_match": "Enviar pesquisa para o público que corresponde...",
         "send_your_respondents_to_a_page_of_your_choice": "Envie seus respondentes para uma página de sua escolha.",
         "set_the_global_placement_in_the_look_feel_settings": "Defina o posicionamento global nas configurações de Aparência.",
+        "settings_saved_successfully": "Configurações salvas com sucesso",
         "seven_points": "7 pontos",
         "show_advanced_settings": "Mostrar configurações avançadas",
         "show_button": "Mostrar Botão",
@@ -1708,6 +1710,19 @@
           "embed_mode_description": "Incorpore sua pesquisa com um design minimalista, sem preenchimento e fundo.",
           "nav_title": "Incorporar no site"
         },
+        "link_settings": {
+          "description": "Dê aos links um título, descrição e imagem personalizados para compartilhamento público.",
+          "language_help_text": "Os metadados são carregados com base no valor `lang` na URL.",
+          "link_description": "Descrição do link",
+          "link_description_description": "\"Descrições entre 55-200 caracteres têm um melhor desempenho.\"",
+          "link_description_placeholder": "Ajude-nos a melhorar compartilhando suas opiniões.",
+          "link_title": "Título do link",
+          "link_title_description": "Títulos curtos têm melhor desempenho como Meta Títulos.",
+          "link_title_placeholder": "Pesquisa de Feedback do Cliente",
+          "preview_image": "Imagem de prévia",
+          "preview_image_description": "Imagens em paisagem com tamanhos de arquivo pequenos (<4MB) têm o melhor desempenho.",
+          "title": "Configurações de link"
+        },
         "personal_links": {
           "create_and_manage_segments": "Crie e gerencie seus Segmentos em Contatos > Segmentos",
           "description": "Gerar links pessoais para um segmento e associar respostas de pesquisa a cada contato.",
@@ -1739,6 +1754,7 @@
           "send_preview": "Enviar prévia",
           "send_preview_email": "Enviar prévia de e-mail"
         },
+        "share_settings_title": "Compartilhar configurações",
         "share_view_title": "Compartilhar via",
         "social_media": {
           "description": "Obtenha respostas de seus contatos em várias redes sociais.",
diff --git a/apps/web/locales/pt-PT.json b/apps/web/locales/pt-PT.json
index 4ca8122d99b3..dd7345cb2e7d 100644
--- a/apps/web/locales/pt-PT.json
+++ b/apps/web/locales/pt-PT.json
@@ -335,6 +335,7 @@
     "sales": "Vendas",
     "save": "Guardar",
     "save_changes": "Guardar alterações",
+    "saving": "Guardando",
     "scheduled": "Agendado",
     "search": "Procurar",
     "security": "Segurança",
@@ -1542,6 +1543,7 @@
         "send_survey_to_audience_who_match": "Enviar inquérito para o público que corresponde...",
         "send_your_respondents_to_a_page_of_your_choice": "Envie os seus respondentes para uma página à sua escolha.",
         "set_the_global_placement_in_the_look_feel_settings": "Definir a colocação global nas definições de Aparência.",
+        "settings_saved_successfully": "Definições guardadas com sucesso",
         "seven_points": "7 pontos",
         "show_advanced_settings": "Mostrar definições avançadas",
         "show_button": "Mostrar Botão",
@@ -1708,6 +1710,19 @@
           "embed_mode_description": "Incorpore o seu inquérito com um design minimalista, descartando o preenchimento e o fundo.",
           "nav_title": "Incorporar no site"
         },
+        "link_settings": {
+          "description": "Dê aos links um título, descrição e imagem personalizados para partilhar publicamente.",
+          "language_help_text": "Os metadados são carregados com base no valor `lang` no URL.",
+          "link_description": "Descrição do link",
+          "link_description_description": "Descrições entre 55 a 200 caracteres têm melhor desempenho.",
+          "link_description_placeholder": "Ajude-nos a melhorar compartilhando suas opiniões.",
+          "link_title": "Título do Link",
+          "link_title_description": "Títulos curtos têm melhor desempenho como Meta Titles.",
+          "link_title_placeholder": "Inquérito de Feedback do Cliente",
+          "preview_image": "Pré-visualização da imagem",
+          "preview_image_description": "Imagens de paisagem com tamanhos pequenos (<4MB) apresentam melhor desempenho.",
+          "title": "Definições de ligação"
+        },
         "personal_links": {
           "create_and_manage_segments": "Crie e gere os seus Segmentos em Contactos > Segmentos",
           "description": "Gerar links pessoais para um segmento e associar as respostas do inquérito a cada contacto.",
@@ -1739,6 +1754,7 @@
           "send_preview": "Enviar pré-visualização",
           "send_preview_email": "Enviar pré-visualização de email"
         },
+        "share_settings_title": "Partilhar Configurações",
         "share_view_title": "Partilhar via",
         "social_media": {
           "description": "Obtenha respostas dos seus contactos em várias redes sociais.",
diff --git a/apps/web/locales/ro-RO.json b/apps/web/locales/ro-RO.json
index dd6b77104cb4..a2c55d26a7f9 100644
--- a/apps/web/locales/ro-RO.json
+++ b/apps/web/locales/ro-RO.json
@@ -335,6 +335,7 @@
     "sales": "Vânzări",
     "save": "Salvează",
     "save_changes": "Salvează modificările",
+    "saving": "Salvare",
     "scheduled": "Programat",
     "search": "Căutare",
     "security": "Securitate",
@@ -1542,6 +1543,7 @@
         "send_survey_to_audience_who_match": "Trimiteți sondajul către publicul care se potrivește...",
         "send_your_respondents_to_a_page_of_your_choice": "Trimiteți respondenții către o pagină la alegerea dumneavoastră",
         "set_the_global_placement_in_the_look_feel_settings": "Setați amplasarea globală în setările Aspect & Stil.",
+        "settings_saved_successfully": "Setările au fost salvate cu succes.",
         "seven_points": "7 puncte",
         "show_advanced_settings": "Afișați setările avansate",
         "show_button": "Afișează butonul",
@@ -1708,6 +1710,19 @@
           "embed_mode_description": "Incorporează sondajul tău cu un design minimalist, eliminând spațiul și fundalul.",
           "nav_title": "Incorporare pe site web"
         },
+        "link_settings": {
+          "description": "Atribuiți linkurilor un titlu, o descriere și o imagine personalizată pentru partajarea în public.",
+          "language_help_text": "Meta datele sunt încărcate pe baza valorii `lang` din URL.",
+          "link_description": "Descriere legătură",
+          "link_description_description": "Descrierile între 55-200 de caractere au cele mai bune performanțe.",
+          "link_description_placeholder": "Ajutați-ne să ne îmbunătățim împărtășindu-vă gândurile.",
+          "link_title": "Titlu link",
+          "link_title_description": "Titlurile scurte funcționează cel mai bine ca Meta Title-uri.",
+          "link_title_placeholder": "Chestionar de feedback al clienților",
+          "preview_image": "Previzualizare imagine",
+          "preview_image_description": "Imaginile panoramice cu dimensiuni de fișier mici (<4MB) au cel mai bun randament.",
+          "title": "Setări link"
+        },
         "personal_links": {
           "create_and_manage_segments": "Creați și gestionați segmentele dvs. sub Contacte > Segmente",
           "description": "Generează linkuri personale pentru un segment și corelează răspunsurile la sondaje cu fiecare contact.",
@@ -1739,6 +1754,7 @@
           "send_preview": "Trimite previzualizare",
           "send_preview_email": "Trimite email de previzualizare"
         },
+        "share_settings_title": "Setări de partajare",
         "share_view_title": "Distribuie prin",
         "social_media": {
           "description": "Obține răspunsuri de la contactele tale pe diferite rețele sociale.",
diff --git a/apps/web/locales/zh-Hant-TW.json b/apps/web/locales/zh-Hant-TW.json
index 2cfe09366145..73eb4fc6a84e 100644
--- a/apps/web/locales/zh-Hant-TW.json
+++ b/apps/web/locales/zh-Hant-TW.json
@@ -335,6 +335,7 @@
     "sales": "銷售",
     "save": "儲存",
     "save_changes": "儲存變更",
+    "saving": "儲存",
     "scheduled": "已排程",
     "search": "搜尋",
     "security": "安全性",
@@ -1542,6 +1543,7 @@
         "send_survey_to_audience_who_match": "將問卷發送給符合以下條件的受眾：",
         "send_your_respondents_to_a_page_of_your_choice": "將您的回應者傳送到您選擇的頁面。",
         "set_the_global_placement_in_the_look_feel_settings": "在「外觀與風格」設定中設定整體位置。",
+        "settings_saved_successfully": "設定已成功儲存",
         "seven_points": "7 分",
         "show_advanced_settings": "顯示進階設定",
         "show_button": "顯示按鈕",
@@ -1708,6 +1710,19 @@
           "embed_mode_description": "以簡約設計嵌入您的問卷，捨棄邊距和背景。",
           "nav_title": "嵌入網站"
         },
+        "link_settings": {
+          "description": "為 分享 在公開的時候給予 連結 自訂 標題 、描述 和 圖像。",
+          "language_help_text": "中 繼資料 會 根據 URL 中 的 `lang` 值 載入。",
+          "link_description": "連結描述",
+          "link_description_description": "描述在 55 - 200 個字符之間的表現最好。",
+          "link_description_placeholder": "幫助 我們 改善 , 分享 您 的 想法 。",
+          "link_title": "連結標題",
+          "link_title_description": "短 標題 在 Meta Titles 中表現最佳。",
+          "link_title_placeholder": "顧客 回饋 調查",
+          "preview_image": "預覽 圖片",
+          "preview_image_description": "景觀 圖片 檔案 大小 小於 4MB 效果 最佳。",
+          "title": "連結 設定"
+        },
         "personal_links": {
           "create_and_manage_segments": "在 聯絡人 > 分段 中建立和管理您的分段",
           "description": "為 一個 群組 生成 個人 連結，並 將 調查 回應 對應 到 每個 聯絡人。",
@@ -1739,6 +1754,7 @@
           "send_preview": "發送預覽",
           "send_preview_email": "發送預覽電子郵件"
         },
+        "share_settings_title": "分享 設定",
         "share_view_title": "透過 分享",
         "social_media": {
           "description": "從 您 的 聯絡人 在 各 種 社交 媒體 網絡 上 獲得 回應。",
diff --git a/apps/web/modules/survey/lib/survey.ts b/apps/web/modules/survey/lib/survey.ts
index 7b347ee5968f..691c10eb6dd1 100644
--- a/apps/web/modules/survey/lib/survey.ts
+++ b/apps/web/modules/survey/lib/survey.ts
@@ -39,6 +39,7 @@ export const selectSurvey = {
   showLanguageSwitch: true,
   recaptcha: true,
   isBackButtonHidden: true,
+  metadata: true,
   languages: {
     select: {
       default: true,
diff --git a/apps/web/modules/survey/link/lib/metadata-utils.ts b/apps/web/modules/survey/link/lib/metadata-utils.ts
index 58188ec42aa1..cf6acc434c2b 100644
--- a/apps/web/modules/survey/link/lib/metadata-utils.ts
+++ b/apps/web/modules/survey/link/lib/metadata-utils.ts
@@ -4,7 +4,13 @@ import { COLOR_DEFAULTS } from "@/lib/styling/constants";
 import { getSurvey } from "@/modules/survey/lib/survey";
 import { getProjectByEnvironmentId } from "@/modules/survey/link/lib/project";
 import { Metadata } from "next";
-import { TSurveyWelcomeCard } from "@formbricks/types/surveys/types";
+
+type TBasicSurveyMetadata = {
+  title: string;
+  description: string;
+  survey: Awaited<ReturnType<typeof getSurvey>> | null;
+  ogImage?: string;
+};
 
 /**
  * Utility function to encode name for URL usage
@@ -17,9 +23,12 @@ export const getNameForURL = (url: string) => url.replace(/ /g, "%20");
 export const getBrandColorForURL = (url: string) => url.replace(/#/g, "%23");
 
 /**
- * Get basic survey metadata (title and description) based on welcome card or survey name
+ * Get basic survey metadata (title and description) based on link metadata, welcome card or survey name
  */
-export const getBasicSurveyMetadata = async (surveyId: string) => {
+export const getBasicSurveyMetadata = async (
+  surveyId: string,
+  languageCode?: string
+): Promise<TBasicSurveyMetadata> => {
   const survey = await getSurvey(surveyId);
 
   // If survey doesn't exist, return default metadata
@@ -28,38 +37,52 @@ export const getBasicSurveyMetadata = async (surveyId: string) => {
       title: "Survey",
       description: "Complete this survey",
       survey: null,
+      ogImage: undefined,
     };
   }
 
-  const project = await getProjectByEnvironmentId(survey.environmentId);
-  const welcomeCard = survey.welcomeCard as TSurveyWelcomeCard;
+  const metadata = survey.metadata;
+  const welcomeCard = survey.welcomeCard;
 
-  // Set title to either welcome card headline or survey name
+  // Determine language code to use for metadata
+  const langCode = languageCode || "default";
+
+  // Set title - priority: custom link metadata > welcome card > survey name
   let title = "Survey";
-  if (welcomeCard.enabled && welcomeCard.headline?.default) {
+  if (metadata.title?.[langCode]) {
+    title = metadata.title[langCode];
+  } else if (welcomeCard.enabled && welcomeCard.headline?.default) {
     title = welcomeCard.headline.default;
   } else {
     title = survey.name;
   }
 
-  // Set description to either welcome card html content or default
+  // Set description - priority: custom link metadata > welcome card > default
   let description = "Complete this survey";
-  if (welcomeCard.enabled && welcomeCard.html?.default) {
-    description = welcomeCard.html.default;
+  if (metadata.description?.[langCode]) {
+    description = metadata.description[langCode];
   }
 
-  // Add product name in title if it's Formbricks cloud
-  if (IS_FORMBRICKS_CLOUD) {
-    title = `${title} | Formbricks`;
-  } else if (project) {
-    // Since project name is not available in the returned type, we'll just use a generic name
-    title = `${title} | Survey`;
+  // Get OG image from link metadata if available
+  const { ogImage } = metadata;
+
+  // Add product name in title if it's Formbricks cloud and not using custom metadata
+  if (!metadata.title?.[langCode]) {
+    if (IS_FORMBRICKS_CLOUD) {
+      title = `${title} | Formbricks`;
+    } else {
+      const project = await getProjectByEnvironmentId(survey.environmentId);
+      if (project) {
+        title = `${title} | ${project.name}`;
+      }
+    }
   }
 
   return {
     title,
     description,
     survey,
+    ogImage,
   };
 };
 
diff --git a/apps/web/modules/survey/link/lib/project.ts b/apps/web/modules/survey/link/lib/project.ts
index f3d91ad8cf0d..baee5af7efc1 100644
--- a/apps/web/modules/survey/link/lib/project.ts
+++ b/apps/web/modules/survey/link/lib/project.ts
@@ -8,7 +8,9 @@ import { ZId } from "@formbricks/types/common";
 import { DatabaseError } from "@formbricks/types/errors";
 
 export const getProjectByEnvironmentId = reactCache(
-  async (environmentId: string): Promise<Pick<Project, "styling" | "logo" | "linkSurveyBranding"> | null> => {
+  async (
+    environmentId: string
+  ): Promise<Pick<Project, "styling" | "logo" | "linkSurveyBranding" | "name"> | null> => {
     validateInputs([environmentId, ZId]);
 
     let projectPrisma;
@@ -26,6 +28,7 @@ export const getProjectByEnvironmentId = reactCache(
           styling: true,
           logo: true,
           linkSurveyBranding: true,
+          name: true,
         },
       });
 
diff --git a/apps/web/modules/survey/link/metadata.ts b/apps/web/modules/survey/link/metadata.ts
index 6fd574dbd065..3b63212249c3 100644
--- a/apps/web/modules/survey/link/metadata.ts
+++ b/apps/web/modules/survey/link/metadata.ts
@@ -1,36 +1,40 @@
-import { COLOR_DEFAULTS } from "@/lib/styling/constants";
 import { getSurveyMetadata } from "@/modules/survey/link/lib/data";
 import { Metadata } from "next";
 import { notFound } from "next/navigation";
-import { getBrandColorForURL, getNameForURL, getSurveyOpenGraphMetadata } from "./lib/metadata-utils";
+import { getBasicSurveyMetadata, getSurveyOpenGraphMetadata } from "./lib/metadata-utils";
 
-export const getMetadataForLinkSurvey = async (surveyId: string): Promise<Metadata> => {
+export const getMetadataForLinkSurvey = async (
+  surveyId: string,
+  languageCode?: string
+): Promise<Metadata> => {
   const survey = await getSurveyMetadata(surveyId);
 
   if (!survey || survey.type !== "link" || survey.status === "draft") {
     notFound();
   }
 
-  const brandColor = getBrandColorForURL(survey.styling?.brandColor?.light ?? COLOR_DEFAULTS.brandColor);
-  const surveyName = getNameForURL(survey.name);
-  const ogImgURL = `/api/v1/client/og?brandColor=${brandColor}&name=${surveyName}`;
+  // Get enhanced metadata that includes custom link metadata
+  const { title, description, ogImage } = await getBasicSurveyMetadata(surveyId, languageCode);
 
-  // Use the shared function for creating the base metadata but override with specific OpenGraph data
-  const baseMetadata = getSurveyOpenGraphMetadata(survey.id, survey.name);
+  // Use the shared function for creating the base metadata but override with custom data
+  const baseMetadata = getSurveyOpenGraphMetadata(survey.id, title);
 
-  // Override with the custom image URL that uses the survey's brand color
+  // Override with the custom image URL
   if (baseMetadata.openGraph) {
-    baseMetadata.openGraph.images = [ogImgURL];
+    baseMetadata.openGraph.images = ogImage ?? baseMetadata.openGraph.images;
+    baseMetadata.openGraph.description = description;
   }
 
   if (baseMetadata.twitter) {
-    baseMetadata.twitter.images = [ogImgURL];
+    baseMetadata.twitter.images = ogImage ?? baseMetadata.twitter.images;
+    baseMetadata.twitter.description = description;
   }
 
   const canonicalPath = `/s/${surveyId}`;
 
   return {
-    title: survey.name,
+    title,
+    description,
     ...baseMetadata,
     alternates: {
       canonical: canonicalPath,
diff --git a/apps/web/modules/survey/link/page.tsx b/apps/web/modules/survey/link/page.tsx
index 587d6c676643..53a58f649d37 100644
--- a/apps/web/modules/survey/link/page.tsx
+++ b/apps/web/modules/survey/link/page.tsx
@@ -25,12 +25,16 @@ interface LinkSurveyPageProps {
 
 export const generateMetadata = async (props: LinkSurveyPageProps): Promise<Metadata> => {
   const params = await props.params;
+  const searchParams = await props.searchParams;
   const validId = ZId.safeParse(params.surveyId);
   if (!validId.success) {
     notFound();
   }
 
-  return getMetadataForLinkSurvey(params.surveyId);
+  // Extract language code from URL params
+  const languageCode = typeof searchParams.lang === "string" ? searchParams.lang : undefined;
+
+  return getMetadataForLinkSurvey(params.surveyId, languageCode);
 };
 
 export const LinkSurveyPage = async (props: LinkSurveyPageProps) => {
diff --git a/apps/web/modules/survey/templates/lib/minimal-survey.ts b/apps/web/modules/survey/templates/lib/minimal-survey.ts
index 19061cd87417..fc80c38ac321 100644
--- a/apps/web/modules/survey/templates/lib/minimal-survey.ts
+++ b/apps/web/modules/survey/templates/lib/minimal-survey.ts
@@ -42,4 +42,5 @@ export const getMinimalSurvey = (t: TFnType): TSurvey => ({
   variables: [],
   followUps: [],
   isBackButtonHidden: false,
+  metadata: {},
 });
diff --git a/apps/web/playwright/survey.spec.ts b/apps/web/playwright/survey.spec.ts
index d86fca1f78b5..29e63694a4ed 100644
--- a/apps/web/playwright/survey.spec.ts
+++ b/apps/web/playwright/survey.spec.ts
@@ -41,8 +41,12 @@ test.describe("Survey Create & Submit Response without logic", async () => {
       await page.waitForURL(/\/s\/[A-Za-z0-9]+$/);
 
       // Welcome Card
-      await expect(page.getByText(surveys.createAndSubmit.welcomeCard.headline)).toBeVisible();
-      await expect(page.getByText(surveys.createAndSubmit.welcomeCard.description)).toBeVisible();
+      await expect(
+        page.locator("#questionCard--1").getByText(surveys.createAndSubmit.welcomeCard.headline)
+      ).toBeVisible();
+      await expect(
+        page.locator("#questionCard--1").getByText(surveys.createAndSubmit.welcomeCard.description)
+      ).toBeVisible();
       await page.locator("#questionCard--1").getByRole("button", { name: "Next" }).click();
 
       // Open Text Question
@@ -673,8 +677,12 @@ test.describe("Testing Survey with advanced logic", async () => {
       await page.waitForURL(/\/s\/[A-Za-z0-9]+$/);
 
       // Welcome Card
-      await expect(page.getByText(surveys.createWithLogicAndSubmit.welcomeCard.headline)).toBeVisible();
-      await expect(page.getByText(surveys.createWithLogicAndSubmit.welcomeCard.description)).toBeVisible();
+      await expect(
+        page.locator("#questionCard--1").getByText(surveys.createWithLogicAndSubmit.welcomeCard.headline)
+      ).toBeVisible();
+      await expect(
+        page.locator("#questionCard--1").getByText(surveys.createWithLogicAndSubmit.welcomeCard.description)
+      ).toBeVisible();
       await page.locator("#questionCard--1").getByRole("button", { name: "Next" }).click();
 
       // Open Text Question
diff --git a/packages/database/json-types.ts b/packages/database/json-types.ts
index e1489c6e0a98..0e8965ae2ff8 100644
--- a/packages/database/json-types.ts
+++ b/packages/database/json-types.ts
@@ -11,6 +11,7 @@ import {
   type TSurveyClosedMessage,
   type TSurveyEnding,
   type TSurveyHiddenFields,
+  type TSurveyMetadata,
   type TSurveyProjectOverwrites,
   type TSurveyQuestions,
   type TSurveyRecaptcha,
@@ -49,5 +50,6 @@ declare global {
     export type SurveyFollowUpTrigger = TSurveyFollowUpTrigger;
     export type SurveyFollowUpAction = TSurveyFollowUpAction;
     export type OrganizationAccess = TOrganizationAccess;
+    export type SurveyMetadata = TSurveyMetadata;
   }
 }
diff --git a/packages/database/migration/20250812041349_add_metadata_to_survey/migration.sql b/packages/database/migration/20250812041349_add_metadata_to_survey/migration.sql
new file mode 100644
index 000000000000..c1b3c99ea8bd
--- /dev/null
+++ b/packages/database/migration/20250812041349_add_metadata_to_survey/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Survey" ADD COLUMN "metadata" JSONB NOT NULL DEFAULT '{}';
\ No newline at end of file
diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma
index 6ce0466a5fd5..a16baac201bf 100644
--- a/packages/database/schema.prisma
+++ b/packages/database/schema.prisma
@@ -389,6 +389,8 @@ model Survey {
   followUps                       SurveyFollowUp[]
   /// [SurveyRecaptcha]
   recaptcha                       Json?            @default("{\"enabled\": false, \"threshold\":0.1}")
+  /// [SurveyLinkMetadata]
+  metadata                        Json             @default("{}")
 
   @@index([environmentId, updatedAt])
   @@index([segmentId])
diff --git a/packages/database/zod/surveys.ts b/packages/database/zod/surveys.ts
index d65b202704c8..459a92d380d4 100644
--- a/packages/database/zod/surveys.ts
+++ b/packages/database/zod/surveys.ts
@@ -2,7 +2,13 @@ import { SurveyStatus, SurveyType } from "@prisma/client";
 import { z } from "zod";
 import { extendZodWithOpenApi } from "zod-openapi";
 // eslint-disable-next-line import/no-relative-packages -- Need to import from parent package
-import { ZSurveyEnding, ZSurveyQuestion, ZSurveyRecaptcha, ZSurveyVariable } from "../../types/surveys/types";
+import {
+  ZSurveyEnding,
+  ZSurveyMetadata,
+  ZSurveyQuestion,
+  ZSurveyRecaptcha,
+  ZSurveyVariable,
+} from "../../types/surveys/types";
 
 extendZodWithOpenApi(z);
 
@@ -213,6 +219,9 @@ const ZSurveyBase = z.object({
   recaptcha: ZSurveyRecaptcha.openapi({
     description: "Google reCAPTCHA configuration",
   }),
+  metadata: ZSurveyMetadata.openapi({
+    description: "Custom link metadata for social sharing",
+  }),
   displayPercentage: z.number().nullable().openapi({
     description: "The display percentage of the survey",
   }),
diff --git a/packages/types/surveys/types.ts b/packages/types/surveys/types.ts
index 6c21c8af6331..3cb425ad5758 100644
--- a/packages/types/surveys/types.ts
+++ b/packages/types/surveys/types.ts
@@ -227,6 +227,14 @@ export const ZSurveyRecaptcha = z
 
 export type TSurveyRecaptcha = z.infer<typeof ZSurveyRecaptcha>;
 
+export const ZSurveyMetadata = z.object({
+  title: ZI18nString.optional(),
+  description: ZI18nString.optional(),
+  ogImage: z.string().url().optional(),
+});
+
+export type TSurveyMetadata = z.infer<typeof ZSurveyMetadata>;
+
 export const ZSurveyQuestionChoice = z.object({
   id: z.string(),
   label: ZI18nString,
@@ -851,6 +859,7 @@ export const ZSurvey = z
     pin: z.string().length(4, { message: "PIN must be a four digit number" }).nullish(),
     displayPercentage: z.number().min(0.01).max(100).nullable(),
     languages: z.array(ZSurveyLanguage),
+    metadata: ZSurveyMetadata,
   })
   .superRefine((survey, ctx) => {
     const { questions, languages, welcomeCard, endings, isBackButtonHidden } = survey;


Test patch:
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.test.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.test.tsx
index cb2818610e48..eb91dc8188ea 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/share-survey-modal.test.tsx
@@ -172,6 +172,15 @@ vi.mock("./shareEmbedModal/success-view", () => ({
   ),
 }));
 
+// Mock LinkSettingsTab
+vi.mock("./shareEmbedModal/link-settings-tab", () => ({
+  LinkSettingsTab: ({ survey, isReadOnly, locale }: any) => (
+    <div data-testid="link-settings-tab">
+      LinkSettings for {survey.id} - ReadOnly: {isReadOnly.toString()} - Locale: {locale}
+    </div>
+  ),
+}));
+
 // Mock lucide-react icons
 vi.mock("lucide-react", async (importOriginal) => {
   const actual = (await importOriginal()) as any;
@@ -184,6 +193,7 @@ vi.mock("lucide-react", async (importOriginal) => {
     SmartphoneIcon: () => <svg data-testid="smartphone-icon" />,
     SquareStack: () => <svg data-testid="square-stack-icon" />,
     UserIcon: () => <svg data-testid="user-icon" />,
+    Settings: () => <svg data-testid="settings-icon" />,
   };
 });
 
@@ -227,6 +237,7 @@ const mockSurvey: TSurvey = {
   recaptcha: null,
   isSingleResponsePerEmailEnabled: false,
   isBackButtonHidden: false,
+  metadata: {},
 };
 
 const mockAppSurvey: TSurvey = {
@@ -270,6 +281,7 @@ const defaultProps = {
   segments: mockSegments,
   isContactsEnabled: true,
   isFormbricksCloud: false,
+  isReadOnly: false,
 };
 
 describe("ShareSurveyModal", () => {
@@ -392,7 +404,6 @@ describe("ShareSurveyModal", () => {
   });
 
   test("resets to start view when modal is closed and reopened", async () => {
-    const user = userEvent.setup();
     const { rerender } = render(<ShareSurveyModal {...defaultProps} modalView="share" />);
 
     expect(screen.getByTestId("share-view")).toBeInTheDocument();
@@ -468,4 +479,40 @@ describe("ShareSurveyModal", () => {
     expect(screen.getByTestId("app-tab")).toBeInTheDocument();
     expect(screen.queryByTestId("share-view")).not.toBeInTheDocument();
   });
+
+  test("includes link settings tab in share tabs for link surveys", () => {
+    render(<ShareSurveyModal {...defaultProps} modalView="share" />);
+
+    const shareView = screen.getByTestId("share-view");
+    expect(shareView).toBeInTheDocument();
+
+    // Check that tabs are rendered and include link settings
+    const tabsContainer = screen.getByTestId("tabs");
+    expect(tabsContainer).toBeInTheDocument();
+
+    // Look for the link-settings tab button
+    expect(screen.getByTestId("tab-link-settings")).toBeInTheDocument();
+  });
+
+  test("does not include link settings for app surveys", () => {
+    render(<ShareSurveyModal {...defaultProps} survey={mockAppSurvey} modalView="share" />);
+
+    // App surveys should render TabContainer, not ShareView with link settings
+    expect(screen.getByTestId("tab-container")).toBeInTheDocument();
+    expect(screen.getByTestId("app-tab")).toBeInTheDocument();
+    expect(screen.queryByTestId("share-view")).not.toBeInTheDocument();
+    expect(screen.queryByTestId("tab-link-settings")).not.toBeInTheDocument();
+  });
+
+  test("handles locale prop correctly for link settings", () => {
+    const customProps = {
+      ...defaultProps,
+      modalView: "share" as const,
+    };
+
+    render(<ShareSurveyModal {...customProps} />);
+
+    // Verify that ShareView is rendered (which would contain LinkSettingsTab)
+    expect(screen.getByTestId("share-view")).toBeInTheDocument();
+  });
 });
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/link-settings-tab.test.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/link-settings-tab.test.tsx
new file mode 100644
index 000000000000..a7749e31ecd4
--- /dev/null
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/link-settings-tab.test.tsx
@@ -0,0 +1,428 @@
+import { useSurvey } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/context/survey-context";
+import { createI18nString, extractLanguageCodes, getEnabledLanguages } from "@/lib/i18n/utils";
+import { updateSurveyAction } from "@/modules/survey/editor/actions";
+import "@testing-library/jest-dom/vitest";
+import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+import { getLanguageLabel } from "@formbricks/i18n-utils/src/utils";
+import { TSurvey, TSurveyLanguage } from "@formbricks/types/surveys/types";
+import { LinkSettingsTab } from "./link-settings-tab";
+
+// Mock dependencies
+vi.mock("@/lib/i18n/utils", () => ({
+  createI18nString: vi.fn(),
+  extractLanguageCodes: vi.fn(),
+  getEnabledLanguages: vi.fn(),
+}));
+
+vi.mock("@/modules/survey/editor/actions", () => ({
+  updateSurveyAction: vi.fn(),
+}));
+
+vi.mock("@formbricks/i18n-utils/src/utils", () => ({
+  getLanguageLabel: vi.fn(),
+}));
+
+vi.mock("@/app/(app)/environments/[environmentId]/surveys/[surveyId]/context/survey-context", () => ({
+  useSurvey: vi.fn(),
+}));
+
+vi.mock("react-hot-toast", () => ({
+  default: {
+    success: vi.fn(),
+    error: vi.fn(),
+  },
+}));
+
+vi.mock("@/modules/ui/components/file-input", () => ({
+  FileInput: vi.fn(({ onFileUpload, fileUrl, disabled, id }) => (
+    <div data-testid={id}>
+      <input
+        data-testid="file-input"
+        type="text"
+        value={fileUrl || ""}
+        onChange={(e) => onFileUpload(e.target.value ? [e.target.value] : undefined)}
+        disabled={disabled}
+      />
+    </div>
+  )),
+}));
+
+vi.mock("@/modules/ui/components/button", () => ({
+  Button: vi.fn(({ children, disabled, type, ...props }) => (
+    <button data-testid="save-button" disabled={disabled} type={type} {...props}>
+      {children}
+    </button>
+  )),
+}));
+
+const mockSurvey: TSurvey = {
+  id: "test-survey-id",
+  createdAt: new Date(),
+  updatedAt: new Date(),
+  name: "Test Survey",
+  type: "link",
+  environmentId: "test-env-id",
+  status: "inProgress",
+  displayOption: "displayOnce",
+  autoClose: null,
+  triggers: [],
+  recontactDays: null,
+  displayLimit: null,
+  welcomeCard: { enabled: false, timeToFinish: false, showResponseCount: false },
+  questions: [],
+  endings: [],
+  hiddenFields: { enabled: false },
+  displayPercentage: null,
+  autoComplete: null,
+  segment: null,
+  languages: [
+    { language: { id: "lang1", code: "default" }, default: true, enabled: true } as TSurveyLanguage,
+    { language: { id: "lang2", code: "en" }, default: false, enabled: true } as TSurveyLanguage,
+  ],
+  showLanguageSwitch: false,
+  singleUse: { enabled: false, isEncrypted: false },
+  projectOverwrites: null,
+  surveyClosedMessage: null,
+  delay: 0,
+  isVerifyEmailEnabled: false,
+  createdBy: null,
+  variables: [],
+  followUps: [],
+  runOnDate: null,
+  closeOnDate: null,
+  styling: null,
+  pin: null,
+  recaptcha: null,
+  isSingleResponsePerEmailEnabled: false,
+  isBackButtonHidden: false,
+  metadata: {
+    title: { default: "Test Title", en: "Test Title EN" },
+    description: { default: "Test Description", en: "Test Description EN" },
+    ogImage: "https://example.com/image.png",
+  },
+};
+
+const mockSingleLanguageSurvey: TSurvey = {
+  ...mockSurvey,
+  languages: [
+    { language: { id: "lang1", code: "default" }, default: true, enabled: true },
+  ] as TSurveyLanguage[],
+};
+
+describe("LinkSettingsTab", () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+
+    vi.mocked(useSurvey).mockReturnValue({
+      survey: mockSurvey,
+    });
+
+    vi.mocked(getEnabledLanguages).mockReturnValue([
+      { language: { id: "lang1", code: "default" }, default: true, enabled: true } as TSurveyLanguage,
+      { language: { id: "lang2", code: "en" }, default: false, enabled: true } as TSurveyLanguage,
+    ]);
+
+    vi.mocked(extractLanguageCodes).mockReturnValue(["default", "en"]);
+
+    vi.mocked(createI18nString).mockImplementation((text, languages) => {
+      const result = {};
+      languages.forEach((lang) => {
+        result[lang] = typeof text === "string" ? text : "";
+      });
+      return result;
+    });
+
+    vi.mocked(getLanguageLabel).mockImplementation((code) => {
+      const labels = {
+        default: "Default",
+        en: "English",
+      };
+      return labels[code] || code;
+    });
+
+    vi.mocked(updateSurveyAction).mockResolvedValue({ data: mockSurvey });
+  });
+
+  afterEach(() => {
+    cleanup();
+  });
+
+  test("renders form fields correctly", () => {
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    expect(screen.getByText("common.language")).toBeInTheDocument();
+    expect(screen.getByText("environments.surveys.share.link_settings.link_title")).toBeInTheDocument();
+    expect(screen.getByText("environments.surveys.share.link_settings.link_description")).toBeInTheDocument();
+    expect(screen.getByText("environments.surveys.share.link_settings.preview_image")).toBeInTheDocument();
+    expect(screen.getByTestId("save-button")).toBeInTheDocument();
+  });
+
+  test("initializes form with existing metadata", () => {
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const titleInput = screen.getByDisplayValue("Test Title");
+    const descriptionInput = screen.getByDisplayValue("Test Description");
+
+    expect(titleInput).toBeInTheDocument();
+    expect(descriptionInput).toBeInTheDocument();
+  });
+
+  test("initializes form with empty values when metadata is undefined", () => {
+    const mockSurveyWithoutMetadata: TSurvey = {
+      ...mockSurvey,
+      metadata: {},
+    };
+
+    vi.mocked(useSurvey).mockReturnValue({
+      survey: mockSurveyWithoutMetadata,
+    });
+
+    vi.mocked(createI18nString).mockReturnValue({ default: "", en: "" });
+
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    expect(vi.mocked(createI18nString)).toHaveBeenCalledWith("", ["default", "en"]);
+  });
+
+  test("does not show language selector for single language surveys", () => {
+    vi.mocked(useSurvey).mockReturnValue({
+      survey: mockSingleLanguageSurvey,
+    });
+
+    vi.mocked(getEnabledLanguages).mockReturnValue([
+      { language: { id: "lang1", code: "default" }, default: true, enabled: true } as TSurveyLanguage,
+    ]);
+
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    expect(screen.queryByText("common.language")).not.toBeInTheDocument();
+  });
+
+  test("shows language selector for multi-language surveys", () => {
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    expect(screen.getByText("common.language")).toBeInTheDocument();
+  });
+
+  test("handles language change correctly", async () => {
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    // Since the Select component is complex to test in JSDOM, let's test that
+    // the language selector is rendered and has the expected options
+    const languageSelect = screen.getByRole("combobox");
+    expect(languageSelect).toBeInTheDocument();
+
+    // Check that the language options are available in the hidden select
+    const hiddenSelect = screen.getByDisplayValue("Default");
+    expect(hiddenSelect).toBeInTheDocument();
+
+    // Check for English option in the select
+    const englishOption = screen
+      .getByDisplayValue("Default")
+      .closest("select")
+      ?.querySelector('option[value="en"]');
+    expect(englishOption).toBeInTheDocument();
+  });
+
+  test("handles title input change", async () => {
+    const user = userEvent.setup();
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const titleInput = screen.getByDisplayValue("Test Title");
+    await user.clear(titleInput);
+    await user.type(titleInput, "New Title");
+
+    expect(titleInput).toHaveValue("New Title");
+  });
+
+  test("handles description input change", async () => {
+    const user = userEvent.setup();
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const descriptionInput = screen.getByDisplayValue("Test Description");
+    await user.clear(descriptionInput);
+    await user.type(descriptionInput, "New Description");
+
+    expect(descriptionInput).toHaveValue("New Description");
+  });
+
+  test("handles file upload", async () => {
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const fileInput = screen.getByTestId("file-input");
+    fireEvent.change(fileInput, { target: { value: "https://example.com/new-image.png" } });
+
+    expect(fileInput).toHaveValue("https://example.com/new-image.png");
+  });
+
+  test("handles file removal", async () => {
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const fileInput = screen.getByTestId("file-input");
+    fireEvent.change(fileInput, { target: { value: "" } });
+
+    expect(fileInput).toHaveValue("");
+  });
+
+  test("disables form when isReadOnly is true", () => {
+    render(<LinkSettingsTab isReadOnly={true} locale="en-US" />);
+
+    const titleInput = screen.getByDisplayValue("Test Title");
+    const descriptionInput = screen.getByDisplayValue("Test Description");
+    const saveButton = screen.getByTestId("save-button");
+
+    expect(titleInput).toBeDisabled();
+    expect(descriptionInput).toBeDisabled();
+    expect(saveButton).toBeDisabled();
+  });
+
+  test("submits form successfully", async () => {
+    const user = userEvent.setup();
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const titleInput = screen.getByDisplayValue("Test Title");
+    await user.clear(titleInput);
+    await user.type(titleInput, "Updated Title");
+
+    const saveButton = screen.getByTestId("save-button");
+    await user.click(saveButton);
+
+    await waitFor(() => {
+      expect(vi.mocked(updateSurveyAction)).toHaveBeenCalled();
+    });
+
+    const callArgs = vi.mocked(updateSurveyAction).mock.calls[0][0];
+    expect(callArgs.metadata.title?.default).toBe("Updated Title");
+  });
+
+  test("handles submission error", async () => {
+    const user = userEvent.setup();
+    vi.mocked(updateSurveyAction).mockResolvedValue({ data: mockSurvey });
+
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const titleInput = screen.getByDisplayValue("Test Title");
+    await user.clear(titleInput);
+    await user.type(titleInput, "Updated Title");
+
+    const saveButton = screen.getByTestId("save-button");
+    await user.click(saveButton);
+
+    await waitFor(() => {
+      expect(vi.mocked(updateSurveyAction)).toHaveBeenCalled();
+    });
+  });
+
+  test("does not submit when form is saving", async () => {
+    const user = userEvent.setup();
+    let resolvePromise: (value: any) => void;
+    const pendingPromise = new Promise((resolve) => {
+      resolvePromise = resolve;
+    });
+    vi.mocked(updateSurveyAction).mockReturnValue(pendingPromise as any);
+
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    // Make form dirty first
+    const titleInput = screen.getByDisplayValue("Test Title");
+    await user.clear(titleInput);
+    await user.type(titleInput, "Modified Title");
+
+    const saveButton = screen.getByTestId("save-button");
+
+    // First click should trigger submission
+    await user.click(saveButton);
+
+    // Wait a bit to ensure the first submission started
+    await new Promise((resolve) => setTimeout(resolve, 10));
+
+    // Second click should be prevented because form is saving
+    await user.click(saveButton);
+
+    // Should only be called once
+    expect(vi.mocked(updateSurveyAction)).toHaveBeenCalledTimes(1);
+
+    // Clean up by resolving the promise
+    resolvePromise!({ data: mockSurvey });
+  });
+
+  test("does not submit when isReadOnly is true", async () => {
+    const user = userEvent.setup();
+    render(<LinkSettingsTab isReadOnly={true} locale="en-US" />);
+
+    const saveButton = screen.getByTestId("save-button");
+    await user.click(saveButton);
+
+    expect(vi.mocked(updateSurveyAction)).not.toHaveBeenCalled();
+  });
+
+  test("handles ogImage correctly in form submission", async () => {
+    const user = userEvent.setup();
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const fileInput = screen.getByTestId("file-input");
+    fireEvent.change(fileInput, { target: { value: "https://example.com/new-image.png" } });
+
+    const saveButton = screen.getByTestId("save-button");
+    await user.click(saveButton);
+
+    await waitFor(() => {
+      expect(vi.mocked(updateSurveyAction)).toHaveBeenCalled();
+    });
+
+    const callArgs = vi.mocked(updateSurveyAction).mock.calls[0][0];
+    expect(callArgs.metadata.ogImage).toBe("https://example.com/new-image.png");
+  });
+
+  test("handles empty ogImage correctly in form submission", async () => {
+    const user = userEvent.setup();
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const fileInput = screen.getByTestId("file-input");
+    fireEvent.change(fileInput, { target: { value: "" } });
+
+    const saveButton = screen.getByTestId("save-button");
+    await user.click(saveButton);
+
+    await waitFor(() => {
+      expect(vi.mocked(updateSurveyAction)).toHaveBeenCalled();
+    });
+
+    const callArgs = vi.mocked(updateSurveyAction).mock.calls[0][0];
+    expect(callArgs.metadata.ogImage).toBeUndefined();
+  });
+
+  test("merges form data with existing metadata correctly", async () => {
+    const user = userEvent.setup();
+    const surveyWithPartialMetadata: TSurvey = {
+      ...mockSurvey,
+      metadata: {
+        title: { default: "Existing Title" },
+        description: { default: "Existing Description" },
+      },
+    };
+
+    vi.mocked(useSurvey).mockReturnValue({
+      survey: surveyWithPartialMetadata,
+    });
+
+    render(<LinkSettingsTab isReadOnly={false} locale="en-US" />);
+
+    const titleInput = screen.getByDisplayValue("Existing Title");
+    await user.clear(titleInput);
+    await user.type(titleInput, "New Title");
+
+    const saveButton = screen.getByTestId("save-button");
+    await user.click(saveButton);
+
+    await waitFor(() => {
+      expect(vi.mocked(updateSurveyAction)).toHaveBeenCalled();
+    });
+
+    const callArgs = vi.mocked(updateSurveyAction).mock.calls[0][0];
+    expect(callArgs.metadata.title?.default).toBe("New Title");
+    expect(callArgs.metadata.description?.default).toBe("Existing Description");
+  });
+});
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.test.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.test.tsx
index 49d6594764b9..8521b00ebdd3 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.test.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.test.tsx
@@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event";
 import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
 import { TSurvey } from "@formbricks/types/surveys/types";
 import { TUserLocale } from "@formbricks/types/user";
-import { ShareViewType } from "../../types/share";
+import { LinkTabsType, ShareSettingsType, ShareViaType } from "../../types/share";
 import { ShareView } from "./share-view";
 
 // Mock sidebar components
@@ -161,6 +161,7 @@ vi.mock("lucide-react", () => ({
   Share2Icon: () => <div data-testid="share2-icon">Share2Icon</div>,
   SquareStack: () => <div data-testid="square-stack-icon">SquareStack</div>,
   UserIcon: () => <div data-testid="user-icon">UserIcon</div>,
+  Settings: () => <div data-testid="settings-icon">Settings</div>,
 }));
 
 // Mock tooltip and typography components
@@ -264,9 +265,25 @@ const mockSurvey = {
   styling: null,
 } as any;
 
+// Mock LinkSettingsTab
+const MockLinkSettingsTab = ({
+  survey,
+  isReadOnly,
+  locale,
+}: {
+  survey: any;
+  isReadOnly: boolean;
+  locale: string;
+}) => (
+  <div data-testid="link-settings-tab">
+    LinkSettingsTab Content for {survey.id} - ReadOnly: {isReadOnly.toString()} - Locale: {locale}
+  </div>
+);
+
 const mockTabs = [
   {
-    id: ShareViewType.EMAIL,
+    id: ShareViaType.EMAIL,
+    type: LinkTabsType.SHARE_VIA,
     label: "Email",
     icon: () => <div data-testid="email-tab-icon" />,
     componentType: MockEmailTab,
@@ -275,7 +292,8 @@ const mockTabs = [
     description: "Send survey via email",
   },
   {
-    id: ShareViewType.WEBSITE_EMBED,
+    id: ShareViaType.WEBSITE_EMBED,
+    type: LinkTabsType.SHARE_VIA,
     label: "Website Embed",
     icon: () => <div data-testid="website-embed-tab-icon" />,
     componentType: MockWebsiteEmbedTab,
@@ -284,7 +302,8 @@ const mockTabs = [
     description: "Embed survey on your website",
   },
   {
-    id: ShareViewType.DYNAMIC_POPUP,
+    id: ShareViaType.DYNAMIC_POPUP,
+    type: LinkTabsType.SHARE_VIA,
     label: "Dynamic Popup",
     icon: () => <div data-testid="dynamic-popup-tab-icon" />,
     componentType: MockDynamicPopupTab,
@@ -293,7 +312,8 @@ const mockTabs = [
     description: "Show survey as popup",
   },
   {
-    id: ShareViewType.ANON_LINKS,
+    id: ShareViaType.ANON_LINKS,
+    type: LinkTabsType.SHARE_VIA,
     label: "Anonymous Links",
     icon: () => <div data-testid="anonymous-links-tab-icon" />,
     componentType: MockAnonymousLinksTab,
@@ -308,7 +328,8 @@ const mockTabs = [
     description: "Share anonymous links",
   },
   {
-    id: ShareViewType.QR_CODE,
+    id: ShareViaType.QR_CODE,
+    type: LinkTabsType.SHARE_VIA,
     label: "QR Code",
     icon: () => <div data-testid="qr-code-tab-icon" />,
     componentType: MockQRCodeTab,
@@ -317,7 +338,8 @@ const mockTabs = [
     description: "Generate QR code",
   },
   {
-    id: ShareViewType.PERSONAL_LINKS,
+    id: ShareViaType.PERSONAL_LINKS,
+    type: LinkTabsType.SHARE_VIA,
     label: "Personal Links",
     icon: () => <div data-testid="personal-links-tab-icon" />,
     componentType: MockPersonalLinksTab,
@@ -326,7 +348,8 @@ const mockTabs = [
     description: "Create personal links",
   },
   {
-    id: ShareViewType.SOCIAL_MEDIA,
+    id: ShareViaType.SOCIAL_MEDIA,
+    type: LinkTabsType.SHARE_VIA,
     label: "Social Media",
     icon: () => <div data-testid="social-media-tab-icon" />,
     componentType: MockSocialMediaTab,
@@ -334,11 +357,21 @@ const mockTabs = [
     title: "Social Media",
     description: "Share on social media",
   },
+  {
+    id: ShareSettingsType.LINK_SETTINGS,
+    type: LinkTabsType.SHARE_SETTING,
+    label: "Link Settings",
+    icon: () => <div data-testid="link-settings-tab-icon" />,
+    componentType: MockLinkSettingsTab,
+    componentProps: { survey: mockSurvey, isReadOnly: false, locale: "en-US" },
+    title: "Link Settings",
+    description: "Configure link settings",
+  },
 ];
 
 const defaultProps = {
   tabs: mockTabs,
-  activeId: ShareViewType.EMAIL,
+  activeId: ShareViaType.EMAIL,
   setActiveId: vi.fn(),
 };
 
@@ -370,43 +403,51 @@ describe("ShareView", () => {
   });
 
   test("renders desktop tabs", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.EMAIL} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.EMAIL} />);
 
     // Desktop sidebar should be rendered
     const sidebarLabel = screen.getByText("environments.surveys.share.share_view_title");
     expect(sidebarLabel).toBeInTheDocument();
   });
 
+  test("renders share settings section", () => {
+    render(<ShareView {...defaultProps} />);
+
+    // Share settings section should be rendered
+    const shareSettingsLabel = screen.getByText("environments.surveys.share.share_settings_title");
+    expect(shareSettingsLabel).toBeInTheDocument();
+  });
+
   test("calls setActiveId when a tab is clicked (desktop)", async () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.EMAIL} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.EMAIL} />);
 
     const websiteEmbedTabButton = screen.getByLabelText("Website Embed");
     await userEvent.click(websiteEmbedTabButton);
-    expect(defaultProps.setActiveId).toHaveBeenCalledWith(ShareViewType.WEBSITE_EMBED);
+    expect(defaultProps.setActiveId).toHaveBeenCalledWith(ShareViaType.WEBSITE_EMBED);
   });
 
   test("renders EmailTab when activeId is EMAIL", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.EMAIL} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.EMAIL} />);
     expect(screen.getByTestId("email-tab")).toBeInTheDocument();
     expect(screen.getByText("EmailTab Content for survey1 with test@example.com")).toBeInTheDocument();
   });
 
   test("renders WebsiteEmbedTab when activeId is WEBSITE_EMBED", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.WEBSITE_EMBED} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.WEBSITE_EMBED} />);
     expect(screen.getByTestId("tab-container")).toBeInTheDocument();
     expect(screen.getByTestId("website-embed-tab")).toBeInTheDocument();
     expect(screen.getByText("WebsiteEmbedTab Content for http://example.com/survey1")).toBeInTheDocument();
   });
 
   test("renders DynamicPopupTab when activeId is DYNAMIC_POPUP", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.DYNAMIC_POPUP} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.DYNAMIC_POPUP} />);
     expect(screen.getByTestId("tab-container")).toBeInTheDocument();
     expect(screen.getByTestId("dynamic-popup-tab")).toBeInTheDocument();
     expect(screen.getByText("DynamicPopupTab Content for survey1 in env1")).toBeInTheDocument();
   });
 
   test("renders AnonymousLinksTab when activeId is ANON_LINKS", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.ANON_LINKS} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.ANON_LINKS} />);
     expect(screen.getByTestId("anonymous-links-tab")).toBeInTheDocument();
     expect(
       screen.getByText("AnonymousLinksTab Content for survey1 at http://example.com/survey1")
@@ -414,16 +455,24 @@ describe("ShareView", () => {
   });
 
   test("renders QRCodeTab when activeId is QR_CODE", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.QR_CODE} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.QR_CODE} />);
     expect(screen.getByTestId("qr-code-tab")).toBeInTheDocument();
   });
 
+  test("renders LinkSettingsTab when activeId is LINK_SETTINGS", () => {
+    render(<ShareView {...defaultProps} activeId={ShareSettingsType.LINK_SETTINGS} />);
+    expect(screen.getByTestId("link-settings-tab")).toBeInTheDocument();
+    expect(
+      screen.getByText("LinkSettingsTab Content for survey1 - ReadOnly: false - Locale: en-US")
+    ).toBeInTheDocument();
+  });
+
   test("renders nothing when activeId doesn't match any tab", () => {
     // Create a special case with no matching tab
     const propsWithNoMatchingTab = {
       ...defaultProps,
       tabs: mockTabs.slice(0, 3), // Only include first 3 tabs
-      activeId: ShareViewType.SOCIAL_MEDIA, // Use a tab not in the subset
+      activeId: ShareViaType.SOCIAL_MEDIA, // Use a tab not in the subset
     };
 
     render(<ShareView {...propsWithNoMatchingTab} />);
@@ -436,16 +485,17 @@ describe("ShareView", () => {
     expect(screen.queryByTestId("qr-code-tab")).not.toBeInTheDocument();
     expect(screen.queryByTestId("personal-links-tab")).not.toBeInTheDocument();
     expect(screen.queryByTestId("social-media-tab")).not.toBeInTheDocument();
+    expect(screen.queryByTestId("link-settings-tab")).not.toBeInTheDocument();
   });
 
   test("renders PersonalLinksTab when activeId is PERSONAL_LINKS", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.PERSONAL_LINKS} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.PERSONAL_LINKS} />);
     expect(screen.getByTestId("personal-links-tab")).toBeInTheDocument();
     expect(screen.getByText("PersonalLinksTab Content for survey1 in env1")).toBeInTheDocument();
   });
 
   test("renders SocialMediaTab when activeId is SOCIAL_MEDIA", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.SOCIAL_MEDIA} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.SOCIAL_MEDIA} />);
     expect(screen.getByTestId("social-media-tab")).toBeInTheDocument();
     expect(
       screen.getByText("SocialMediaTab Content for Test Survey at http://example.com/survey1")
@@ -453,7 +503,7 @@ describe("ShareView", () => {
   });
 
   test("calls setActiveId when a responsive tab is clicked", async () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.EMAIL} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.EMAIL} />);
 
     // Get responsive buttons - these are Button components containing icons
     const responsiveButtons = screen.getAllByTestId("website-embed-tab-icon");
@@ -467,12 +517,12 @@ describe("ShareView", () => {
 
     if (responsiveButton) {
       await userEvent.click(responsiveButton);
-      expect(defaultProps.setActiveId).toHaveBeenCalledWith(ShareViewType.WEBSITE_EMBED);
+      expect(defaultProps.setActiveId).toHaveBeenCalledWith(ShareViaType.WEBSITE_EMBED);
     }
   });
 
   test("applies active styles to the active tab (desktop)", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.EMAIL} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.EMAIL} />);
 
     const emailTabButton = screen.getByLabelText("Email");
     expect(emailTabButton).toHaveClass("bg-slate-100");
@@ -485,7 +535,7 @@ describe("ShareView", () => {
   });
 
   test("applies active styles to the active tab (responsive)", () => {
-    render(<ShareView {...defaultProps} activeId={ShareViewType.EMAIL} />);
+    render(<ShareView {...defaultProps} activeId={ShareViaType.EMAIL} />);
 
     // Get responsive buttons - these are Button components with ghost variant
     const responsiveButtons = screen.getAllByTestId("email-tab-icon");
@@ -537,6 +587,7 @@ describe("ShareView", () => {
       "qr-code-tab-icon",
       "personal-links-tab-icon",
       "social-media-tab-icon",
+      "link-settings-tab-icon",
     ];
 
     expectedTestIds.forEach((testId) => {
@@ -548,4 +599,28 @@ describe("ShareView", () => {
       expect(responsiveButton).toBeTruthy();
     });
   });
+
+  test("separates share via and share settings tabs correctly", () => {
+    render(<ShareView {...defaultProps} />);
+
+    // Check that share via tabs are rendered
+    expect(screen.getByLabelText("Email")).toBeInTheDocument();
+    expect(screen.getByLabelText("Website Embed")).toBeInTheDocument();
+    expect(screen.getByLabelText("Anonymous Links")).toBeInTheDocument();
+
+    // Check that share settings tabs are rendered
+    expect(screen.getByLabelText("Link Settings")).toBeInTheDocument();
+
+    // Check that both sections have their titles
+    expect(screen.getByText("environments.surveys.share.share_view_title")).toBeInTheDocument();
+    expect(screen.getByText("environments.surveys.share.share_settings_title")).toBeInTheDocument();
+  });
+
+  test("calls setActiveId when a share settings tab is clicked", async () => {
+    render(<ShareView {...defaultProps} activeId={ShareViaType.EMAIL} />);
+
+    const linkSettingsTabButton = screen.getByLabelText("Link Settings");
+    await userEvent.click(linkSettingsTabButton);
+    expect(defaultProps.setActiveId).toHaveBeenCalledWith(ShareSettingsType.LINK_SETTINGS);
+  });
 });
diff --git a/apps/web/modules/survey/link/lib/metadata-utils.test.ts b/apps/web/modules/survey/link/lib/metadata-utils.test.ts
index 28eb8149f16d..9c898ae945e1 100644
--- a/apps/web/modules/survey/link/lib/metadata-utils.test.ts
+++ b/apps/web/modules/survey/link/lib/metadata-utils.test.ts
@@ -1,4 +1,3 @@
-import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
 import { getPublicDomain } from "@/lib/getPublicUrl";
 import { COLOR_DEFAULTS } from "@/lib/styling/constants";
 import { getSurvey } from "@/modules/survey/lib/survey";
@@ -23,7 +22,7 @@ vi.mock("@/modules/survey/link/lib/project", () => ({
 
 // Mock constants
 vi.mock("@/lib/constants", () => ({
-  IS_FORMBRICKS_CLOUD: vi.fn(() => false),
+  IS_FORMBRICKS_CLOUD: false,
   WEBAPP_URL: "https://test.formbricks.com",
 }));
 
@@ -80,6 +79,7 @@ describe("Metadata Utils", () => {
         title: "Survey",
         description: "Complete this survey",
         survey: null,
+        ogImage: undefined,
       });
     });
 
@@ -88,6 +88,7 @@ describe("Metadata Utils", () => {
         id: mockSurveyId,
         environmentId: mockEnvironmentId,
         name: "Test Survey",
+        metadata: {},
         welcomeCard: {
           enabled: true,
           timeToFinish: false,
@@ -109,9 +110,10 @@ describe("Metadata Utils", () => {
       expect(getSurvey).toHaveBeenCalledWith(mockSurveyId);
       expect(getProjectByEnvironmentId).toHaveBeenCalledWith(mockEnvironmentId);
       expect(result).toEqual({
-        title: "Welcome Headline | Formbricks",
-        description: "Welcome Description",
+        title: "Welcome Headline | Test Project",
+        description: "Complete this survey",
         survey: mockSurvey,
+        ogImage: undefined,
       });
     });
 
@@ -120,6 +122,7 @@ describe("Metadata Utils", () => {
         id: mockSurveyId,
         environmentId: mockEnvironmentId,
         name: "Test Survey",
+        metadata: {},
         welcomeCard: {
           enabled: false,
         } as TSurveyWelcomeCard,
@@ -131,20 +134,30 @@ describe("Metadata Utils", () => {
       const result = await getBasicSurveyMetadata(mockSurveyId);
 
       expect(result).toEqual({
-        title: "Test Survey | Formbricks",
+        title: "Test Survey | Test Project",
         description: "Complete this survey",
         survey: mockSurvey,
+        ogImage: undefined,
       });
     });
 
     test("adds Formbricks to title when IS_FORMBRICKS_CLOUD is true", async () => {
-      // Change the mock for this specific test
-      (IS_FORMBRICKS_CLOUD as unknown as ReturnType<typeof vi.fn>).mockReturnValue(true);
+      // Temporarily modify the mocked module
+      vi.doMock("@/lib/constants", () => ({
+        IS_FORMBRICKS_CLOUD: true,
+        WEBAPP_URL: "https://test.formbricks.com",
+      }));
+
+      // Re-import the function to use the updated mock
+      const { getBasicSurveyMetadata: getBasicSurveyMetadataWithCloudMock } = await import(
+        "./metadata-utils"
+      );
 
       const mockSurvey = {
         id: mockSurveyId,
         environmentId: mockEnvironmentId,
         name: "Test Survey",
+        metadata: {},
         welcomeCard: {
           enabled: false,
         } as TSurveyWelcomeCard,
@@ -152,12 +165,15 @@ describe("Metadata Utils", () => {
 
       vi.mocked(getSurvey).mockResolvedValue(mockSurvey);
 
-      const result = await getBasicSurveyMetadata(mockSurveyId);
+      const result = await getBasicSurveyMetadataWithCloudMock(mockSurveyId);
 
       expect(result.title).toBe("Test Survey | Formbricks");
 
       // Reset the mock
-      (IS_FORMBRICKS_CLOUD as unknown as ReturnType<typeof vi.fn>).mockReturnValue(false);
+      vi.doMock("@/lib/constants", () => ({
+        IS_FORMBRICKS_CLOUD: false,
+        WEBAPP_URL: "https://test.formbricks.com",
+      }));
     });
   });
 
diff --git a/apps/web/modules/survey/link/lib/project.test.ts b/apps/web/modules/survey/link/lib/project.test.ts
index 966bab07361f..2220c70b4af2 100644
--- a/apps/web/modules/survey/link/lib/project.test.ts
+++ b/apps/web/modules/survey/link/lib/project.test.ts
@@ -44,6 +44,7 @@ describe("getProjectByEnvironmentId", () => {
       linkSurveyBranding: true,
       logo: null,
       styling: {},
+      name: "Test Project",
     };
 
     vi.mocked(prisma.project.findFirst).mockResolvedValueOnce(mockProject as any);
@@ -63,6 +64,7 @@ describe("getProjectByEnvironmentId", () => {
         linkSurveyBranding: true,
         logo: true,
         styling: true,
+        name: true,
       },
     });
   });
diff --git a/apps/web/modules/survey/link/metadata.test.ts b/apps/web/modules/survey/link/metadata.test.ts
index 4bbaf45beaf8..0ca428b19e43 100644
--- a/apps/web/modules/survey/link/metadata.test.ts
+++ b/apps/web/modules/survey/link/metadata.test.ts
@@ -1,8 +1,7 @@
-import { COLOR_DEFAULTS } from "@/lib/styling/constants";
 import { getSurveyMetadata } from "@/modules/survey/link/lib/data";
 import { notFound } from "next/navigation";
 import { beforeEach, describe, expect, test, vi } from "vitest";
-import { getBrandColorForURL, getNameForURL, getSurveyOpenGraphMetadata } from "./lib/metadata-utils";
+import { getBasicSurveyMetadata, getSurveyOpenGraphMetadata } from "./lib/metadata-utils";
 import { getMetadataForLinkSurvey } from "./metadata";
 
 vi.mock("@/modules/survey/link/lib/data", () => ({
@@ -14,32 +13,35 @@ vi.mock("next/navigation", () => ({
 }));
 
 vi.mock("./lib/metadata-utils", () => ({
-  getBrandColorForURL: vi.fn(),
-  getNameForURL: vi.fn(),
   getSurveyOpenGraphMetadata: vi.fn(),
+  getBasicSurveyMetadata: vi.fn(),
 }));
 
 describe("getMetadataForLinkSurvey", () => {
   const mockSurveyId = "survey-123";
   const mockSurveyName = "Test Survey";
-  const mockBrandColor = "#123456";
-  const mockEncodedBrandColor = "123456";
-  const mockEncodedName = "Test-Survey";
-  const mockOgImageUrl = `/api/v1/client/og?brandColor=${mockEncodedBrandColor}&name=${mockEncodedName}`;
+  const mockDescription = "Complete this survey";
+  const mockOgImageUrl = "https://example.com/custom-image.png";
 
   beforeEach(() => {
     vi.resetAllMocks();
 
-    vi.mocked(getBrandColorForURL).mockReturnValue(mockEncodedBrandColor);
-    vi.mocked(getNameForURL).mockReturnValue(mockEncodedName);
+    vi.mocked(getBasicSurveyMetadata).mockResolvedValue({
+      title: mockSurveyName,
+      description: mockDescription,
+      survey: null,
+      ogImage: undefined,
+    });
     vi.mocked(getSurveyOpenGraphMetadata).mockReturnValue({
       openGraph: {
         title: mockSurveyName,
-        images: [],
+        images: ["/api/v1/client/og?brandColor=%2364748b&name=Test%20Survey"],
+        description: "Thanks a lot for your time 🙏",
       },
       twitter: {
         title: mockSurveyName,
-        images: [],
+        images: ["/api/v1/client/og?brandColor=%2364748b&name=Test%20Survey"],
+        description: "Thanks a lot for your time 🙏",
       },
     });
   });
@@ -50,11 +52,6 @@ describe("getMetadataForLinkSurvey", () => {
       name: mockSurveyName,
       type: "link",
       status: "published",
-      styling: {
-        brandColor: {
-          light: mockBrandColor,
-        },
-      },
     } as any;
 
     vi.mocked(getSurveyMetadata).mockResolvedValue(mockSurvey);
@@ -62,18 +59,21 @@ describe("getMetadataForLinkSurvey", () => {
     const result = await getMetadataForLinkSurvey(mockSurveyId);
 
     expect(getSurveyMetadata).toHaveBeenCalledWith(mockSurveyId);
-    expect(getBrandColorForURL).toHaveBeenCalledWith(mockBrandColor);
-    expect(getNameForURL).toHaveBeenCalledWith(mockSurveyName);
+    expect(getBasicSurveyMetadata).toHaveBeenCalledWith(mockSurveyId, undefined);
+    expect(getSurveyOpenGraphMetadata).toHaveBeenCalledWith(mockSurveyId, mockSurveyName);
 
     expect(result).toEqual({
       title: mockSurveyName,
+      description: mockDescription,
       openGraph: {
         title: mockSurveyName,
-        images: [mockOgImageUrl],
+        description: mockDescription,
+        images: ["/api/v1/client/og?brandColor=%2364748b&name=Test%20Survey"],
       },
       twitter: {
         title: mockSurveyName,
-        images: [mockOgImageUrl],
+        description: mockDescription,
+        images: ["/api/v1/client/og?brandColor=%2364748b&name=Test%20Survey"],
       },
       alternates: {
         canonical: `/s/${mockSurveyId}`,
@@ -90,7 +90,7 @@ describe("getMetadataForLinkSurvey", () => {
     });
   });
 
-  test("uses default brand color when styling is not defined", async () => {
+  test("returns correct metadata with custom ogImage", async () => {
     const mockSurvey = {
       id: mockSurveyId,
       name: mockSurveyName,
@@ -99,10 +99,17 @@ describe("getMetadataForLinkSurvey", () => {
     } as any;
 
     vi.mocked(getSurveyMetadata).mockResolvedValue(mockSurvey);
+    vi.mocked(getBasicSurveyMetadata).mockResolvedValue({
+      title: mockSurveyName,
+      description: mockDescription,
+      survey: null,
+      ogImage: mockOgImageUrl as any,
+    });
 
-    await getMetadataForLinkSurvey(mockSurveyId);
+    const result = await getMetadataForLinkSurvey(mockSurveyId);
 
-    expect(getBrandColorForURL).toHaveBeenCalledWith(COLOR_DEFAULTS.brandColor);
+    expect(result.openGraph?.images).toEqual(mockOgImageUrl);
+    expect(result.twitter?.images).toEqual(mockOgImageUrl);
   });
 
   test("calls notFound when survey type is not link", async () => {
@@ -147,7 +154,8 @@ describe("getMetadataForLinkSurvey", () => {
     vi.mocked(getSurveyOpenGraphMetadata).mockReturnValue({
       twitter: {
         title: mockSurveyName,
-        images: [],
+        images: ["/api/v1/client/og?brandColor=%2364748b&name=Test%20Survey"],
+        description: "Thanks a lot for your time 🙏",
       },
     });
 
@@ -155,9 +163,11 @@ describe("getMetadataForLinkSurvey", () => {
 
     expect(result).toEqual({
       title: mockSurveyName,
+      description: mockDescription,
       twitter: {
         title: mockSurveyName,
-        images: [mockOgImageUrl],
+        description: mockDescription,
+        images: ["/api/v1/client/og?brandColor=%2364748b&name=Test%20Survey"],
       },
       alternates: {
         canonical: `/s/${mockSurveyId}`,
@@ -186,7 +196,8 @@ describe("getMetadataForLinkSurvey", () => {
     vi.mocked(getSurveyOpenGraphMetadata).mockReturnValue({
       openGraph: {
         title: mockSurveyName,
-        images: [],
+        images: ["/api/v1/client/og?brandColor=%2364748b&name=Test%20Survey"],
+        description: "Thanks a lot for your time 🙏",
       },
     });
 
@@ -194,9 +205,11 @@ describe("getMetadataForLinkSurvey", () => {
 
     expect(result).toEqual({
       title: mockSurveyName,
+      description: mockDescription,
       openGraph: {
         title: mockSurveyName,
-        images: [mockOgImageUrl],
+        description: mockDescription,
+        images: ["/api/v1/client/og?brandColor=%2364748b&name=Test%20Survey"],
       },
       alternates: {
         canonical: `/s/${mockSurveyId}`,
diff --git a/apps/web/modules/survey/link/page.test.tsx b/apps/web/modules/survey/link/page.test.tsx
index 88c6f78e7eb8..f53379c6cf87 100644
--- a/apps/web/modules/survey/link/page.test.tsx
+++ b/apps/web/modules/survey/link/page.test.tsx
@@ -88,7 +88,7 @@ describe("LinkSurveyPage", () => {
 
     const result = await generateMetadata(props);
 
-    expect(getMetadataForLinkSurvey).toHaveBeenCalledWith("survey123");
+    expect(getMetadataForLinkSurvey).toHaveBeenCalledWith("survey123", undefined);
     expect(result).toEqual(mockMetadata);
   });
 
@@ -415,7 +415,7 @@ describe("generateMetadata", () => {
       searchParams: Promise.resolve({}),
     };
     const metadata = await generateMetadata(props);
-    expect(getMetadataForLinkSurvey).toHaveBeenCalledWith("survey-123");
+    expect(getMetadataForLinkSurvey).toHaveBeenCalledWith("survey-123", undefined);
     expect(metadata).toEqual({ title: "Test Survey" });
   });
 });