Solution to backup Reminders.app (semi-solution)

Backing up Reminders (or lack thereof) is what probably makes users find alternatives. I’d hope that in 2025 Apple would be competing with other companies with such basic features, but… well, it’s Apple.

Today, I spent a few hours going back and forth with ChatGPT and Claude, because I’m not a developer myself, but I know enough to ask the right questions, and I came up with this final semi-solution. Why “semi-solution”?

1 - This doesn’t export all the information in Reminders (if someone who knows how to achieve that, please share)
2 - This is just a “safety net” so we don’t completely lose everything in Reminders. It’s not a “restore” solution, but considering that losing all reminders is something that is probably a 1 in a million chance, this is just there to guarantee that we don’t lose everything!

SUMMARY

  • This was done on Catalina, so if something doesn’t work for you in other macOS versions, let me know.

  • This uses AppleScript, which can run inside Script Editor (or Script Debugger) and/or can be converted into an app (permissions are requested when it runs the first time - instructions below).

  • The final file will be a CSV file with the current date and time (images below).

  • Completed tasks can be included or excluded by default on every run, or a dialog can be shown on every run (more about this below)

LIMITATIONS:

  • List Groups are not included
  • URLs are not included
  • Recurring tasks are not shown properly, for example if a task is set to be every 3rd and 17th or the month, that information isn’t shown, just when the task is next due
  • Attachments are not included
  • Maybe other things are not included, but at the moment these are the ones I can think of.

User Setup

At the very top of the script, there are 3 sections to define:

1 - The path where the final CSV file will be saved (short or full paths will work)

2 - If a dialog is shown where you can pick whether to include or exclude completed tasks

3 - The default for those completed tasks (include or exclude, regardless of it showing the dialog or not)

I tried my best to document everything the best way possible and asked ChatGPT and Claude to name variables in a way that (hopefully) makes everything simple to understand.

---- USER SETUP ----
-- Export path. It works with both short paths (~/Desktop) and full paths (/Users/yourusernamehere/Desktop)
set export_path to "~/Desktop"

-- Default for completed tasks
---- Set to "true" if you want the dialog to show where you can pick if you want to include or exclude completed tasks
set completed_tasks_show_dialog to false

---- Set to "true" if you want the "Include completed tasks" option to be selected by default when "completed_tasks_show_dialog" is set to "true". If "completed_tasks_show_dialog" is set to "false", this setting will determine if every export will include or exclude completed tasks
set completed_tasks_default_include to false

Run The Script

You can run the script directly in Script Editor or Script Debugger. If you decide to save this as an app, here are the steps:

1 - Once you have everything set up the way you want it, go to File > Export…
2 - In the next window, change the File Format to Application:

SCR-20251023-lrir

3 - Give it a name (I used Reminders - Backup) and that will give you this in Finder:

SCR-20251023-lsng

Allow Access When Saved As An App

When you first try to run it as an app, you will be presented with dialogs asking for permission. Here they are in order of appearance:

SCR-20251023-lspa

SCR-20251023-lsug

Include or Exclude completed tasks

If you set it up to show the dialog, you will see one of these, depending on what the default for completed tasks is:

SCR-20251023-lpma

SCR-20251023-lozb

The Output

You will get a file that looks like this:

SCR-20251023-lphq

and will have the content similar to this (whether completed tasks are included or excluded):

SCR-20251023-lpfi

You will see a notification at the end:

SCR-20251023-mhzm


Script (copy and paste into Script Editor / Script Debugger):

Click to expand
use AppleScript version "2.4"
use scripting additions

---- USER SETUP ----
-- Export path. It works with both short paths (~/Desktop) and full paths (/Users/yourusernamehere/Desktop)
set export_path to "~/My Files/Inbox Global"

-- Default for completed tasks
---- Set to "true" if you want the dialog to show where you can pick if you want to include or exclude completed tasks
set completed_tasks_show_dialog to false

---- Set to "true" if you want the "Include completed tasks" option to be selected by default when "completed_tasks_show_dialog" is set to "true". If "completed_tasks_show_dialog" is set to "false", this setting will determine if every export will include or exclude completed tasks
set completed_tasks_default_include to false

