Search Notes using Alfred

Download →Alfred workflow

Features

  • Search across all NotePlan notes by title and content
  • Filter searches by note type (calendar vs. regular notes)
  • Support for both SetApp and regular NotePlan installations
  • Display notes with frontmatter titles
  • Show additional metadata (note type, modification date)
  • Copy note content to clipboard (Alt modifier)
  • Reveal notes in Finder (Cmd modifier)
  • Open notes directly in NotePlan with title-based lookups

Installation

  1. Download the workflow file
  2. Double-click to install in Alfred
  3. Ensure Alfred has the Powerpack activated

Usage

Basic Search

Type np followed by your search term:

np meeting notes

This will search all your NotePlan notes (both regular and calendar notes) for "meeting notes".

Filtered Search

Filter your search by note type using prefixes:

  • np a [query] - Search all notes (same as np [query])
  • np c [query] - Search only calendar notes
  • np r [query] - Search only regular notes

Examples:

np a project ideas
np c meeting
np r journal saturday

Actions

  • Enter: Open the selected note in NotePlan
  • Alt+Enter: Copy the note content to clipboard
  • Cmd+Enter: Reveal the note in Finder

How It Works

This workflow:

  1. Detects which NotePlan installation you have (SetApp or regular)
  2. Uses macOS Spotlight (mdfind) to search your notes quickly
  3. Extracts frontmatter titles and metadata
  4. Formats results for display in Alfred
  5. Opens selected notes using NotePlan's URL scheme

Technical Details

Supported NotePlan Paths

The workflow checks for NotePlan data in:

  • SetApp: ~/Library/Containers/co.noteplan.NotePlan-setapp/Data/Library/Application Support/co.noteplan.NotePlan-setapp/
  • Regular: ~/Library/Containers/co.noteplan.NotePlan/Data/Library/Application Support/co.noteplan.NotePlan/

Script Filter

#!/bin/bash

# Parse the input
full_input="$1"
filter_prefix=""
query=""

# Extract the prefix and query
if [[ "$full_input" == "a "* ]]; then
  filter_prefix="a"
  query="${full_input:2}"
elif [[ "$full_input" == "c "* ]]; then
  filter_prefix="c"
  query="${full_input:2}"
elif [[ "$full_input" == "r "* ]]; then
  filter_prefix="r"
  query="${full_input:2}"
else
  # No prefix, search all
  query="$full_input"
fi

# Define both possible NotePlan directories
setapp_notes_dir=~/Library/Containers/co.noteplan.NotePlan-setapp/Data/Library/Application\ Support/co.noteplan.NotePlan-setapp/Notes/
setapp_calendar_dir=~/Library/Containers/co.noteplan.NotePlan-setapp/Data/Library/Application\ Support/co.noteplan.NotePlan-setapp/Calendar/

regular_notes_dir=~/Library/Containers/co.noteplan.NotePlan/Data/Library/Application\ Support/co.noteplan.NotePlan/Notes/
regular_calendar_dir=~/Library/Containers/co.noteplan.NotePlan/Data/Library/Application\ Support/co.noteplan.NotePlan/Calendar/

# Check which version exists and set the appropriate directories
if [ -d "$setapp_notes_dir" ]; then
  notes_dir="$setapp_notes_dir"
  calendar_dir="$setapp_calendar_dir"
  app_name="NotePlan.app"
elif [ -d "$regular_notes_dir" ]; then
  notes_dir="$regular_notes_dir"
  calendar_dir="$regular_calendar_dir"
  app_name="NotePlan.app"
else
  # No valid NotePlan directories found
  echo '{"items":[{"uid":"error","title":"NotePlan directories not found","subtitle":"Please check your NotePlan installation","valid":false}]}'
  exit 0
fi

# Start Alfred JSON output
echo '{"items":['

# Use mdfind for searching based on the filter
if [ "$filter_prefix" = "c" ]; then
  # Calendar notes only
  files=$(mdfind -onlyin "$calendar_dir" "$query" | head -15)
elif [ "$filter_prefix" = "r" ]; then
  # Regular notes only
  files=$(mdfind -onlyin "$notes_dir" "$query" | grep -v "@Archive" | grep -v "@Templates" | grep -v "@Theme" | grep -v "@Trash" | head -15)
else
  # All notes (default)
  files=$(mdfind -onlyin "$notes_dir" -onlyin "$calendar_dir" "$query" | grep -v "@Archive" | grep -v "@Templates" | grep -v "@Theme" | grep -v "@Trash" | head -15)
fi

