Back up every Apple Voice Memo with one command

Resources


Voice Memos can export a recording to Finder, but Apple does not document a command for backing up the complete library with its titles and dates.

Dragging a recording is enough when there are only a few. With dozens or hundreds of memos, a reproducible export becomes more useful.

Why copying the folder is not enough

On macOS 26, Voice Memos data is currently stored in this directory:

~/Library/Group Containers/group.com.apple.VoiceMemos.shared/Recordings/

Audio files generally use an identifier rather than the title displayed by the application. The useful metadata is stored in the CloudRecordings.db SQLite database.

An attempt to access that directory from a terminal may return Operation not permitted or authorization denied. This is not fixed by sudo. macOS protects other applications’ data with TCC, and the terminal needs Full Disk Access.

Temporarily allow the terminal

  1. Open System Settings, then Privacy & Security.
  2. Open Full Disk Access.
  3. Add your terminal, such as Terminal, iTerm, or Ghostty.
  4. Quit the terminal completely with Cmd+Q, then reopen it.

This permission grants broad access. You can remove it after the export.

The script

The following script copies recordings without deleting or modifying the originals. It restores titles when the local schema allows it, preserves dates, and keeps files missing from the database under their technical name.

Save it as export-voice-memos.sh, then run chmod +x export-voice-memos.sh.

#!/usr/bin/env bash
set -uo pipefail

SRC="$HOME/Library/Group Containers/group.com.apple.VoiceMemos.shared/Recordings"
DEST="$HOME/Downloads/VoiceMemos"
TO_MP3=0

say() { printf '%s\n' "$*"; }
die() { printf 'Error: %s\n' "$*" >&2; exit 1; }

while [ $# -gt 0 ]; do
  case "$1" in
    -o|--output)
      [ $# -ge 2 ] || die "missing directory after $1"
      DEST="$2"
      shift 2
      ;;
    --mp3)
      TO_MP3=1
      shift
      ;;
    -h|--help)
      printf 'Usage: %s [-o directory] [--mp3]\n' "$0"
      exit 0
      ;;
    *) die "unknown option: $1" ;;
  esac
done

[ -d "$SRC" ] || die "Voice Memos directory not found"
command -v sqlite3 >/dev/null 2>&1 || die "sqlite3 is required"

if ! find "$SRC" -maxdepth 1 -type f -print -quit >/dev/null 2>&1; then
  die "macOS denied access. Grant Full Disk Access to the terminal, then restart it"
fi

mkdir -p "$DEST"
work_dir=$(mktemp -d "${TMPDIR:-/tmp}/voice-memos-export.XXXXXX") || die "cannot create temporary directory"
manifest="$work_dir/exported-source-files"
: > "$manifest"

cleanup() {
  rm -f "$manifest"
  rmdir "$work_dir" 2>/dev/null || true
}
trap cleanup EXIT

slug() {
  printf '%s' "$1" \
    | iconv -f UTF-8 -t ASCII//TRANSLIT 2>/dev/null \
    | sed 's#[/\\:]#-#g' \
    | tr '\n\t' '--' \
    | sed -E 's/[^A-Za-z0-9 ._-]//g; s/^ +| +$//g; s/ /-/g'
}

copy_named() {
  source_file="$1"
  target_base="$2"
  target="$DEST/${target_base}.m4a"
  suffix=2

  while [ -e "$target" ]; do
    if cmp -s "$source_file" "$target"; then
      return 0
    fi
    target="$DEST/${target_base}_${suffix}.m4a"
    suffix=$((suffix + 1))
  done

  cp "$source_file" "$target" || return 1
  [ -n "${stamp:-}" ] && touch -t "$stamp" "$target" 2>/dev/null
  say "Copied: $(basename "$target")"
}

DB="$SRC/CloudRecordings.db"
title_col=""

if [ -f "$DB" ]; then
  for column in ZCUSTOMLABEL ZENCRYPTEDTITLE ZTITLE ZNAME; do
    if sqlite3 "$DB" "SELECT $column FROM ZCLOUDRECORDING LIMIT 1;" >/dev/null 2>&1; then
      title_col="$column"
      break
    fi
  done
fi

exported=0
fallback=0

if [ -n "$title_col" ]; then
  while IFS=$'\t' read -r label path apple_date; do
    [ -n "$path" ] || continue
    source_name=$(basename "$path")
    source_file="$SRC/$source_name"
    [ -f "$source_file" ] || continue

    day=""
    stamp=""
    case "$apple_date" in
      ''|*[!0-9.]*) ;;
      *)
        unix_date=$(awk "BEGIN { printf \"%d\", $apple_date + 978307200 }")
        day=$(date -r "$unix_date" +%Y-%m-%d 2>/dev/null || true)
        stamp=$(date -r "$unix_date" +%Y%m%d%H%M.%S 2>/dev/null || true)
        ;;
    esac

    name=$(slug "${label:-memo}")
    [ -n "$name" ] || name="memo"
    [ -n "$day" ] && name="${day}_${name}"

    if copy_named "$source_file" "$name"; then
      printf '%s\n' "$source_name" >> "$manifest"
      exported=$((exported + 1))
    fi
  done < <(
    sqlite3 -separator $'\t' "$DB" \
      "SELECT COALESCE($title_col,''), ZPATH, ZDATE
       FROM ZCLOUDRECORDING
       WHERE ZPATH IS NOT NULL;" 2>/dev/null
  )
fi

while IFS= read -r -d '' source_file; do
  source_name=$(basename "$source_file")
  if ! grep -Fqx "$source_name" "$manifest"; then
    cp -np "$source_file" "$DEST/$source_name" && fallback=$((fallback + 1))
  fi
done < <(
  find "$SRC" -maxdepth 1 -type f \
    \( -iname '*.m4a' -o -iname '*.caf' -o -iname '*.wav' \) -print0
)

if [ "$TO_MP3" -eq 1 ]; then
  command -v ffmpeg >/dev/null 2>&1 || die "ffmpeg is required for --mp3"
  for source_file in "$DEST"/*.m4a; do
    [ -e "$source_file" ] || continue
    ffmpeg -nostdin -loglevel error -y -i "$source_file" \
      -codec:a libmp3lame -q:a 2 "${source_file%.m4a}.mp3"
  done
fi

say "Done: $exported named file(s), $fallback fallback file(s)"
say "Directory: $DEST"

Run the export

./export-voice-memos.sh
./export-voice-memos.sh -o "$HOME/Documents/VoiceMemos"
./export-voice-memos.sh --mp3

The resulting names follow the pattern 2026-08-31_Memo-title.m4a. If two recordings produce the same name but have different contents, a numeric suffix prevents overwriting.

The MP3 option requires ffmpeg, which can be installed with brew install ffmpeg. The original M4A files also remain in the export directory.

Limitations

The path and SQLite schema are internal macOS implementation details. Apple may change them in an update. The script looks for several known title columns and keeps every unmatched audio file as a fallback, but you should inspect the result before treating it as a backup.

Some recordings synchronised through iCloud may not be downloaded locally yet. Open Voice Memos and allow synchronisation to finish before exporting.

Finally, a copy on the same Mac does not protect against device failure or theft. After checking the result, copy the directory to another disk or your regular backup system.