------------------------------------------------------------

-- Ensure path ends with /
if export_path does not end with "/" then set export_path to export_path & "/"

-- Handler to format date as Thu, Oct 23, 2023 - 11h 51m 23s PM
on formatDate(d)
	set {wd, mo, dayNum, yearNum, hoursNum, minutesNum, secondsNum} to {weekday of d, month of d, day of d, year of d, hours of d, minutes of d, seconds of d}
	
	-- Weekday short name
	set weekdayStr to ""
	if wd = Sunday then set weekdayStr to "Sun"
	if wd = Monday then set weekdayStr to "Mon"
	if wd = Tuesday then set weekdayStr to "Tue"
	if wd = Wednesday then set weekdayStr to "Wed"
	if wd = Thursday then set weekdayStr to "Thu"
	if wd = Friday then set weekdayStr to "Fri"
	if wd = Saturday then set weekdayStr to "Sat"
	
	-- Month short name
	set monthStr to ""
	if mo = January then set monthStr to "Jan"
	if mo = February then set monthStr to "Feb"
	if mo = March then set monthStr to "Mar"
	if mo = April then set monthStr to "Apr"
	if mo = May then set monthStr to "May"
	if mo = June then set monthStr to "Jun"
	if mo = July then set monthStr to "Jul"
	if mo = August then set monthStr to "Aug"
	if mo = September then set monthStr to "Sep"
	if mo = October then set monthStr to "Oct"
	if mo = November then set monthStr to "Nov"
	if mo = December then set monthStr to "Dec"
	
	-- 12-hour format with AM/PM
	set ampm to "AM"
	set hours12 to hoursNum
	if hoursNum = 0 then
		set hours12 to 12
	else if hoursNum ≥ 12 then
		if hoursNum > 12 then set hours12 to hoursNum - 12
		set ampm to "PM"
	end if
	
	-- Leading zeros
	set minutesStr to text -2 thru -1 of ("0" & minutesNum)
	set secondsStr to text -2 thru -1 of ("0" & secondsNum)
	
	return weekdayStr & ", " & monthStr & " " & dayNum & ", " & yearNum & " - " & hours12 & "h " & minutesStr & "m " & secondsStr & "s " & ampm
end formatDate

-- Handler to extract recurring info from notes
on extractRecurring(noteText)
	set recurringInfo to ""
	set cleanedNote to ""
	
	-- Replace different line break types with return
	set noteText to replaceText(noteText, linefeed, return)
	set noteText to replaceText(noteText, ASCII character 13, return)
	
	set AppleScript's text item delimiters to return
	set noteLines to text items of noteText
	set AppleScript's text item delimiters to ""
	
	repeat with currentLine in noteLines
		set lineText to currentLine as text
		set upperLine to do shell script "echo " & quoted form of lineText & " | tr '[:lower:]' '[:upper:]'"
		if upperLine contains "[RECURRING:" then
			set startPos to offset of "[RECURRING:" in upperLine
			set endPos to offset of "]" in upperLine
			if endPos > startPos then
				set recurringInfo to text (startPos + 11) thru (endPos - 1) of lineText
			end if
		else
			if cleanedNote is "" then
				set cleanedNote to lineText
			else
				set cleanedNote to cleanedNote & return & lineText
			end if
		end if
	end repeat
	
	return {cleanedNote, recurringInfo}
end extractRecurring

-- Helper to replace text
on replaceText(theText, oldText, newText)
	set AppleScript's text item delimiters to oldText
	set tempList to text items of theText
	set AppleScript's text item delimiters to newText
	set theResult to tempList as text
	set AppleScript's text item delimiters to ""
	return theResult
end replaceText

-- === Main script ===

-- Handle dialog logic
if completed_tasks_show_dialog then
	if completed_tasks_default_include then
		set defaultSelection to {"Include completed tasks"}
	else
		set defaultSelection to {"Exclude completed tasks"}
	end if
	
	set userChoice to choose from list {"Include completed tasks", "Exclude completed tasks"} with prompt "Select an option:" default items defaultSelection OK button name "Continue" cancel button name "Cancel"
	if userChoice is false then return
	set includeCompleted to (item 1 of userChoice = "Include completed tasks")