# Process each file
first=true
while IFS= read -r file; do
  # Skip empty lines
  [ -z "$file" ] && continue

  # Add comma for all but first item
  if [ "$first" = true ]; then
    first=false
  else
    echo ','
  fi

  # Get filename without extension for fallback title
  filename=$(basename "$file")
  fallback_title="${filename%.*}"

  # Extract title from frontmatter if available
  frontmatter_title=$(grep -m 1 "title:" "$file" | sed 's/title: *//' | tr -d '"' | tr -d "'")

  # Use frontmatter title if available, otherwise use filename
  if [ -n "$frontmatter_title" ]; then
    title="$frontmatter_title"
  else
    title="$fallback_title"
  fi

  # Get note_type from frontmatter if available
  note_type=$(grep -m 1 "type:" "$file" | sed 's/type: *//' | tr -d '"' | tr -d "'")

  # Get modification date for the file
  mod_date=$(stat -f "%Sm" -t "%Y-%m-%d" "$file")

  # Get note type (regular note or calendar note)
  if [[ "$file" == *"/Calendar/"* ]]; then
    base_type="📅 Calendar Note"
  else
    base_type="📝 Note"
  fi

  # Build subtitle with metadata
  if [ -n "$note_type" ]; then
    subtitle="${base_type} | Type: ${note_type} | Modified: ${mod_date}"
  else
    subtitle="${base_type} | Modified: ${mod_date}"
  fi

  # Create Alfred item with proper JSON escaping
  title_escaped=$(echo "$title" | sed 's/"/\\"/g')
  subtitle_escaped=$(echo "$subtitle" | sed 's/"/\\"/g')
  file_escaped=$(echo "$file" | sed 's/"/\\"/g')

  cat << EOF
  {
    "uid": "np-$(echo "$file" | md5)",
    "title": "${title_escaped}",
    "subtitle": "${subtitle_escaped}",
    "arg": "${file_escaped}",
    "mods": {
      "alt": {
        "arg": "copy:${file_escaped}",
        "subtitle": "Copy note content to clipboard"
      },
      "cmd": {
        "arg": "reveal:${file_escaped}",
        "subtitle": "Reveal in Finder"
      }
    }
  }
EOF
done <<< "$files"

# If no results were found, provide a fallback item
if [ "$first" = true ]; then
  # Customize the message based on the filter
  if [ "$filter_prefix" = "c" ]; then
    filter_message="No matching calendar notes found"
  elif [ "$filter_prefix" = "r" ]; then
    filter_message="No matching regular notes found"
  else
    filter_message="No matching notes found"
  fi

  echo '{
    "uid": "no-results",
    "title": "'"$filter_message"'",
    "subtitle": "Try a different search term",
    "valid": false
  }'
fi

# End JSON output
echo ']}'

Handler Script

#!/bin/bash

input="$1"

# Handle different action types
if [[ "$input" == copy:* ]]; then
  # Copy note content to clipboard
  file="${input#copy:}"
  cat "$file" | pbcopy
  echo "Note content copied to clipboard"

elif [[ "$input" == reveal:* ]]; then
  # Reveal in Finder
  file="${input#reveal:}"
  open -R "$file"

else
  # Extract the actual title from the frontmatter
  title=$(grep -m 1 "title:" "$input" | sed 's/title: *//' | tr -d '"' | tr -d "'")

  # If no title in frontmatter, use filename as backup
  if [ -z "$title" ]; then
    filename=$(basename "$input")
    title="${filename%.*}"
  fi

  # URL encode the title
  encoded_title=$(echo "$title" | sed -e 's/ /%20/g' -e 's/&/%26/g' -e 's/#/%23/g' -e 's/\?/%3F/g' -e 's/=/%3D/g' -e 's/\+/%2B/g')

  # Use noteTitle parameter to open the note in NotePlan
  open "noteplan://x-callback-url/openNote?noteTitle=$encoded_title"

  # If that doesn't work, try directly opening the file as fallback
  sleep 0.5
  open -a "NotePlan.app" "$input"
fi

Workflow Setup in Alfred

  1. Create a Script Filter:

    • Keyword: np
    • Check "with space"
    • Set "Argument Required"
    • Language: /bin/bash
    • Script: [Script Filter code above]
  2. Create a Run Script Action:

    • Language: /bin/bash
    • Script: [Handler Script code above]
  3. Connect the Script Filter to the Run Script action.

Troubleshooting

  • No results found: Verify that your NotePlan notes directory is in one of the supported locations
  • NotePlan doesn't open: Make sure NotePlan is installed and try reinstalling the workflow
  • Search is slow: The workflow uses Spotlight index; make sure Spotlight indexing is enabled

Customization

You can modify the script to:

  • Change the excluded folders (currently @ Archive, @ Templates, @ Theme, @ Trash)
  • Adjust the number of results shown (currently limited to 15)
  • Change the metadata displayed in results
  • Add additional actions with more modifiers

Version History

  • 1.0: Initial release with basic search functionality
  • 1.1: Added support for frontmatter titles
  • 1.2: Added note type filtering and metadata display
  • 1.3: Added support for both SetApp and regular NotePlan installations