diff --git a/packages/desktop-client/src/components/schedules/LinkSchedule.js b/packages/desktop-client/src/components/schedules/LinkSchedule.js
index ddced09a641a703d47b7a18bfc34c98795f91c66..d7289298ade2983a7c3a7d2695972f57d3dd5c0d 100644
--- a/packages/desktop-client/src/components/schedules/LinkSchedule.js
+++ b/packages/desktop-client/src/components/schedules/LinkSchedule.js
@@ -1,9 +1,9 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useState } from 'react';
 import { useLocation, useHistory } from 'react-router-dom';
 
 import { useSchedules } from 'loot-core/src/client/data-hooks/schedules';
 import { send } from 'loot-core/src/platform/client/fetch';
-import { Text } from 'loot-design/src/components/common';
+import { Search, Text, View } from 'loot-design/src/components/common';
 
 import { Page } from '../Page';
 
@@ -16,6 +16,8 @@ export default function ScheduleLink() {
     useCallback(query => query.filter({ completed: false }), [])
   );
 
+  let [filter, setFilter] = useState('');
+
   if (scheduleData == null) {
     return null;
   }
@@ -35,15 +37,27 @@ export default function ScheduleLink() {
 
   return (
     <Page title="Link Schedule" modalSize="medium">
-      <Text style={{ marginBottom: 20 }}>
-        Choose a schedule to link these transactions to:
-      </Text>
+      <View
+        style={{ flexDirection: 'row', marginBottom: 20, alignItems: 'center' }}
+      >
+        <Text>Choose a schedule to link these transactions to:</Text>
+        <View style={{ flex: 1 }} />
+        <Search
+          isInModal
+          width={300}
+          placeholder="Filter schedules…"
+          value={filter}
+          onChange={setFilter}
+        />
+      </View>
 
       <SchedulesTable
         schedules={schedules}
+        filter={filter}
         statuses={statuses}
         minimal={true}
         onSelect={onSelect}
+        tableStyle={{ marginInline: -20 }}
       />
     </Page>
   );
diff --git a/packages/desktop-client/src/components/schedules/SchedulesTable.js b/packages/desktop-client/src/components/schedules/SchedulesTable.js
index 2986ad49b182a18638fb7a865539702acc95dac0..878c32f7132c603e806687ad0e4b2c5fe75a0e2a 100644
--- a/packages/desktop-client/src/components/schedules/SchedulesTable.js
+++ b/packages/desktop-client/src/components/schedules/SchedulesTable.js
@@ -1,6 +1,8 @@
 import React, { useState, useMemo } from 'react';
 import { useSelector } from 'react-redux';
 
+import { useCachedAccounts } from 'loot-core/src/client/data-hooks/accounts';
+import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees';
 import * as monthUtils from 'loot-core/src/shared/months';
 import { getScheduledAmount } from 'loot-core/src/shared/schedules';
 import { integerToCurrency } from 'loot-core/src/shared/util';
@@ -126,11 +128,13 @@ export function ScheduleAmountCell({ amount, op }) {
 export function SchedulesTable({
   schedules,
   statuses,
+  filter,
   minimal,
   allowCompleted,
   style,
   onSelect,
-  onAction
+  onAction,
+  tableStyle
 }) {
   let dateFormat = useSelector(state => {
     return state.prefs.local.dateFormat || 'MM/dd/yyyy';
@@ -138,19 +142,56 @@ export function SchedulesTable({
 
   let [showCompleted, setShowCompleted] = useState(false);
 
+  let payees = useCachedPayees();
+  let accounts = useCachedAccounts();
+
+  let filteredSchedules = useMemo(() => {
+    if (!filter) {
+      return schedules;
+    }
+    const filterIncludes = str =>
+      str
+        ? str.toLowerCase().includes(filter.toLowerCase()) ||
+          filter.toLowerCase().includes(str.toLowerCase())
+        : false;
+
+    return schedules.filter(schedule => {
+      let payee = payees.find(p => schedule._payee === p.id);
+      let account = accounts.find(a => schedule._account === a.id);
+      let amount = getScheduledAmount(schedule._amount);
+      let amountStr =
+        (schedule._amountOp === 'isapprox' || schedule._amountOp === 'isbetween'
+          ? '~'
+          : '') +
+        (amount > 0 ? '+' : '') +
+        integerToCurrency(Math.abs(amount || 0));
+      let dateStr = schedule.next_date
+        ? monthUtils.format(schedule.next_date, dateFormat)
+        : null;
+
+      return (
+        filterIncludes(payee && payee.name) ||
+        filterIncludes(account && account.name) ||
+        filterIncludes(amountStr) ||
+        filterIncludes(statuses.get(schedule.id)) ||
+        filterIncludes(dateStr)
+      );
+    });
+  }, [schedules, filter, statuses]);
+
   let items = useMemo(() => {
     if (!allowCompleted) {
-      return schedules.filter(s => !s.completed);
+      return filteredSchedules.filter(s => !s.completed);
     }
     if (showCompleted) {
-      return schedules;
+      return filteredSchedules;
     }
-    let arr = schedules.filter(s => !s.completed);
-    if (schedules.find(s => s.completed)) {
+    let arr = filteredSchedules.filter(s => !s.completed);
+    if (filteredSchedules.find(s => s.completed)) {
       arr.push({ type: 'show-completed' });
     }
     return arr;
-  }, [schedules, showCompleted, allowCompleted]);
+  }, [filteredSchedules, showCompleted, allowCompleted]);
 
   function renderSchedule({ item }) {
     return (
@@ -231,7 +272,7 @@ export function SchedulesTable({
   }
 
   return (
-    <>
+    <View style={[{ flex: 1 }, tableStyle]}>
       <TableHeader height={ROW_HEIGHT} inset={15} version="v2">
         <Field width="flex">Payee</Field>
         <Field width="flex">Account</Field>
@@ -254,9 +295,9 @@ export function SchedulesTable({
         style={[{ flex: 1, backgroundColor: 'transparent' }, style]}
         items={items}
         renderItem={renderItem}
-        renderEmpty="No schedules"
+        renderEmpty={filter ? 'No matching schedules' : 'No schedules'}
         allowPopupsEscape={items.length < 6}
       />
-    </>
+    </View>
   );
 }
diff --git a/packages/desktop-client/src/components/schedules/index.js b/packages/desktop-client/src/components/schedules/index.js
index 08822c76eb9e58a1f63b0e1e63bc1304e9266cee..a945113c5083d2c7795fab0e23fabda0b194a053 100644
--- a/packages/desktop-client/src/components/schedules/index.js
+++ b/packages/desktop-client/src/components/schedules/index.js
@@ -1,9 +1,9 @@
-import React from 'react';
+import React, { useState } from 'react';
 import { useHistory } from 'react-router-dom';
 
 import { useSchedules } from 'loot-core/src/client/data-hooks/schedules';
 import { send } from 'loot-core/src/platform/client/fetch';
-import { View, Button } from 'loot-design/src/components/common';
+import { View, Button, Search } from 'loot-design/src/components/common';
 
 import { Page } from '../Page';
 
@@ -12,6 +12,8 @@ import { SchedulesTable, ROW_HEIGHT } from './SchedulesTable';
 export default function Schedules() {
   let history = useHistory();
 
+  let [filter, setFilter] = useState('');
+
   let scheduleData = useSchedules();
 
   if (scheduleData == null) {
@@ -58,6 +60,14 @@ export default function Schedules() {
 
   return (
     <Page title="Schedules">
+      <View style={{ alignItems: 'flex-end' }}>
+        <Search
+          placeholder="Filter schedules…"
+          value={filter}
+          onChange={setFilter}
+        />
+      </View>
+
       <View
         style={{
           marginTop: 20,
@@ -67,6 +77,7 @@ export default function Schedules() {
       >
         <SchedulesTable
           schedules={schedules}
+          filter={filter}
           statuses={statuses}
           allowCompleted={true}
           onSelect={onEdit}
diff --git a/packages/loot-design/src/components/common.js b/packages/loot-design/src/components/common.js
index c22fbc8111d83922d0e3d6e5ca6bd50ca8818680..1cf92084e43099ca6debef94c1f230de73370746 100644
--- a/packages/loot-design/src/components/common.js
+++ b/packages/loot-design/src/components/common.js
@@ -422,6 +422,35 @@ export function InputWithContent({
   );
 }
 
+export function Search({
+  inputRef,
+  value,
+  onChange,
+  placeholder,
+  isInModal,
+  width = 350
+}) {
+  return (
+    <Input
+      inputRef={inputRef}
+      placeholder={placeholder}
+      value={value}
+      onChange={e => onChange(e.target.value)}
+      style={{
+        width,
+        borderColor: isInModal ? null : 'transparent',
+        backgroundColor: isInModal ? null : colors.n11,
+        ':focus': isInModal
+          ? null
+          : {
+              backgroundColor: 'white',
+              '::placeholder': { color: colors.n8 }
+            }
+      }}
+    />
+  );
+}
+
 export function KeyboardButton({ highlighted, children, ...props }) {
   return (
     <Button