diff --git a/packages/desktop-client/src/components/forms.tsx b/packages/desktop-client/src/components/forms.tsx
index cf13ec0d2903cd47f7c9c47b904e592a9294f354..1fcd8fcfd3fd640c00a37c8b2729d4b5ab91534b 100644
--- a/packages/desktop-client/src/components/forms.tsx
+++ b/packages/desktop-client/src/components/forms.tsx
@@ -105,6 +105,17 @@ export const Checkbox = (props: CheckboxProps) => {
                 content: ' ',
               },
             },
+            ':disabled': {
+              border: '1px solid ' + theme.buttonNormalDisabledBorder,
+              backgroundColor: theme.buttonNormalDisabledBorder,
+            },
+            ':checked:disabled': {
+              border: '1px solid ' + theme.buttonNormalDisabledBorder,
+              backgroundColor: theme.buttonNormalDisabledBorder,
+              '::after': {
+                backgroundColor: theme.buttonNormalDisabledBorder,
+              },
+            },
             '&.focus-visible:focus': {
               '::before': {
                 position: 'absolute',
diff --git a/packages/desktop-client/src/components/modals/ImportTransactions.jsx b/packages/desktop-client/src/components/modals/ImportTransactions.jsx
index cfe9133c7731afee0cddb36c159c3dec14a5c52b..b1675e77e61cf1fd8de4f1e28cb79d599e761a23 100644
--- a/packages/desktop-client/src/components/modals/ImportTransactions.jsx
+++ b/packages/desktop-client/src/components/modals/ImportTransactions.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useMemo } from 'react';
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
 
 import * as d from 'date-fns';
 
@@ -12,6 +12,7 @@ import {
 import { useActions } from '../../hooks/useActions';
 import { useDateFormat } from '../../hooks/useDateFormat';
 import { useLocalPrefs } from '../../hooks/useLocalPrefs';
+import { SvgDownAndRightArrow } from '../../icons/v2';
 import { theme, styles } from '../../style';
 import { Button, ButtonWithLoading } from '../common/Button';
 import { Input } from '../common/Input';
@@ -19,6 +20,7 @@ import { Modal } from '../common/Modal';
 import { Select } from '../common/Select';
 import { Stack } from '../common/Stack';
 import { Text } from '../common/Text';
+import { Tooltip } from '../common/Tooltip';
 import { View } from '../common/View';
 import { Checkbox, SectionLabel } from '../forms';
 import { TableHeader, TableWithNavigator, Row, Field } from '../table';
@@ -245,6 +247,12 @@ function applyFieldMappings(transaction, mappings) {
 
     result[field] = transaction[target || field];
   }
+  // Keep preview fields on the mapped transactions
+  result.trx_id = transaction.trx_id;
+  result.existing = transaction.existing;
+  result.ignored = transaction.ignored;
+  result.selected = transaction.selected;
+  result.selected_merge = transaction.selected_merge;
   return result;
 }
 
@@ -335,33 +343,124 @@ function Transaction({
   flipAmount,
   multiplierAmount,
   categories,
+  onCheckTransaction,
+  reconcile,
 }) {
   const categoryList = categories.map(category => category.name);
   const transaction = useMemo(
     () =>
-      fieldMappings
+      fieldMappings && !rawTransaction.isMatchedTransaction
         ? applyFieldMappings(rawTransaction, fieldMappings)
         : rawTransaction,
     [rawTransaction, fieldMappings],
   );
 
-  const { amount, outflow, inflow } = parseAmountFields(
-    transaction,
-    splitMode,
-    inOutMode,
-    outValue,
-    flipAmount,
-    multiplierAmount,
-  );
+  let amount, outflow, inflow;
+  if (rawTransaction.isMatchedTransaction) {
+    amount = rawTransaction.amount;
+    if (splitMode) {
+      outflow = amount < 0 ? -amount : 0;
+      inflow = amount > 0 ? amount : 0;
+    }
+  } else {
+    ({ amount, outflow, inflow } = parseAmountFields(
+      transaction,
+      splitMode,
+      inOutMode,
+      outValue,
+      flipAmount,
+      multiplierAmount,
+    ));
+  }
 
   return (
     <Row
       style={{
         backgroundColor: theme.tableBackground,
+        color:
+          (transaction.isMatchedTransaction && !transaction.selected_merge) ||
+          !transaction.selected
+            ? theme.tableTextInactive
+            : theme.tableText,
       }}
     >
+      {reconcile && (
+        <Field width={31}>
+          {!transaction.isMatchedTransaction && (
+            <Tooltip
+              content={
+                !transaction.existing && !transaction.ignored
+                  ? 'New transaction. You can import it, or skip it.'
+                  : transaction.ignored
+                    ? 'Already imported transaction. You can skip it, or import it again.'
+                    : transaction.existing
+                      ? 'Updated transaction. You can update it, import it again, or skip it.'
+                      : ''
+              }
+              placement="right top"
+            >
+              <Checkbox
+                checked={transaction.selected}
+                onChange={() => onCheckTransaction(transaction.trx_id)}
+                style={
+                  transaction.selected_merge
+                    ? {
+                        ':checked': {
+                          '::after': {
+                            background:
+                              theme.checkboxBackgroundSelected +
+                              // update sign from packages/desktop-client/src/icons/v1/layer.svg
+                              // eslint-disable-next-line rulesdir/typography
+                              ' url(\'data:image/svg+xml; utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="white" d="M10 1l10 6-10 6L0 7l10-6zm6.67 10L20 13l-10 6-10-6 3.33-2L10 15l6.67-4z" /></svg>\') 9px 9px',
+                          },
+                        },
+                      }
+                    : {
+                        '&': {
+                          border:
+                            '1px solid ' + theme.buttonNormalDisabledBorder,
+                          backgroundColor: theme.buttonNormalDisabledBorder,
+                          '::after': {
+                            display: 'block',
+                            background:
+                              theme.buttonNormalDisabledBorder +
+                              // minus sign adapted from packages/desktop-client/src/icons/v1/add.svg
+                              // eslint-disable-next-line rulesdir/typography
+                              ' url(\'data:image/svg+xml; utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="white" className="path" d="M23,11.5 L23,11.5 L23,11.5 C23,12.3284271 22.3284271,13 21.5,13 L1.5,13 L1.5,13 C0.671572875,13 1.01453063e-16,12.3284271 0,11.5 L0,11.5 L0,11.5 C-1.01453063e-16,10.6715729 0.671572875,10 1.5,10 L21.5,10 L21.5,10 C22.3284271,10 23,10.6715729 23,11.5 Z" /></svg>\') 9px 9px',
+                            width: 9,
+                            height: 9,
+                            content: ' ',
+                          },
+                        },
+                        ':checked': {
+                          border: '1px solid ' + theme.checkboxBorderSelected,
+                          backgroundColor: theme.checkboxBackgroundSelected,
+                          '::after': {
+                            background:
+                              theme.checkboxBackgroundSelected +
+                              // plus sign from packages/desktop-client/src/icons/v1/add.svg
+                              // eslint-disable-next-line rulesdir/typography
+                              ' url(\'data:image/svg+xml; utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="white" className="path" d="M23,11.5 L23,11.5 L23,11.5 C23,12.3284271 22.3284271,13 21.5,13 L1.5,13 L1.5,13 C0.671572875,13 1.01453063e-16,12.3284271 0,11.5 L0,11.5 L0,11.5 C-1.01453063e-16,10.6715729 0.671572875,10 1.5,10 L21.5,10 L21.5,10 C22.3284271,10 23,10.6715729 23,11.5 Z" /><path fill="white" className="path" d="M11.5,23 C10.6715729,23 10,22.3284271 10,21.5 L10,1.5 C10,0.671572875 10.6715729,1.52179594e-16 11.5,0 C12.3284271,-1.52179594e-16 13,0.671572875 13,1.5 L13,21.5 C13,22.3284271 12.3284271,23 11.5,23 Z" /></svg>\') 9px 9px',
+                          },
+                        },
+                      }
+                }
+              />
+            </Tooltip>
+          )}
+        </Field>
+      )}
       <Field width={200}>
-        {showParsed ? (
+        {transaction.isMatchedTransaction ? (
+          <View>
+            <Stack direction="row" align="flex-start">
+              <View>
+                <SvgDownAndRightArrow width={16} height={16} />
+              </View>
+              <View>{formatDate(transaction.date, dateFormat)}</View>
+            </Stack>
+          </View>
+        ) : showParsed ? (
           <ParsedDate
             parseDateFormat={parseDateFormat}
             dateFormat={dateFormat}
@@ -625,7 +724,9 @@ function FieldMappings({
     return null;
   }
 
-  const options = Object.keys(transactions[0]);
+  const { existing, ignored, selected, selected_merge, trx_id, ...trans } =
+    transactions[0];
+  const options = Object.keys(trans);
   mappings = mappings || {};
 
   return (
@@ -738,8 +839,13 @@ function FieldMappings({
 export function ImportTransactions({ modalProps, options }) {
   const dateFormat = useDateFormat() || 'MM/dd/yyyy';
   const prefs = useLocalPrefs();
-  const { parseTransactions, importTransactions, getPayees, savePrefs } =
-    useActions();
+  const {
+    parseTransactions,
+    importTransactions,
+    importPreviewTransactions,
+    getPayees,
+    savePrefs,
+  } = useActions();
 
   const [multiplierAmount, setMultiplierAmount] = useState('');
   const [loadingState, setLoadingState] = useState('parsing');
@@ -754,6 +860,7 @@ export function ImportTransactions({ modalProps, options }) {
   const [flipAmount, setFlipAmount] = useState(false);
   const [multiplierEnabled, setMultiplierEnabled] = useState(false);
   const [reconcile, setReconcile] = useState(true);
+  const [previewTrigger, setPreviewTrigger] = useState(0);
   const { accountId, categories, onImported } = options;
 
   // This cannot be set after parsing the file, because changing it
@@ -783,7 +890,18 @@ export function ImportTransactions({ modalProps, options }) {
     setFilename(filename);
     setFileType(filetype);
 
-    const { errors, transactions } = await parseTransactions(filename, options);
+    const { errors, transactions: parsedTransactions } =
+      await parseTransactions(filename, options);
+
+    let index = 0;
+    const transactions = parsedTransactions.map(trans => {
+      // Add a transient transaction id to match preview with imported transactions
+      trans.trx_id = index++;
+      // Select all parsed transactions before first preview run
+      trans.selected = true;
+      return trans;
+    });
+
     setLoadingState(null);
     setError(null);
 
@@ -794,8 +912,14 @@ export function ImportTransactions({ modalProps, options }) {
         message: errors[0].message || 'Internal error',
       });
     } else {
+      let flipAmount = false;
+      let fieldMappings = null;
+      let splitMode = false;
+      let parseDateFormat = null;
+
       if (filetype === 'csv' || filetype === 'qif') {
-        setFlipAmount(prefs[`flip-amount-${accountId}-${filetype}`] || false);
+        flipAmount = prefs[`flip-amount-${accountId}-${filetype}`] || false;
+        setFlipAmount(flipAmount);
       }
 
       if (filetype === 'csv') {
@@ -804,21 +928,22 @@ export function ImportTransactions({ modalProps, options }) {
           ? JSON.parse(mappings)
           : getInitialMappings(transactions);
 
+        fieldMappings = mappings;
         setFieldMappings(mappings);
 
         // Set initial split mode based on any saved mapping
-        const initialSplitMode = !!(mappings.outflow || mappings.inflow);
-        setSplitMode(initialSplitMode);
+        splitMode = !!(mappings.outflow || mappings.inflow);
+        setSplitMode(splitMode);
 
-        setParseDateFormat(
+        parseDateFormat =
           prefs[`parse-date-${accountId}-${filetype}`] ||
-            getInitialDateFormat(transactions, mappings),
-        );
+          getInitialDateFormat(transactions, mappings);
+        setParseDateFormat(parseDateFormat);
       } else if (filetype === 'qif') {
-        setParseDateFormat(
+        parseDateFormat =
           prefs[`parse-date-${accountId}-${filetype}`] ||
-            getInitialDateFormat(transactions, { date: 'date' }),
-        );
+          getInitialDateFormat(transactions, { date: 'date' });
+        setParseDateFormat(parseDateFormat);
       } else {
         setFieldMappings(null);
         setParseDateFormat(null);
@@ -827,7 +952,18 @@ export function ImportTransactions({ modalProps, options }) {
       // Reverse the transactions because it's very common for them to
       // be ordered ascending, but we show transactions descending by
       // date. This is purely cosmetic.
-      setTransactions(transactions.reverse());
+      const transactionPreview = await getImportPreview(
+        transactions.reverse(),
+        filetype,
+        flipAmount,
+        fieldMappings,
+        splitMode,
+        parseDateFormat,
+        inOutMode,
+        outValue,
+        multiplierAmount,
+      );
+      setTransactions(transactionPreview);
     }
   }
 
@@ -835,6 +971,7 @@ export function ImportTransactions({ modalProps, options }) {
     const amt = e;
     if (!amt || amt.match(/^\d{1,}(\.\d{0,4})?$/)) {
       setMultiplierAmount(amt);
+      runImportPreview();
     }
   }
 
@@ -902,7 +1039,54 @@ export function ImportTransactions({ modalProps, options }) {
   }
 
   function onUpdateFields(field, name) {
-    setFieldMappings({ ...fieldMappings, [field]: name === '' ? null : name });
+    const newFieldMappings = {
+      ...fieldMappings,
+      [field]: name === '' ? null : name,
+    };
+    setFieldMappings(newFieldMappings);
+    runImportPreview();
+  }
+
+  function onCheckTransaction(trx_id) {
+    const newTransactions = transactions.map(trans => {
+      if (trans.trx_id === trx_id) {
+        if (trans.existing) {
+          // 3-states management for transactions with existing (merged transactions)
+          // flow of states:
+          // (selected true && selected_merge true)
+          //   => (selected true && selected_merge false)
+          //     => (selected false)
+          //       => back to (selected true && selected_merge true)
+          if (!trans.selected) {
+            return {
+              ...trans,
+              selected: true,
+              selected_merge: true,
+            };
+          } else if (trans.selected_merge) {
+            return {
+              ...trans,
+              selected: true,
+              selected_merge: false,
+            };
+          } else {
+            return {
+              ...trans,
+              selected: false,
+              selected_merge: false,
+            };
+          }
+        } else {
+          return {
+            ...trans,
+            selected: !trans.selected,
+          };
+        }
+      }
+      return trans;
+    });
+
+    setTransactions(newTransactions);
   }
 
   async function onImport() {
@@ -912,6 +1096,16 @@ export function ImportTransactions({ modalProps, options }) {
     let errorMessage;
 
     for (let trans of transactions) {
+      if (
+        trans.isMatchedTransaction ||
+        (reconcile && !trans.selected && !trans.ignored)
+      ) {
+        // skip transactions that are
+        // - matched transaction (existing transaction added to show update changes)
+        // - unselected transactions that are not ignored by the reconcilation algorithm (only when reconcilation is enabled)
+        continue;
+      }
+
       trans = fieldMappings ? applyFieldMappings(trans, fieldMappings) : trans;
 
       const date =
@@ -941,7 +1135,29 @@ export function ImportTransactions({ modalProps, options }) {
       const category_id = parseCategoryFields(trans, categories.list);
       trans.category = category_id;
 
-      const { inflow, outflow, inOut, ...finalTransaction } = trans;
+      const {
+        inflow,
+        outflow,
+        inOut,
+        existing,
+        ignored,
+        selected,
+        selected_merge,
+        trx_id,
+        ...finalTransaction
+      } = trans;
+
+      if (
+        reconcile &&
+        ((trans.ignored && trans.selected) ||
+          (trans.existing && trans.selected && !trans.selected_merge))
+      ) {
+        // in reconcile mode, force transaction add for
+        // - ignored transactions (aleardy existing) that are checked
+        // - transactions with existing (merged transactions) that are not selected_merge
+        finalTransaction.forceAddTransaction = true;
+      }
+
       finalTransactions.push({
         ...finalTransaction,
         date,
@@ -994,6 +1210,156 @@ export function ImportTransactions({ modalProps, options }) {
     modalProps.onClose();
   }
 
+  const runImportPreviewCallback = useCallback(async () => {
+    const transactionPreview = await getImportPreview(
+      transactions,
+      filetype,
+      flipAmount,
+      fieldMappings,
+      splitMode,
+      parseDateFormat,
+      inOutMode,
+      outValue,
+      multiplierAmount,
+    );
+    setTransactions(transactionPreview);
+  }, [
+    transactions,
+    filetype,
+    flipAmount,
+    fieldMappings,
+    splitMode,
+    parseDateFormat,
+    inOutMode,
+    outValue,
+    multiplierAmount,
+  ]);
+
+  useEffect(() => {
+    runImportPreviewCallback();
+  }, [previewTrigger]);
+
+  function runImportPreview() {
+    setPreviewTrigger(value => value + 1);
+  }
+
+  async function getImportPreview(
+    transactions,
+    filetype,
+    flipAmount,
+    fieldMappings,
+    splitMode,
+    parseDateFormat,
+    inOutMode,
+    outValue,
+    multiplierAmount,
+  ) {
+    const previewTransactions = [];
+
+    for (let trans of transactions) {
+      if (trans.isMatchedTransaction) {
+        // skip transactions that are matched transaction (existing transaction added to show update changes)
+        continue;
+      }
+
+      trans = fieldMappings ? applyFieldMappings(trans, fieldMappings) : trans;
+
+      const date = isOfxFile(filetype)
+        ? trans.date
+        : parseDate(trans.date, parseDateFormat);
+      if (date == null) {
+        console.log(
+          `Unable to parse date ${
+            trans.date || '(empty)'
+          } with given date format`,
+        );
+        break;
+      }
+
+      const { amount } = parseAmountFields(
+        trans,
+        splitMode,
+        inOutMode,
+        outValue,
+        flipAmount,
+        multiplierAmount,
+      );
+      if (amount == null) {
+        console.log(`Transaction on ${trans.date} has no amount`);
+        break;
+      }
+
+      const category_id = parseCategoryFields(trans, categories.list);
+      if (category_id != null) {
+        trans.category = category_id;
+      }
+
+      const {
+        inflow,
+        outflow,
+        inOut,
+        existing,
+        ignored,
+        selected,
+        selected_merge,
+        ...finalTransaction
+      } = trans;
+      previewTransactions.push({
+        ...finalTransaction,
+        date,
+        amount: amountToInteger(amount),
+        cleared: clearOnImport,
+      });
+    }
+
+    // Retreive the transactions that would be updated (along with the existing trx)
+    const previewTrx = await importPreviewTransactions(
+      accountId,
+      previewTransactions,
+    );
+    const matchedUpdateMap = previewTrx.reduce((map, entry) => {
+      map[entry.transaction.trx_id] = entry;
+      return map;
+    }, {});
+
+    return transactions
+      .filter(trans => !trans.isMatchedTransaction)
+      .reduce((previous, current_trx) => {
+        let next = previous;
+        const entry = matchedUpdateMap[current_trx.trx_id];
+        const existing_trx = entry?.existing;
+
+        // if the transaction is matched with an existing one for update
+        current_trx.existing = !!existing_trx;
+        // if the transaction is an update that will be ignored
+        // (reconciled transactions or no change detected)
+        current_trx.ignored = entry?.ignored || false;
+
+        current_trx.selected = !current_trx.ignored;
+        current_trx.selected_merge = current_trx.existing;
+
+        next = next.concat({ ...current_trx });
+
+        if (existing_trx) {
+          // add the updated existing transaction in the list, with the
+          // isMatchedTransaction flag to identify it in display and not send it again
+          existing_trx.isMatchedTransaction = true;
+          existing_trx.category = categories.list.find(
+            cat => cat.id === existing_trx.category,
+          )?.name;
+          // add parent transaction attribute to mimic behaviour
+          existing_trx.trx_id = current_trx.trx_id;
+          existing_trx.existing = current_trx.existing;
+          existing_trx.selected = current_trx.selected;
+          existing_trx.selected_merge = current_trx.selected_merge;
+
+          next = next.concat({ ...existing_trx });
+        }
+
+        return next;
+      }, []);
+  }
+
   const headers = [
     { name: 'Date', width: 200 },
     { name: 'Payee', width: 'flex' },
@@ -1001,6 +1367,9 @@ export function ImportTransactions({ modalProps, options }) {
     { name: 'Category', width: 'flex' },
   ];
 
+  if (reconcile) {
+    headers.unshift({ name: ' ', width: 31 });
+  }
   if (inOutMode) {
     headers.push({ name: 'In/Out', width: 90, style: { textAlign: 'left' } });
   }
@@ -1038,7 +1407,11 @@ export function ImportTransactions({ modalProps, options }) {
           <TableHeader headers={headers} />
 
           <TableWithNavigator
-            items={transactions}
+            items={transactions.filter(
+              trans =>
+                !trans.isMatchedTransaction ||
+                (trans.isMatchedTransaction && reconcile),
+            )}
             fields={['payee', 'category', 'amount']}
             style={{ backgroundColor: theme.tableHeaderBackground }}
             getItemKey={index => index}
@@ -1070,6 +1443,8 @@ export function ImportTransactions({ modalProps, options }) {
                   flipAmount={flipAmount}
                   multiplierAmount={multiplierAmount}
                   categories={categories.list}
+                  onCheckTransaction={onCheckTransaction}
+                  reconcile={reconcile}
                 />
               </View>
             )}
@@ -1094,7 +1469,7 @@ export function ImportTransactions({ modalProps, options }) {
       )}
 
       {filetype === 'csv' && (
-        <View style={{ marginTop: 25 }}>
+        <View style={{ marginTop: 10 }}>
           <FieldMappings
             transactions={transactions}
             onChange={onUpdateFields}
@@ -1128,16 +1503,16 @@ export function ImportTransactions({ modalProps, options }) {
           id="form_dont_reconcile"
           checked={reconcile}
           onChange={() => {
-            setReconcile(state => !state);
+            setReconcile(!reconcile);
           }}
         >
-          Reconcile transactions
+          Merge with existing transactions
         </CheckboxOption>
       )}
 
       {/*Import Options */}
       {(filetype === 'qif' || filetype === 'csv') && (
-        <View style={{ marginTop: 25 }}>
+        <View style={{ marginTop: 10 }}>
           <Stack
             direction="row"
             align="flex-start"
@@ -1151,14 +1526,17 @@ export function ImportTransactions({ modalProps, options }) {
                   transactions={transactions}
                   fieldMappings={fieldMappings}
                   parseDateFormat={parseDateFormat}
-                  onChange={setParseDateFormat}
+                  onChange={value => {
+                    setParseDateFormat(value);
+                    runImportPreview();
+                  }}
                 />
               )}
             </View>
 
             {/* CSV Options */}
             {filetype === 'csv' && (
-              <View style={{ marginLeft: 25, gap: 5 }}>
+              <View style={{ marginLeft: 10, gap: 5 }}>
                 <SectionLabel title="CSV OPTIONS" />
                 <label
                   style={{
@@ -1219,23 +1597,26 @@ export function ImportTransactions({ modalProps, options }) {
                   id="form_dont_reconcile"
                   checked={reconcile}
                   onChange={() => {
-                    setReconcile(state => !state);
+                    setReconcile(!reconcile);
                   }}
                 >
-                  Reconcile transactions
+                  Merge with existing transactions
                 </CheckboxOption>
               </View>
             )}
 
             <View style={{ flex: 1 }} />
 
-            <View style={{ marginRight: 25, gap: 5 }}>
+            <View style={{ marginRight: 10, gap: 5 }}>
               <SectionLabel title="AMOUNT OPTIONS" />
               <CheckboxOption
                 id="form_flip"
                 checked={flipAmount}
                 disabled={splitMode || inOutMode}
-                onChange={() => setFlipAmount(!flipAmount)}
+                onChange={() => {
+                  setFlipAmount(!flipAmount);
+                  runImportPreview();
+                }}
               >
                 Flip amount
               </CheckboxOption>
@@ -1245,7 +1626,10 @@ export function ImportTransactions({ modalProps, options }) {
                     id="form_split"
                     checked={splitMode}
                     disabled={inOutMode || flipAmount}
-                    onChange={onSplitMode}
+                    onChange={() => {
+                      onSplitMode();
+                      runImportPreview();
+                    }}
                   >
                     Split amount into separate inflow/outflow columns
                   </CheckboxOption>
@@ -1253,7 +1637,10 @@ export function ImportTransactions({ modalProps, options }) {
                     inOutMode={inOutMode}
                     outValue={outValue}
                     disabled={splitMode || flipAmount}
-                    onToggle={() => setInOutMode(!inOutMode)}
+                    onToggle={() => {
+                      setInOutMode(!inOutMode);
+                      runImportPreview();
+                    }}
                     onChangeText={setOutValue}
                   />
                 </>
@@ -1264,6 +1651,7 @@ export function ImportTransactions({ modalProps, options }) {
                 onToggle={() => {
                   setMultiplierEnabled(!multiplierEnabled);
                   setMultiplierAmount('');
+                  runImportPreview();
                 }}
                 onChangeAmount={onMultiplierChange}
               />
@@ -1279,15 +1667,21 @@ export function ImportTransactions({ modalProps, options }) {
             alignSelf: 'flex-end',
             flexDirection: 'row',
             alignItems: 'center',
+            gap: '1em',
           }}
         >
           <ButtonWithLoading
             type="primary"
-            disabled={transactions.length === 0}
+            disabled={
+              transactions?.filter(trans => !trans.isMatchedTransaction)
+                .length === 0
+            }
             loading={loadingState === 'importing'}
             onClick={onImport}
           >
-            Import {transactions.length} transactions
+            Import{' '}
+            {transactions?.filter(trans => !trans.isMatchedTransaction).length}{' '}
+            transactions
           </ButtonWithLoading>
         </View>
       </View>
diff --git a/packages/desktop-client/src/icons/v2/DownAndRightArrow.tsx b/packages/desktop-client/src/icons/v2/DownAndRightArrow.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..44f3d82fc7fd4fb2c2953906ee78d1a019ebe9ac
--- /dev/null
+++ b/packages/desktop-client/src/icons/v2/DownAndRightArrow.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import type { SVGProps } from 'react';
+export const SvgDownAndRightArrow = (props: SVGProps<SVGSVGElement>) => (
+  <svg
+    {...props}
+    xmlns="http://www.w3.org/2000/svg"
+    viewBox="0 0 20 20"
+    style={{
+      color: 'inherit',
+      ...props.style,
+    }}
+  >
+    <path
+      d="M4.092 3.658 1.267 6.484l.708.708.708.708 1.609-1.608L5.9 4.684V7.4c.001 2.838.022 3.466.132 3.96.393 1.766 1.732 2.972 3.985 3.59.238.065.53.133.65.151.119.018 1.229.055 2.466.082 1.238.028 2.458.06 2.712.072l.462.021-1.561 1.562-1.562 1.562.708.708.708.708 2.7-2.699 2.7-2.7v-.5l-2.7-2.7-2.7-2.7-.7.7-.7.699 1.675 1.679 1.675 1.678-.617-.019c-.339-.011-1.584-.043-2.766-.071-1.191-.028-2.232-.067-2.334-.087a6.822 6.822 0 0 1-1.283-.426c-.754-.356-1.201-.777-1.447-1.365-.167-.399-.17-.463-.17-3.649V4.684L9.55 6.3l1.617 1.616.708-.708.708-.708L9.75 3.667 6.917.833 4.092 3.658"
+      fill="currentColor"
+    />
+  </svg>
+);
diff --git a/packages/desktop-client/src/icons/v2/down-and-right-arrow.svg b/packages/desktop-client/src/icons/v2/down-and-right-arrow.svg
new file mode 100644
index 0000000000000000000000000000000000000000..100aa0668380bcb4408500f1c36df4b4b196bd55
--- /dev/null
+++ b/packages/desktop-client/src/icons/v2/down-and-right-arrow.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M4.092 3.658 1.267 6.484l.708.708.708.708 1.609-1.608L5.9 4.684V7.4c.001 2.838.022 3.466.132 3.96.393 1.766 1.732 2.972 3.985 3.59.238.065.53.133.65.151.119.018 1.229.055 2.466.082 1.238.028 2.458.06 2.712.072l.462.021-1.561 1.562-1.562 1.562.708.708.708.708 2.7-2.699 2.7-2.7v-.5l-2.7-2.7-2.7-2.7-.7.7-.7.699 1.675 1.679 1.675 1.678-.617-.019c-.339-.011-1.584-.043-2.766-.071-1.191-.028-2.232-.067-2.334-.087a6.822 6.822 0 0 1-1.283-.426c-.754-.356-1.201-.777-1.447-1.365-.167-.399-.17-.463-.17-3.649V4.684L9.55 6.3l1.617 1.616.708-.708.708-.708L9.75 3.667 6.917.833 4.092 3.658" /></svg>
\ No newline at end of file
diff --git a/packages/desktop-client/src/icons/v2/index.ts b/packages/desktop-client/src/icons/v2/index.ts
index 82e3d55308797cbd5fdf58af490559cc8e23ebfd..23af708a141c198749ae606e31dc8ecdc0d59f6b 100644
--- a/packages/desktop-client/src/icons/v2/index.ts
+++ b/packages/desktop-client/src/icons/v2/index.ts
@@ -16,6 +16,7 @@ export { SvgCheck } from './Check';
 export { SvgCloudUnknown } from './CloudUnknown';
 export { SvgCloudUpload } from './CloudUpload';
 export { SvgCustomNotesPaper } from './CustomNotesPaper';
+export { SvgDownAndRightArrow } from './DownAndRightArrow';
 export { SvgDownloadThickBottom } from './DownloadThickBottom';
 export { SvgEditSkull1 } from './EditSkull1';
 export { SvgFavoriteStar } from './FavoriteStar';
diff --git a/packages/loot-core/src/client/actions/account.ts b/packages/loot-core/src/client/actions/account.ts
index 120d20b9df4c3527da20433d207c9b7a235a21a4..f2c80730d2678a1eae0ec678ca84f073f86da2b3 100644
--- a/packages/loot-core/src/client/actions/account.ts
+++ b/packages/loot-core/src/client/actions/account.ts
@@ -185,6 +185,27 @@ export function parseTransactions(filepath, options) {
   };
 }
 
+export function importPreviewTransactions(id: string, transactions) {
+  return async (dispatch: Dispatch): Promise<boolean> => {
+    const { errors = [], updatedPreview } = await send('transactions-import', {
+      accountId: id,
+      transactions,
+      isPreview: true,
+    });
+
+    errors.forEach(error => {
+      dispatch(
+        addNotification({
+          type: 'error',
+          message: error.message,
+        }),
+      );
+    });
+
+    return updatedPreview;
+  };
+}
+
 export function importTransactions(id: string, transactions, reconcile = true) {
   return async (dispatch: Dispatch): Promise<boolean> => {
     if (!reconcile) {
@@ -203,6 +224,7 @@ export function importTransactions(id: string, transactions, reconcile = true) {
     } = await send('transactions-import', {
       accountId: id,
       transactions,
+      isPreview: false,
     });
 
     errors.forEach(error => {
diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts
index 618c48918e211d1bc334fac49e03009e9bb29dea..51300ba1ddce400ff38ddd02828a5e9f624c06c5 100644
--- a/packages/loot-core/src/server/accounts/sync.ts
+++ b/packages/loot-core/src/server/accounts/sync.ts
@@ -8,7 +8,11 @@ import {
   makeChild as makeChildTransaction,
   recalculateSplit,
 } from '../../shared/transactions';
-import { hasFieldsChanged, amountToInteger } from '../../shared/util';
+import {
+  hasFieldsChanged,
+  amountToInteger,
+  integerToAmount,
+} from '../../shared/util';
 import * as db from '../db';
 import { runMutator } from '../mutators';
 import { post } from '../post';
@@ -349,12 +353,117 @@ export async function reconcileTransactions(
   acctId,
   transactions,
   isBankSyncAccount = false,
+  isPreview = false,
 ) {
   console.log('Performing transaction reconciliation');
 
-  const hasMatched = new Set();
   const updated = [];
   const added = [];
+  const updatedPreview = [];
+  const existingPayeeMap = new Map<string, string>();
+
+  const {
+    payeesToCreate,
+    transactionsStep1,
+    transactionsStep2,
+    transactionsStep3,
+  } = await matchTransactions(acctId, transactions, isBankSyncAccount);
+
+  // Finally, generate & commit the changes
+  for (const { trans, subtransactions, match } of transactionsStep3) {
+    if (match && !trans.forceAddTransaction) {
+      // Skip updating already reconciled (locked) transactions
+      if (match.reconciled) {
+        updatedPreview.push({ transaction: trans, ignored: true });
+        continue;
+      }
+
+      // TODO: change the above sql query to use aql
+      const existing = {
+        ...match,
+        cleared: match.cleared === 1,
+        date: db.fromDateRepr(match.date),
+      };
+
+      // Update the transaction
+      const updates = {
+        imported_id: trans.imported_id || null,
+        payee: existing.payee || trans.payee || null,
+        category: existing.category || trans.category || null,
+        imported_payee: trans.imported_payee || null,
+        notes: existing.notes || trans.notes || null,
+        cleared: trans.cleared != null ? trans.cleared : true,
+      };
+
+      if (hasFieldsChanged(existing, updates, Object.keys(updates))) {
+        updated.push({ id: existing.id, ...updates });
+        if (!existingPayeeMap.has(existing.payee)) {
+          const payee = await db.getPayee(existing.payee);
+          existingPayeeMap.set(existing.payee, payee?.name);
+        }
+        existing.payee_name = existingPayeeMap.get(existing.payee);
+        existing.amount = integerToAmount(existing.amount);
+        updatedPreview.push({ transaction: trans, existing });
+      } else {
+        updatedPreview.push({ transaction: trans, ignored: true });
+      }
+
+      if (existing.is_parent && existing.cleared !== updates.cleared) {
+        const children = await db.all(
+          'SELECT id FROM v_transactions WHERE parent_id = ?',
+          [existing.id],
+        );
+        for (const child of children) {
+          updated.push({ id: child.id, cleared: updates.cleared });
+        }
+      }
+    } else {
+      // Insert a new transaction
+      const { forceAddTransaction, ...newTrans } = trans;
+      const finalTransaction = {
+        ...newTrans,
+        id: uuidv4(),
+        category: trans.category || null,
+        cleared: trans.cleared != null ? trans.cleared : true,
+      };
+
+      if (subtransactions && subtransactions.length > 0) {
+        added.push(...makeSplitTransaction(finalTransaction, subtransactions));
+      } else {
+        added.push(finalTransaction);
+      }
+    }
+  }
+
+  if (!isPreview) {
+    await createNewPayees(payeesToCreate, [...added, ...updated]);
+    await batchUpdateTransactions({ added, updated });
+  }
+
+  console.log('Debug data for the operations:', {
+    transactionsStep1,
+    transactionsStep2,
+    transactionsStep3,
+    added,
+    updated,
+    updatedPreview,
+  });
+
+  return {
+    added: added.map(trans => trans.id),
+    updated: updated.map(trans => trans.id),
+    updatedPreview,
+  };
+}
+
+export async function matchTransactions(
+  acctId,
+  transactions,
+  isBankSyncAccount = false,
+) {
+  console.log('Performing transaction reconciliation matching');
+
+  const hasMatched = new Set();
 
   const transactionNormalization = isBankSyncAccount
     ? normalizeBankSyncTransactions
@@ -399,7 +508,7 @@ export async function reconcileTransactions(
       // matched transaction. See the final pass below for the needed
       // fields.
       fuzzyDataset = await db.all(
-        `SELECT id, is_parent, date, imported_id, payee, category, notes, reconciled FROM v_transactions
+        `SELECT id, is_parent, date, imported_id, payee, imported_payee, category, notes, reconciled, cleared, amount FROM v_transactions
            WHERE date >= ? AND date <= ? AND amount = ? AND account = ?`,
         [
           db.toDateRepr(monthUtils.subDays(trans.date, 7)),
@@ -474,75 +583,11 @@ export async function reconcileTransactions(
     return data;
   });
 
-  // Finally, generate & commit the changes
-  for (const { trans, subtransactions, match } of transactionsStep3) {
-    if (match) {
-      // Skip updating already reconciled (locked) transactions
-      if (match.reconciled) {
-        continue;
-      }
-
-      // TODO: change the above sql query to use aql
-      const existing = {
-        ...match,
-        cleared: match.cleared === 1,
-        date: db.fromDateRepr(match.date),
-      };
-
-      // Update the transaction
-      const updates = {
-        imported_id: trans.imported_id || null,
-        payee: existing.payee || trans.payee || null,
-        category: existing.category || trans.category || null,
-        imported_payee: trans.imported_payee || null,
-        notes: existing.notes || trans.notes || null,
-        cleared: trans.cleared != null ? trans.cleared : true,
-      };
-
-      if (hasFieldsChanged(existing, updates, Object.keys(updates))) {
-        updated.push({ id: existing.id, ...updates });
-      }
-
-      if (existing.is_parent && existing.cleared !== updates.cleared) {
-        const children = await db.all(
-          'SELECT id FROM v_transactions WHERE parent_id = ?',
-          [existing.id],
-        );
-        for (const child of children) {
-          updated.push({ id: child.id, cleared: updates.cleared });
-        }
-      }
-    } else {
-      // Insert a new transaction
-      const finalTransaction = {
-        ...trans,
-        id: uuidv4(),
-        category: trans.category || null,
-        cleared: trans.cleared != null ? trans.cleared : true,
-      };
-
-      if (subtransactions && subtransactions.length > 0) {
-        added.push(...makeSplitTransaction(finalTransaction, subtransactions));
-      } else {
-        added.push(finalTransaction);
-      }
-    }
-  }
-
-  await createNewPayees(payeesToCreate, [...added, ...updated]);
-  await batchUpdateTransactions({ added, updated });
-
-  console.log('Debug data for the operations:', {
+  return {
+    payeesToCreate,
     transactionsStep1,
     transactionsStep2,
     transactionsStep3,
-    added,
-    updated,
-  });
-
-  return {
-    added: added.map(trans => trans.id),
-    updated: updated.map(trans => trans.id),
   };
 }
 
diff --git a/packages/loot-core/src/server/api.ts b/packages/loot-core/src/server/api.ts
index b49ef58f18893ad369c33e7528e3761134ad9501..d50cf08e61c66cbe92cafbd7eff4d0f3e19747c8 100644
--- a/packages/loot-core/src/server/api.ts
+++ b/packages/loot-core/src/server/api.ts
@@ -411,9 +411,14 @@ handlers['api/transactions-export'] = async function ({
 handlers['api/transactions-import'] = withMutation(async function ({
   accountId,
   transactions,
+  isPreview = false,
 }) {
   checkFileOpen();
-  return handlers['transactions-import']({ accountId, transactions });
+  return handlers['transactions-import']({
+    accountId,
+    transactions,
+    isPreview,
+  });
 });
 
 handlers['api/transactions-add'] = withMutation(async function ({
diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts
index c137cc123a1a07cb89df023048eb798e84a17e31..c85fb311699e8295f2aa09b6247c67b3d15ab2bf 100644
--- a/packages/loot-core/src/server/main.ts
+++ b/packages/loot-core/src/server/main.ts
@@ -1131,6 +1131,7 @@ handlers['accounts-bank-sync'] = async function ({ id }) {
 handlers['transactions-import'] = mutator(function ({
   accountId,
   transactions,
+  isPreview,
 }) {
   return withUndo(async () => {
     if (typeof accountId !== 'string') {
@@ -1138,10 +1139,20 @@ handlers['transactions-import'] = mutator(function ({
     }
 
     try {
-      return await bankSync.reconcileTransactions(accountId, transactions);
+      return await bankSync.reconcileTransactions(
+        accountId,
+        transactions,
+        false,
+        isPreview,
+      );
     } catch (err) {
       if (err instanceof TransactionError) {
-        return { errors: [{ message: err.message }], added: [], updated: [] };
+        return {
+          errors: [{ message: err.message }],
+          added: [],
+          updated: [],
+          updatedPreview: [],
+        };
       }
 
       throw err;
diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts
index 50736f01325d8b5e4a9f2cb4d2002562f81fc34f..9e3b91fb60fe9b4836ad592197255fc2437cab5d 100644
--- a/packages/loot-core/src/types/server-handlers.d.ts
+++ b/packages/loot-core/src/types/server-handlers.d.ts
@@ -222,10 +222,15 @@ export interface ServerHandlers {
     updatedAccounts;
   }>;
 
-  'transactions-import': (arg: { accountId; transactions }) => Promise<{
+  'transactions-import': (arg: {
+    accountId;
+    transactions;
+    isPreview;
+  }) => Promise<{
     errors?: { message: string }[];
     added;
     updated;
+    updatedPreview;
   }>;
 
   'account-unlink': (arg: { id }) => Promise<'ok'>;
diff --git a/upcoming-release-notes/2717.md b/upcoming-release-notes/2717.md
new file mode 100644
index 0000000000000000000000000000000000000000..63aff1f265ec33239aac1e48d229748e1fb8a333
--- /dev/null
+++ b/upcoming-release-notes/2717.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [Wizmaster]
+---
+
+Explicitly ask when reconciling transactions on manual import