else
	set includeCompleted to completed_tasks_default_include
end if

-- Timestamped CSV filename
set csvFile to (do shell script "eval echo " & quoted form of (export_path & "Reminders - " & formatDate(current date) & ".csv"))

-- CSV header
set csvOutput to "LIST,CONTENT,DATE,NOTE,PRIORITY,FLAGGED,RECURRENCE"
if includeCompleted then set csvOutput to csvOutput & ",COMPLETED"
set csvOutput to csvOutput & linefeed

tell application "Reminders"
	repeat with l in lists
		set listName to name of l
		repeat with r in reminders of l
			set taskContent to name of r
			set taskDue to ""
			set taskNotes to ""
			set taskPriority to ""
			set taskCompleted to ""
			set taskFlagged to ""
			set taskRecurring to ""
			
			if due date of r is not missing value then set taskDue to due date of r as text
			
			-- Convert priority values to names
			set priorityValue to priority of r
			if priorityValue is 1 then
				set taskPriority to "High"
			else if priorityValue is 5 then
				set taskPriority to "Medium"
			else if priorityValue is 9 then
				set taskPriority to "Low"
			else
				set taskPriority to ""
			end if
			
			if completion date of r is not missing value then set taskCompleted to completion date of r as text
			if flagged of r is true then set taskFlagged to "Yes"
			
			-- Process notes to extract recurring info
			if body of r is not missing value then
				set noteResult to my extractRecurring(body of r)
				set taskNotes to item 1 of noteResult
				set taskRecurring to item 2 of noteResult
			end if
			
			-- Wrap fields in quotes
			set csvOutput to csvOutput & "\"" & listName & "\","
			set csvOutput to csvOutput & "\"" & taskContent & "\","
			set csvOutput to csvOutput & "\"" & taskDue & "\","
			set csvOutput to csvOutput & "\"" & taskNotes & "\","
			set csvOutput to csvOutput & "\"" & taskPriority & "\","
			set csvOutput to csvOutput & "\"" & taskFlagged & "\","
			set csvOutput to csvOutput & "\"" & taskRecurring & "\""
			
			if includeCompleted then set csvOutput to csvOutput & ",\"" & taskCompleted & "\""
			set csvOutput to csvOutput & linefeed
		end repeat
	end repeat
end tell

-- Save CSV
do shell script "echo " & quoted form of csvOutput & " > " & quoted form of csvFile

-- Show completion notification
display notification "✅ Reminders Backup Completed!" with title "Backup Status"

If you will be using this as an app, I recommend that you also save the script as an editable file in case you want to edit it in the future.


:warning: EDIT (Nov 17, 2025): I noticed that this doesn’t export sub tasks and doesn’t read lists inside Groups. I will eventually see if there’s a way to fix this, because I really want to use this method without flaws, and once it’s fixed (hopefully it will), I will share it here. For now, I avoid using sub tasks or groups.

6 Likes

Made some updates to the original script:

  1. Now the priorities show the names in the app (Low, Medium, High) instead of (9, 5, 1)
  2. Added a new “RECURRENCE” column. This requires a special “code”, but again, for those who want to use Reminders as their main app and don’t want to worry much about losing it, the extra effort is worth it, I guess.

RECURRENCE

In the Note, add a line that has the information about your recurring task. For example my test reminder’s note is:

This is my note
[RECURRING: Weekly on Tuesday and Thursday]
This is a new line https://google.com test
Another line
And another one

image

The rules is that it needs to follow this format: [RECURRING: YourInfoHere]

I’ve made it case-insensitive, so using any of these options will work:

[RECURRING: YourInfoHere]
[Recurring: YourInfoHere]
[recurring: YourInfoHere]
[recuRRinG: YourInfoHere]

And use its own line. Don’t do something like this:

[RECURRING: YourInfoHere] more information here
or
Some information here [RECURRING: YourInfoHere]


I understand that this adds an extra step, but if that extra step brings some peace of mind, why not? Also, are we always creating recurring tasks, or is it something that happens 10% of the time and so adding an extra step isn’t really that bad?

Hope it helps, anyway.

1 Like

Mode write up. Thanks for sharing.