diff --git a/e2e/test/scenarios/collections/permissions.cy.spec.js b/e2e/test/scenarios/collections/permissions.cy.spec.js
index 761c4b28dea7e42898986ce752535da3745f5e1f..2d41e8d4aa2ccfdc0075e1ba83ed21c7762dd939 100644
--- a/e2e/test/scenarios/collections/permissions.cy.spec.js
+++ b/e2e/test/scenarios/collections/permissions.cy.spec.js
@@ -326,7 +326,8 @@ describe("collection permissions", () => {
               cy.visit("/collection/root");
               openCollectionItemMenu("Orders in a dashboard");
               popover().findByText("Duplicate").click();
-              cy.findByTestId("select-button").findByText(
+              cy.findByTestId("collection-picker-button").should(
+                "have.text",
                 `${first_name} ${last_name}'s Personal Collection`,
               );
             });
diff --git a/e2e/test/scenarios/dashboard/dashboard-management.cy.spec.js b/e2e/test/scenarios/dashboard/dashboard-management.cy.spec.js
index 2e0ceeb185479533e9f27917eb80a6e5e1327e27..0c3587b889208a469fe0209fbda07db714519637 100644
--- a/e2e/test/scenarios/dashboard/dashboard-management.cy.spec.js
+++ b/e2e/test/scenarios/dashboard/dashboard-management.cy.spec.js
@@ -16,6 +16,7 @@ import {
   openDashboardMenu,
   toggleDashboardInfoSidebar,
   entityPickerModal,
+  collectionOnTheGoModal,
 } from "e2e/support/helpers";
 
 const PERMISSIONS = {
@@ -188,18 +189,21 @@ describe("managing dashboard from the dashboard's edit menu", () => {
                   cy.findByLabelText("Only duplicate the dashboard").should(
                     "not.be.checked",
                   );
-                  cy.findByTestId("select-button").click();
+                  cy.findByTestId("collection-picker-button").click();
                 });
-                popover().findByText("New collection").click();
+                entityPickerModal()
+                  .findByText("Create a new collection")
+                  .click();
                 const NEW_COLLECTION = "Foo Collection";
-                cy.findByTestId("new-collection-modal").then(modal => {
-                  cy.findByPlaceholderText("My new fantastic collection").type(
+                collectionOnTheGoModal().within(() => {
+                  cy.findByPlaceholderText("My new collection").type(
                     NEW_COLLECTION,
                   );
                   cy.button("Create").click();
-                  cy.button("Duplicate").click();
-                  assertOnRequest("copyDashboard");
                 });
+                cy.button("Select").click();
+                cy.button("Duplicate").click();
+                assertOnRequest("copyDashboard");
 
                 cy.url().should("contain", `/dashboard/${newDashboardId}`);
 
@@ -304,7 +308,8 @@ describe("managing dashboard from the dashboard's edit menu", () => {
             const { first_name, last_name } = USERS[user];
 
             popover().findByText("Duplicate").click();
-            cy.findByTestId("select-button").findByText(
+            cy.findByTestId("collection-picker-button").should(
+              "have.text",
               `${first_name} ${last_name}'s Personal Collection`,
             );
           });
diff --git a/e2e/test/scenarios/question/saved.cy.spec.js b/e2e/test/scenarios/question/saved.cy.spec.js
index 315ee0366634ddd3fd0f5a22519063372afa7c12..40b90d911a82896dedecba98fbfe0bf94e9e31e2 100644
--- a/e2e/test/scenarios/question/saved.cy.spec.js
+++ b/e2e/test/scenarios/question/saved.cy.spec.js
@@ -17,6 +17,8 @@ import {
   queryBuilderHeader,
   openNotebook,
   selectFilterOperator,
+  entityPickerModal,
+  collectionOnTheGoModal,
 } from "e2e/support/helpers";
 
 describe("scenarios > question > saved", () => {
@@ -149,18 +151,25 @@ describe("scenarios > question > saved", () => {
 
     modal().within(() => {
       cy.findByLabelText("Name").should("have.value", "Orders - Duplicate");
-      cy.findByTestId("select-button").click();
+      cy.findByTestId("collection-picker-button").click();
     });
-    popover().findByText("New collection").click();
+
+    entityPickerModal().findByText("Create a new collection").click();
 
     const NEW_COLLECTION = "Foo";
-    cy.findByTestId("new-collection-modal").then(modal => {
-      cy.findByPlaceholderText("My new fantastic collection").type(
-        NEW_COLLECTION,
-      );
+    collectionOnTheGoModal().then(() => {
+      cy.findByPlaceholderText("My new collection").type(NEW_COLLECTION);
       cy.findByText("Create").click();
+    });
+
+    entityPickerModal().findByText("Select").click();
+
+    modal().within(() => {
       cy.findByLabelText("Name").should("have.value", "Orders - Duplicate");
-      cy.findByTestId("select-button").should("have.text", NEW_COLLECTION);
+      cy.findByTestId("collection-picker-button").should(
+        "have.text",
+        NEW_COLLECTION,
+      );
       cy.findByText("Duplicate").click();
       cy.wait("@cardCreate");
     });
diff --git a/frontend/src/metabase/dashboard/components/DashboardCopyModal.jsx b/frontend/src/metabase/dashboard/components/DashboardCopyModal.jsx
index 9e4444861f52dfe7298fb896e4bea973b3d54fa3..364296560b791ae43ba947b2563427f0c4e0678c 100644
--- a/frontend/src/metabase/dashboard/components/DashboardCopyModal.jsx
+++ b/frontend/src/metabase/dashboard/components/DashboardCopyModal.jsx
@@ -68,8 +68,8 @@ const DashboardCopyModal = ({
       form={Dashboards.forms.duplicate}
       title={title}
       overwriteOnInitialValuesChange
-      copy={object =>
-        copyDashboard({ id: initialDashboardId }, dissoc(object, "id"))
+      copy={async object =>
+        await copyDashboard({ id: initialDashboardId }, dissoc(object, "id"))
       }
       onClose={onClose}
       onSaved={dashboard => onReplaceLocation(Urls.dashboard(dashboard))}
diff --git a/frontend/src/metabase/dashboard/containers/CopyDashboardForm.tsx b/frontend/src/metabase/dashboard/containers/CopyDashboardForm.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3f9a02a1c67836e95ea7f73fbeba4467417169d8
--- /dev/null
+++ b/frontend/src/metabase/dashboard/containers/CopyDashboardForm.tsx
@@ -0,0 +1,130 @@
+import { useCallback, useMemo } from "react";
+import { withRouter } from "react-router";
+import { t } from "ttag";
+import _ from "underscore";
+import * as Yup from "yup";
+
+import FormCollectionPicker from "metabase/collections/containers/FormCollectionPicker/FormCollectionPicker";
+import type { FilterItemsInPersonalCollection } from "metabase/containers/ItemPicker";
+import Button from "metabase/core/components/Button";
+import FormFooter from "metabase/core/components/FormFooter";
+import Dashboards from "metabase/entities/dashboards";
+import {
+  FormTextInput,
+  FormTextarea,
+  FormSubmitButton,
+  FormErrorMessage,
+  FormCheckbox,
+  Form,
+  FormProvider,
+  FormObserver,
+} from "metabase/forms";
+import * as Errors from "metabase/lib/errors";
+import type { CollectionId, Dashboard } from "metabase-types/api";
+
+import { DashboardCopyModalShallowCheckboxLabel } from "../components/DashboardCopyModal/DashboardCopyModalShallowCheckboxLabel/DashboardCopyModalShallowCheckboxLabel";
+
+const DASHBOARD_SCHEMA = Yup.object({
+  name: Yup.string()
+    .required(Errors.required)
+    .max(100, Errors.maxLength)
+    .default(""),
+  description: Yup.string().nullable().max(255, Errors.maxLength).default(null),
+  collection_id: Yup.number().nullable().default(null),
+  is_shallow_copy: Yup.boolean().default(false),
+});
+
+export interface CopyDashboardFormProperties {
+  name: string;
+  description: string | null;
+  collection_id: CollectionId | null;
+}
+
+export interface CopyDashboardFormProps {
+  onSubmit?: (values: CopyDashboardFormProperties) => Dashboard;
+  onSaved?: (dashboard?: Dashboard) => void;
+  onClose?: () => void;
+  initialValues?: CopyDashboardFormProperties | null;
+  filterPersonalCollections?: FilterItemsInPersonalCollection;
+  onValuesChange?: (vals: CopyDashboardFormProperties) => void;
+}
+
+function CopyDashboardForm({
+  onSubmit,
+  onSaved,
+  onClose,
+  initialValues,
+  filterPersonalCollections,
+  onValuesChange,
+}: CopyDashboardFormProps) {
+  const computedInitialValues = useMemo(
+    () => ({
+      ...DASHBOARD_SCHEMA.getDefault(),
+      ...initialValues,
+    }),
+    [initialValues],
+  );
+
+  const handleSubmit = useCallback(
+    async (values: CopyDashboardFormProperties) => {
+      const result = await onSubmit?.(values);
+      const dashboard = Dashboards.HACK_getObjectFromAction(result);
+      onSaved?.(dashboard);
+    },
+    [onSubmit, onSaved],
+  );
+
+  const handleChange = useCallback(
+    (values: CopyDashboardFormProperties) => {
+      onValuesChange?.(values);
+    },
+    [onValuesChange],
+  );
+
+  return (
+    <FormProvider
+      initialValues={computedInitialValues}
+      validationSchema={DASHBOARD_SCHEMA}
+      onSubmit={handleSubmit}
+      enableReinitialize
+    >
+      <Form>
+        <FormObserver onChange={handleChange} />
+        <FormTextInput
+          name="name"
+          label={t`Name`}
+          placeholder={t`What is the name of your dashboard?`}
+          autoFocus
+          mb="1.5rem"
+        />
+        <FormTextarea
+          name="description"
+          label={t`Description`}
+          placeholder={t`It's optional but oh, so helpful`}
+          nullable
+          mb="1.5rem"
+          minRows={6}
+        />
+        <FormCollectionPicker
+          name="collection_id"
+          title={t`Which collection should this go in?`}
+          filterPersonalCollections={filterPersonalCollections}
+        />
+        <FormCheckbox
+          name="is_shallow_copy"
+          label={<DashboardCopyModalShallowCheckboxLabel />}
+        />
+        <FormFooter>
+          <FormErrorMessage inline />
+          {!!onClose && (
+            <Button type="button" onClick={onClose}>{t`Cancel`}</Button>
+          )}
+          <FormSubmitButton label={t`Duplicate`} />
+        </FormFooter>
+      </Form>
+    </FormProvider>
+  );
+}
+
+export const CopyDashboardFormConnected =
+  _.compose(withRouter)(CopyDashboardForm);
diff --git a/frontend/src/metabase/entities/containers/EntityCopyModal.tsx b/frontend/src/metabase/entities/containers/EntityCopyModal.tsx
index 24b5ae1fcdb2f06539da2e50d3bf06dc3d6c3aa4..75b4833a20db9b3fd1034d84c0929b241c292643 100644
--- a/frontend/src/metabase/entities/containers/EntityCopyModal.tsx
+++ b/frontend/src/metabase/entities/containers/EntityCopyModal.tsx
@@ -1,11 +1,16 @@
 import { dissoc } from "icepick";
 import { t } from "ttag";
 
+import {
+  getInstanceAnalyticsCustomCollection,
+  isInstanceAnalyticsCollection,
+} from "metabase/collections/utils";
 import { useCollectionListQuery } from "metabase/common/hooks";
 import ModalContent from "metabase/components/ModalContent";
 import { CreateCollectionOnTheGo } from "metabase/containers/CreateCollectionOnTheGo";
 import type { FormContainerProps } from "metabase/containers/FormikForm";
-import EntityForm from "metabase/entities/containers/EntityForm";
+import { CopyDashboardFormConnected } from "metabase/dashboard/containers/CopyDashboardForm";
+import { CopyQuestionForm } from "metabase/questions/components/CopyQuestionForm";
 import { Flex, Loader } from "metabase/ui";
 import type { BaseFieldValues } from "metabase-types/forms";
 
@@ -28,13 +33,55 @@ const EntityCopyModal = ({
   onSaved,
   ...props
 }: EntityCopyModalProps & Partial<FormContainerProps<BaseFieldValues>>) => {
-  const { data: collections } = useCollectionListQuery();
+  const { data: collections = [] } = useCollectionListQuery();
+
+  const resolvedObject =
+    typeof entityObject?.getPlainObject === "function"
+      ? entityObject.getPlainObject()
+      : entityObject;
+
+  if (isInstanceAnalyticsCollection(resolvedObject?.collection)) {
+    const customCollection = getInstanceAnalyticsCustomCollection(collections);
+    if (customCollection) {
+      resolvedObject.collection_id = customCollection.id;
+    }
+  }
+
+  const initialValues = {
+    ...dissoc(resolvedObject, "id"),
+    name: resolvedObject.name + " - " + t`Duplicate`,
+  };
+
+  const renderForm = (props: any) => {
+    switch (entityType) {
+      case "dashboards":
+        return (
+          <CopyDashboardFormConnected
+            onSubmit={copy}
+            onClose={onClose}
+            onSaved={onSaved}
+            collections={collections}
+            {...props}
+          />
+        );
+      case "questions":
+        return (
+          <CopyQuestionForm
+            onSubmit={copy}
+            onClose={onClose}
+            onSaved={onSaved}
+            collections={collections}
+            {...props}
+          />
+        );
+    }
+  };
 
   return (
     <CreateCollectionOnTheGo>
       {({ resumedValues }) => (
         <ModalContent
-          title={title || t`Duplicate "${entityObject.name}"`}
+          title={title || t`Duplicate "${resolvedObject.name}"`}
           onClose={onClose}
         >
           {!collections?.length ? (
@@ -42,20 +89,7 @@ const EntityCopyModal = ({
               <Loader />
             </Flex>
           ) : (
-            <EntityForm
-              resumedValues={resumedValues}
-              entityType={entityType}
-              entityObject={{
-                ...dissoc(entityObject, "id"),
-                name: entityObject.name + " - " + t`Duplicate`,
-              }}
-              onSubmit={copy}
-              onClose={onClose}
-              onSaved={onSaved}
-              submitTitle={t`Duplicate`}
-              collections={collections}
-              {...props}
-            />
+            renderForm({ ...props, resumedValues, initialValues })
           )}
         </ModalContent>
       )}
diff --git a/frontend/src/metabase/forms/components/FormObserver/FormObserver.tsx b/frontend/src/metabase/forms/components/FormObserver/FormObserver.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c8d2abaf0ec17d8582f8f022f0eaf7b02f1e1a5f
--- /dev/null
+++ b/frontend/src/metabase/forms/components/FormObserver/FormObserver.tsx
@@ -0,0 +1,21 @@
+import { useFormikContext } from "formik";
+import { useEffect } from "react";
+
+interface FormObserverProps<T> {
+  onChange: (vals: T) => void;
+}
+
+/** This component can be used to effectivy add an onChange handler to a from.
+    however, this should be used with caution as it is bad practice to duplicate
+    state. */
+export const FormObserver = <T,>({ onChange }: FormObserverProps<T>) => {
+  const { values } = useFormikContext<T>();
+
+  useEffect(() => {
+    if (values) {
+      onChange(values);
+    }
+  }, [values, onChange]);
+
+  return null;
+};
diff --git a/frontend/src/metabase/forms/components/FormObserver/index.ts b/frontend/src/metabase/forms/components/FormObserver/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..16cd020dcec07e8d580f1466766c79d9b2a770fb
--- /dev/null
+++ b/frontend/src/metabase/forms/components/FormObserver/index.ts
@@ -0,0 +1 @@
+export * from "./FormObserver";
diff --git a/frontend/src/metabase/forms/components/FormTextInput/FormTextInput.tsx b/frontend/src/metabase/forms/components/FormTextInput/FormTextInput.tsx
index eab58db63ce88bd93ca6c7c7290e26939730e676..aa931dacae2ccabfc6ecc461a0632319b60b8bbd 100644
--- a/frontend/src/metabase/forms/components/FormTextInput/FormTextInput.tsx
+++ b/frontend/src/metabase/forms/components/FormTextInput/FormTextInput.tsx
@@ -60,6 +60,11 @@ export const FormTextInput = forwardRef(function FormTextInput(
       onBlur={handleBlur}
       rightSection={hasCopyButton ? <CopyWidgetButton value={value} /> : null}
       rightSectionWidth={hasCopyButton ? 40 : undefined}
+      styles={{
+        input: {
+          fontWeight: "bold",
+        },
+      }}
     />
   );
 });
diff --git a/frontend/src/metabase/forms/components/FormTextarea/FormTextarea.tsx b/frontend/src/metabase/forms/components/FormTextarea/FormTextarea.tsx
index 3037e7c98b8b8bba1984bdc86ca18ec1263deb16..f571a93a1edf0928eaee62559c60cb77b942d0bc 100644
--- a/frontend/src/metabase/forms/components/FormTextarea/FormTextarea.tsx
+++ b/frontend/src/metabase/forms/components/FormTextarea/FormTextarea.tsx
@@ -48,6 +48,11 @@ export const FormTextarea = forwardRef(function FormTextarea(
       error={touched ? error : null}
       onChange={handleChange}
       onBlur={handleBlur}
+      styles={{
+        input: {
+          fontWeight: "bold",
+        },
+      }}
     />
   );
 });
diff --git a/frontend/src/metabase/forms/components/index.ts b/frontend/src/metabase/forms/components/index.ts
index bd873a241dcc9dced8db0b60df3634c7bb1332f6..d469ab767ee8656d318d08d1d905e4d656ed7ca8 100644
--- a/frontend/src/metabase/forms/components/index.ts
+++ b/frontend/src/metabase/forms/components/index.ts
@@ -5,6 +5,7 @@ export * from "./FormErrorMessage";
 export * from "./FormGroupsWidget";
 export * from "./FormGroupWidget";
 export * from "./FormNumberInput";
+export * from "./FormObserver";
 export * from "./FormProvider";
 export * from "./FormRadioGroup";
 export * from "./FormSecretKey";
diff --git a/frontend/src/metabase/questions/components/CopyQuestionForm.tsx b/frontend/src/metabase/questions/components/CopyQuestionForm.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d8133a2dce12fec5a0e47eaac4d293cac34eb50b
--- /dev/null
+++ b/frontend/src/metabase/questions/components/CopyQuestionForm.tsx
@@ -0,0 +1,96 @@
+import { useMemo } from "react";
+import { t } from "ttag";
+import * as Yup from "yup";
+
+import FormCollectionPicker from "metabase/collections/containers/FormCollectionPicker/FormCollectionPicker";
+import FormFooter from "metabase/core/components/FormFooter";
+import {
+  Form,
+  FormTextInput,
+  FormTextarea,
+  FormProvider,
+  FormSubmitButton,
+  FormErrorMessage,
+} from "metabase/forms";
+import * as Errors from "metabase/lib/errors";
+import { Button } from "metabase/ui";
+import type { CollectionId } from "metabase-types/api";
+
+const QUESTION_SCHEMA = Yup.object({
+  name: Yup.string()
+    .required(Errors.required)
+    .max(100, Errors.maxLength)
+    .default(""),
+  description: Yup.string().nullable().max(255, Errors.maxLength).default(null),
+  collection_id: Yup.number().nullable().default(null),
+});
+
+type CopyQuestionProperties = {
+  name: string;
+  description: string | null;
+  collection_id: CollectionId | null;
+};
+
+interface CopyQuestionFormProps {
+  initialValues: Partial<CopyQuestionProperties>;
+  onCancel: () => void;
+  onSubmit: (vals: CopyQuestionProperties) => void;
+  onSaved: () => void;
+}
+
+export const CopyQuestionForm = ({
+  initialValues,
+  onCancel,
+  onSubmit,
+  onSaved,
+}: CopyQuestionFormProps) => {
+  const computedInitialValues = useMemo<CopyQuestionProperties>(
+    () => ({
+      ...QUESTION_SCHEMA.getDefault(),
+      ...initialValues,
+    }),
+    [initialValues],
+  );
+
+  const handleDuplicate = async (vals: CopyQuestionProperties) => {
+    await onSubmit(vals);
+    onSaved?.();
+  };
+
+  return (
+    <FormProvider
+      initialValues={computedInitialValues}
+      validationSchema={QUESTION_SCHEMA}
+      onSubmit={handleDuplicate}
+    >
+      <Form>
+        <FormTextInput
+          name="name"
+          label={t`Name`}
+          placeholder={t`What is the name of your dashboard?`}
+          autoFocus
+          mb="1.5rem"
+        />
+        <FormTextarea
+          name="description"
+          label={t`Description`}
+          placeholder={t`It's optional but oh, so helpful`}
+          nullable
+          mb="1.5rem"
+          minRows={4}
+        />
+        <FormCollectionPicker
+          name="collection_id"
+          title={t`Which collection should this go in?`}
+        />
+        <FormFooter>
+          <FormErrorMessage inline />
+          {!!onCancel && (
+            <Button type="button" onClick={onCancel}>{t`Cancel`}</Button>
+          )}
+          <FormSubmitButton label={t`Duplicate`} />
+        </FormFooter>
+      </Form>
+    </FormProvider>
+  );
+};