diff --git a/e2e/test/scenarios/visualizations-tabular/column-shortcuts/column-shortcuts.cy.spec.ts b/e2e/test/scenarios/visualizations-tabular/column-shortcuts/column-shortcuts.cy.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c87ac1ea2d2092b22f3d15e46ae36fb223423d3
--- /dev/null
+++ b/e2e/test/scenarios/visualizations-tabular/column-shortcuts/column-shortcuts.cy.spec.ts
@@ -0,0 +1,233 @@
+import _ from "underscore";
+
+import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
+import {
+  describeWithSnowplow,
+  enterCustomColumnDetails,
+  getNotebookStep,
+  openNotebook,
+  openOrdersTable,
+  popover,
+  restore,
+  visualize,
+  createQuestion,
+} from "e2e/support/helpers";
+
+const { PEOPLE, PEOPLE_ID } = SAMPLE_DATABASE;
+
+const DATE_CASES = [
+  {
+    option: "Hour of day",
+    value: "21",
+    example: "0, 1",
+  },
+  {
+    option: "Day of month",
+    value: "11",
+    example: "1, 2",
+  },
+  {
+    option: "Day of week",
+    value: "Tuesday",
+    example: "Monday, Tuesday",
+  },
+  {
+    option: "Month of year",
+    value: "Feb",
+    example: "Jan, Feb",
+  },
+  {
+    option: "Quarter of year",
+    value: "Q1",
+    example: "Q1, Q2",
+  },
+  {
+    option: "Year",
+    value: "2,025",
+    example: "2023, 2024",
+  },
+];
+
+const EMAIL_CASES = [
+  {
+    option: "Domain",
+    value: "yahoo",
+    example: "example, online",
+  },
+  {
+    option: "Host",
+    value: "yahoo.com",
+    example: "example.com, online.com",
+  },
+];
+
+const URL_CASES = [
+  {
+    option: "Domain",
+    value: "yahoo",
+    example: "example, online",
+  },
+  {
+    option: "Subdomain",
+    value: "",
+    example: "www, maps",
+  },
+  {
+    option: "Host",
+    value: "yahoo.com",
+    example: "example.com, online.com",
+  },
+];
+
+describeWithSnowplow("extract shortcut", () => {
+  beforeEach(() => {
+    restore();
+    cy.signInAsAdmin();
+  });
+
+  describe("date columns", () => {
+    describe("should add a date expression for each option", () => {
+      DATE_CASES.forEach(({ option, value, example }) => {
+        it(option, () => {
+          openOrdersTable({ limit: 1 });
+          extractColumnAndCheck({
+            column: "Created At",
+            option,
+            value,
+            example,
+          });
+        });
+      });
+    });
+
+    it("should handle duplicate expression names", () => {
+      openOrdersTable({ limit: 1 });
+      extractColumnAndCheck({
+        column: "Created At",
+        option: "Hour of day",
+        newColumn: "Hour of day",
+      });
+      extractColumnAndCheck({
+        column: "Created At",
+        option: "Hour of day",
+        newColumn: "Hour of day_2",
+      });
+    });
+
+    it("should be able to modify the expression in the notebook editor", () => {
+      openOrdersTable({ limit: 1 });
+      extractColumnAndCheck({
+        column: "Created At",
+        option: "Year",
+        value: "2,025",
+      });
+      openNotebook();
+      getNotebookStep("expression").findByText("Year").click();
+      enterCustomColumnDetails({ name: "custom formula", formula: "+ 2" });
+      popover().button("Update").click();
+      visualize();
+      cy.findByRole("gridcell", { name: "2,027" }).should("be.visible");
+    });
+  });
+
+  describe("email columns", () => {
+    beforeEach(() => {
+      restore();
+      cy.signInAsAdmin();
+    });
+
+    EMAIL_CASES.forEach(({ option, value, example }) => {
+      it(option, () => {
+        createQuestion(
+          {
+            query: {
+              "source-table": PEOPLE_ID,
+              limit: 1,
+              fields: [["field", PEOPLE.EMAIL, null]],
+            },
+          },
+          {
+            visitQuestion: true,
+          },
+        );
+
+        extractColumnAndCheck({
+          column: "Email",
+          option,
+          value,
+          example,
+        });
+      });
+    });
+  });
+
+  describe("url columns", () => {
+    beforeEach(() => {
+      restore();
+      cy.signInAsAdmin();
+
+      // Make the Email column a URL column for these tests, to avoid having to create a new model
+      cy.request("PUT", `/api/field/${PEOPLE.EMAIL}`, {
+        semantic_type: "type/URL",
+      });
+    });
+
+    URL_CASES.forEach(({ option, value, example }) => {
+      it(option, () => {
+        createQuestion(
+          {
+            query: {
+              "source-table": PEOPLE_ID,
+              limit: 1,
+              fields: [["field", PEOPLE.EMAIL, { "base-type": "type/String" }]],
+            },
+          },
+          {
+            visitQuestion: true,
+          },
+        );
+
+        extractColumnAndCheck({
+          column: "Email",
+          option,
+          value,
+          example,
+        });
+      });
+    });
+  });
+});
+
+function extractColumnAndCheck({
+  column,
+  option,
+  newColumn = option,
+  value,
+  example,
+}: {
+  column: string;
+  option: string;
+  value?: string;
+  example?: string;
+  newColumn?: string;
+}) {
+  const requestAlias = _.uniqueId("dataset");
+  cy.intercept("POST", "/api/dataset").as(requestAlias);
+  cy.findByLabelText("Add column").click();
+
+  popover().findByText("Extract part of column").click();
+  popover().findAllByText(column).first().click();
+
+  if (example) {
+    popover().findByText(option).parent().should("contain", example);
+  }
+
+  popover().findByText(option).click();
+
+  cy.wait(`@${requestAlias}`);
+
+  cy.findByRole("columnheader", { name: newColumn }).should("be.visible");
+  if (value) {
+    cy.findByRole("gridcell", { name: value }).should("be.visible");
+  }
+}
diff --git a/e2e/test/scenarios/visualizations-tabular/table-column-settings.cy.spec.js b/e2e/test/scenarios/visualizations-tabular/table-column-settings.cy.spec.js
index 65f9966f5bbd0656e7142741c803f6480122231f..671ec4e33ea3dfc6a93da71e8b8d07e68a38c327 100644
--- a/e2e/test/scenarios/visualizations-tabular/table-column-settings.cy.spec.js
+++ b/e2e/test/scenarios/visualizations-tabular/table-column-settings.cy.spec.js
@@ -619,12 +619,14 @@ describe("scenarios > visualizations > table column settings", () => {
         column: "Products → Category",
         columnName: "Products → Category",
         table: "test question",
+        scrollTimes: 3,
       };
 
       const testData2 = {
         column: "Ean",
         columnName: "Product → Ean",
         table: "product",
+        scrollTimes: 3,
       };
 
       _hideColumn(testData);
diff --git a/frontend/src/metabase-lib/types.ts b/frontend/src/metabase-lib/types.ts
index c9fceec95d2564e99ef5395708212ffaedead7e4..387abbe9a3ae7aeb915e3316f9369a4f4429691a 100644
--- a/frontend/src/metabase-lib/types.ts
+++ b/frontend/src/metabase-lib/types.ts
@@ -575,6 +575,7 @@ export interface ClickObject {
   seriesIndex?: number;
   cardId?: CardId;
   settings?: Record<string, unknown>;
+  columnShortcuts?: boolean;
   origin?: {
     row: RowValue;
     cols: DatasetColumn[];
diff --git a/frontend/src/metabase/query_builder/components/expressions/ExtractColumn/ExtractColumn.tsx b/frontend/src/metabase/query_builder/components/expressions/ExtractColumn/ExtractColumn.tsx
index 64280a764087ca93f4a4d2483e1b8c44e951330c..a93cac3f01e69475986f939a804757563d0f4b97 100644
--- a/frontend/src/metabase/query_builder/components/expressions/ExtractColumn/ExtractColumn.tsx
+++ b/frontend/src/metabase/query_builder/components/expressions/ExtractColumn/ExtractColumn.tsx
@@ -2,7 +2,7 @@ import { useState, useMemo } from "react";
 import { t } from "ttag";
 
 import { QueryColumnPicker } from "metabase/common/components/QueryColumnPicker";
-import { Text, Box, Stack, Button } from "metabase/ui";
+import { Text, Box, Stack, Button, Title } from "metabase/ui";
 import * as Lib from "metabase-lib";
 
 import { ExpressionWidgetHeader } from "../ExpressionWidgetHeader";
@@ -18,17 +18,22 @@ type Props = {
     name: string,
     extraction: Lib.ColumnExtraction,
   ) => void;
-  onCancel: () => void;
+  onCancel?: () => void;
 };
 
 export function ExtractColumn({
-  query,
-  stageIndex,
+  query: originalQuery,
+  stageIndex: originalStageIndex,
   onCancel,
   onSubmit,
 }: Props) {
   const [column, setColumn] = useState<Lib.ColumnMetadata | null>(null);
 
+  const { query, stageIndex } = Lib.asReturned(
+    originalQuery,
+    originalStageIndex,
+  );
+
   function handleSelect(column: Lib.ColumnMetadata) {
     setColumn(column);
   }
@@ -81,7 +86,7 @@ function ColumnPicker({
   stageIndex: number;
   column: Lib.ColumnMetadata | null;
   onSelect: (column: Lib.ColumnMetadata) => void;
-  onCancel: () => void;
+  onCancel?: () => void;
 }) {
   const extractableColumns = useMemo(
     () =>
@@ -94,11 +99,18 @@ function ColumnPicker({
 
   return (
     <>
-      <ExpressionWidgetHeader
-        title={t`Select column to extract from`}
-        onBack={onCancel}
-      />
+      {onCancel && (
+        <ExpressionWidgetHeader
+          title={t`Select column to extract from`}
+          onBack={onCancel}
+        />
+      )}
       <Box py="sm">
+        {!onCancel && (
+          <Title p="md" pt="sm" pb={0} order={6}>
+            {t`Select column to extract from`}
+          </Title>
+        )}
         <QueryColumnPicker
           query={query}
           stageIndex={stageIndex}
diff --git a/frontend/src/metabase/visualizations/click-actions/actions/ExtractColumn/ExtractColumn.tsx b/frontend/src/metabase/visualizations/click-actions/actions/ExtractColumn/ExtractColumn.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a6919c54df4fc37b8556d63bf34d244f17c958d0
--- /dev/null
+++ b/frontend/src/metabase/visualizations/click-actions/actions/ExtractColumn/ExtractColumn.tsx
@@ -0,0 +1,64 @@
+import { t } from "ttag";
+
+import { ExtractColumn } from "metabase/query_builder/components/expressions/ExtractColumn";
+import type { LegacyDrill } from "metabase/visualizations/types";
+import type { ClickActionPopoverProps } from "metabase/visualizations/types/click-actions";
+import * as Lib from "metabase-lib";
+
+export const ExtractColumnAction: LegacyDrill = ({ question, clicked }) => {
+  const { isEditable } = Lib.queryDisplayInfo(question.query());
+
+  if (
+    !clicked ||
+    clicked.value !== undefined ||
+    !clicked.columnShortcuts ||
+    clicked?.extraData?.isRawTable ||
+    !isEditable
+  ) {
+    return [];
+  }
+
+  const Popover = ({
+    onChangeCardAndRun,
+    onClose,
+  }: ClickActionPopoverProps) => {
+    const query = question.query();
+    const stageIndex = -1;
+
+    function handleSubmit(
+      _clause: Lib.Clause,
+      _name: string,
+      extraction: Lib.ColumnExtraction,
+    ) {
+      const newQuery = Lib.extract(query, stageIndex, extraction);
+
+      const nextQuestion = question.setQuery(newQuery);
+      const nextCard = nextQuestion.card();
+
+      onChangeCardAndRun({ nextCard });
+      onClose();
+    }
+
+    return (
+      <ExtractColumn
+        query={query}
+        stageIndex={stageIndex}
+        onSubmit={handleSubmit}
+        onCancel={onClose}
+      />
+    );
+  };
+
+  return [
+    {
+      name: "column-extract",
+      title: t`Extract part of column`,
+      tooltip: t`Extract part of column`,
+      buttonType: "horizontal",
+      icon: "arrow_split",
+      default: true,
+      section: "new-column",
+      popover: Popover,
+    },
+  ];
+};
diff --git a/frontend/src/metabase/visualizations/click-actions/actions/ExtractColumn/index.ts b/frontend/src/metabase/visualizations/click-actions/actions/ExtractColumn/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..775b1a3e18b02f6787ed1957d0e8a40e56779c42
--- /dev/null
+++ b/frontend/src/metabase/visualizations/click-actions/actions/ExtractColumn/index.ts
@@ -0,0 +1 @@
+export { ExtractColumnAction } from "./ExtractColumn";
diff --git a/frontend/src/metabase/visualizations/click-actions/modes/DefaultMode.ts b/frontend/src/metabase/visualizations/click-actions/modes/DefaultMode.ts
index 1607e2a4c40e043e9507b27a39ce2a2229a7339c..4ef60b8682dc9fdbaf4b8f06dd5891ac0c70719b 100644
--- a/frontend/src/metabase/visualizations/click-actions/modes/DefaultMode.ts
+++ b/frontend/src/metabase/visualizations/click-actions/modes/DefaultMode.ts
@@ -1,6 +1,7 @@
 import type { QueryClickActionsMode } from "../../types";
 import { ColumnFormattingAction } from "../actions/ColumnFormattingAction";
 import { DashboardClickAction } from "../actions/DashboardClickAction";
+import { ExtractColumnAction } from "../actions/ExtractColumn";
 import { HideColumnAction } from "../actions/HideColumnAction";
 import { NativeQueryClickFallback } from "../actions/NativeQueryClickFallback";
 
@@ -11,6 +12,7 @@ export const DefaultMode: QueryClickActionsMode = {
     HideColumnAction,
     ColumnFormattingAction,
     DashboardClickAction,
+    ExtractColumnAction,
   ],
   fallback: NativeQueryClickFallback,
 };
diff --git a/frontend/src/metabase/visualizations/click-actions/modes/EmbeddingSdkMode.ts b/frontend/src/metabase/visualizations/click-actions/modes/EmbeddingSdkMode.ts
index 53e0b694495de31bc77e57e8c74c88d4711a2ca4..c7adf1fdb4a6551a98615c083392b43707207b84 100644
--- a/frontend/src/metabase/visualizations/click-actions/modes/EmbeddingSdkMode.ts
+++ b/frontend/src/metabase/visualizations/click-actions/modes/EmbeddingSdkMode.ts
@@ -1,5 +1,6 @@
 import type { QueryClickActionsMode } from "../../types";
 import { DashboardClickAction } from "../actions/DashboardClickAction";
+import { ExtractColumnAction } from "../actions/ExtractColumn";
 import { HideColumnAction } from "../actions/HideColumnAction";
 import { NativeQueryClickFallback } from "../actions/NativeQueryClickFallback";
 
@@ -23,6 +24,6 @@ export const EmbeddingSdkMode: QueryClickActionsMode = {
     "drill-thru/zoom-in.geographic",
     "drill-thru/zoom-in.timeseries",
   ],
-  clickActions: [HideColumnAction, DashboardClickAction],
+  clickActions: [HideColumnAction, DashboardClickAction, ExtractColumnAction],
   fallback: NativeQueryClickFallback,
 };
diff --git a/frontend/src/metabase/visualizations/components/ClickActions/utils.ts b/frontend/src/metabase/visualizations/components/ClickActions/utils.ts
index 1dde6aa0c5a5c82898e2bf76889ce999222f4c35..f4f27a9ce1f39639c555cd5c08db6e1bddafe7ee 100644
--- a/frontend/src/metabase/visualizations/components/ClickActions/utils.ts
+++ b/frontend/src/metabase/visualizations/components/ClickActions/utils.ts
@@ -29,6 +29,7 @@ export const SECTIONS: Record<ClickActionSection, Section> = {
   filter: {},
   details: {},
   custom: {},
+  "new-column": {},
 };
 Object.values(SECTIONS).map((section, index) => {
   section.index = index;
@@ -82,6 +83,9 @@ export const getSectionTitle = (
 
     case "extract-popover":
       return t`Select a part to extract`;
+
+    case "new-column":
+      return t`New column`;
   }
 
   return null;
diff --git a/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx b/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx
index 0d3f6cb4a4fe19a020bebc83336fbbd9d64d0ccd..dc18ff208f40e9b9ded9b8c1f1f3c13a31bb78c4 100644
--- a/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx
+++ b/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx
@@ -25,7 +25,7 @@ import {
 } from "metabase/query_builder/selectors";
 import { getIsEmbeddingSdk } from "metabase/selectors/embed";
 import { EmotionCacheProvider } from "metabase/styled-components/components/EmotionCacheProvider";
-import { Icon, DelayGroup } from "metabase/ui";
+import { Button as UIButton, Icon, DelayGroup } from "metabase/ui";
 import {
   getTableCellClickedObject,
   getTableHeaderClickedObject,
@@ -1017,6 +1017,7 @@ class TableInteractive extends Component {
       data: { cols, rows },
       className,
       scrollToColumn,
+      question,
     } = this.props;
 
     if (!width || !height) {
@@ -1025,6 +1026,9 @@ class TableInteractive extends Component {
 
     const headerHeight = this.props.tableHeaderHeight || HEADER_HEIGHT;
     const gutterColumn = this.state.showDetailShortcut ? 1 : 0;
+    const shortcutColumn = 1;
+    const query = question?.query();
+    const info = query && Lib.queryDisplayInfo(query);
 
     return (
       <DelayGroup>
@@ -1091,6 +1095,18 @@ class TableInteractive extends Component {
                     </div>
                   </>
                 )}
+                {shortcutColumn && (
+                  <ColumnShortcut
+                    height={headerHeight - 1}
+                    isEditable={info?.isEditable}
+                    onClick={evt => {
+                      this.onVisualizationClick(
+                        { columnShortcuts: true },
+                        evt.target,
+                      );
+                    }}
+                  />
+                )}
                 <Grid
                   ref={ref => (this.header = ref)}
                   style={{
@@ -1110,16 +1126,24 @@ class TableInteractive extends Component {
                   height={headerHeight}
                   rowCount={1}
                   rowHeight={headerHeight}
-                  columnCount={cols.length + gutterColumn}
+                  columnCount={cols.length + gutterColumn + shortcutColumn}
                   columnWidth={this.getDisplayColumnWidth}
-                  cellRenderer={props =>
-                    gutterColumn && props.columnIndex === 0
-                      ? null // we need a phantom cell to properly offset columns
-                      : this.tableHeaderRenderer({
-                          ...props,
-                          columnIndex: props.columnIndex - gutterColumn,
-                        })
-                  }
+                  cellRenderer={props => {
+                    if (props.columnIndex === 0 && gutterColumn) {
+                      // we need a phantom cell to properly offset gutter columns
+                      return null;
+                    }
+
+                    if (props.columnIndex === cols.length + gutterColumn) {
+                      // we need a phantom cell to properly offset the shortcut column
+                      return null;
+                    }
+
+                    return this.tableHeaderRenderer({
+                      ...props,
+                      columnIndex: props.columnIndex - gutterColumn,
+                    });
+                  }}
                   onScroll={({ scrollLeft }) => onScroll({ scrollLeft })}
                   scrollLeft={scrollLeft}
                   tabIndex={null}
@@ -1137,18 +1161,26 @@ class TableInteractive extends Component {
                   }}
                   width={width}
                   height={height - headerHeight}
-                  columnCount={cols.length + gutterColumn}
+                  columnCount={cols.length + gutterColumn + shortcutColumn}
                   columnWidth={this.getDisplayColumnWidth}
                   rowCount={rows.length}
                   rowHeight={ROW_HEIGHT}
-                  cellRenderer={props =>
-                    gutterColumn && props.columnIndex === 0
-                      ? null // we need a phantom cell to properly offset columns
-                      : this.cellRenderer({
-                          ...props,
-                          columnIndex: props.columnIndex - gutterColumn,
-                        })
-                  }
+                  cellRenderer={props => {
+                    if (props.columnIndex === 0 && gutterColumn) {
+                      // we need a phantom cell to properly offset gutter columns
+                      return null;
+                    }
+
+                    if (props.columnIndex === cols.length + gutterColumn) {
+                      // we need a phantom cell to properly offset the shortcut column
+                      return null;
+                    }
+
+                    return this.cellRenderer({
+                      ...props,
+                      columnIndex: props.columnIndex - gutterColumn,
+                    });
+                  }}
                   scrollTop={scrollTop}
                   onScroll={({ scrollLeft, scrollTop }) => {
                     this.props.onActionDismissal();
@@ -1239,3 +1271,22 @@ const DetailShortcut = forwardRef((_props, ref) => (
 ));
 
 DetailShortcut.displayName = "DetailShortcut";
+
+function ColumnShortcut({ height, onClick, isEditable }) {
+  if (!isEditable) {
+    return null;
+  }
+
+  return (
+    <div className={TableS.shortcutsWrapper} style={{ height }}>
+      <UIButton
+        variant="filled"
+        compact
+        leftIcon={<Icon name="add" />}
+        title={t`Add column`}
+        aria-label={t`Add column`}
+        onClick={onClick}
+      />
+    </div>
+  );
+}
diff --git a/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.module.css b/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.module.css
index 9add690363d917a592b07cb49f385060dce80743..94b5995372a352675dc9164c5e84aaff671f93b8 100644
--- a/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.module.css
+++ b/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.module.css
@@ -105,3 +105,16 @@
 .TableInteractiveGutter {
   background-color: var(--color-bg-white);
 }
+
+.shortcutsWrapper {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 50;
+  background: white;
+  padding: 0 0.5em;
+  box-sizing: border-box;
+  border-left: 1px solid var(--color-border);
+  display: flex;
+  align-items: center;
+}
diff --git a/frontend/src/metabase/visualizations/types/click-actions.ts b/frontend/src/metabase/visualizations/types/click-actions.ts
index 43c424c809463aef52286df0a7d27906a97892a4..e7b8c59efa3e88244e93fff9f77675e35f2cf659 100644
--- a/frontend/src/metabase/visualizations/types/click-actions.ts
+++ b/frontend/src/metabase/visualizations/types/click-actions.ts
@@ -35,6 +35,7 @@ export type ClickActionSection =
   | "filter"
   | "info"
   | "records"
+  | "new-column"
   | "sort"
   | "standalone_filter"
   | "sum"