Also absolutely bummed out that this feature was deprecated, I would hope that cursor would have stats on how much this feature was loved, and will then think about getting it back
The export notepads feature worked great when i updated to 2.0
I would like to see notepads return, they were a good place for, erm, notes ![]()
Alternative is using some notepad extension like
which creates a notepad area that is shared between all workspaces.
It’s just one note however, so far from the same feature.
The low tech alternative is to just have Notepad++ open all the time, which is what I went back to doing after trying Cursor’s notepad feature before it was deprecated. I get people’s frustrations, but I agree that Cursor should deprecate features that have outlived their original use (with warnings so people aren’t surprised of course).
In notepads you could reference files like you do in the agent chat.
Curious why this is not included in Commands. Is it because of reusability? If you share a Command, obviously those dependencies won’t be included?
How do people handle when a Command needs some context/file you have in your codebase? Do you manually give it as an “argument” to the Command every time?
Cheers!
Notepad is super useful to me…
Removing it is so far the only update I feel disappointed about cursor!!
![]()
Erm… where the heck are my notepads? I had information stored there I need.
There are some posts above talking about how to recover/export them. Could give it a shot.
Agreed,
It was funny to me, just reading all the features/complexities notepads were meant to cover, whereas for me, I just used them like a notepad ¯\_(ツ)_/¯. It was my place to jot down notes while working; those not worthy of a ticket/issue reply. Mostly I used it for quick to-dos that I didn’t want to forget to come back to. I replaced this need by symlinking a markdown file called .notepad.local.md to all my git worktree branches. (Yes I know we can all use just about any app to take notes, so it is a bit of a meh, but I liked popping it open via keyboard shortcut).
It is a shame to not keep this feature and tailor it better. PyCharm, for example, has Scratch files that could be any type of file and are saved as application data to be shared by all instances. It is a really great, heavily used feature. …I suppose a Jupyter workflow can cover this. I haven’t tried.
I was one of the people who found Cursor’s Notepads feature really useful. When it suddenly disappeared, I built a replacement extension. It’s currently in beta and only supports reading existing notes, but I plan to add writing support in future updates and ultimately deliver something even better than the original Notepads feature.
Why did Notepads exist? Why are we deprecating Notepads?
HEY! DUDE!
Notepads are for “NOTES”
not for RULES or AGENTS
Knock knock?
I want my NOTEPADS back
My AI refined script to get them back, organized per projects and ordered from last modified.
Have a good day.
Cursor Notepad Recovery Script
==============================
This script recovers notepad data from Cursor's workspace storage databases
and organizes them into markdown files.
Features:
- Recovers all notepads from all workspace databases
- Creates raw JSON backup
- Organizes notepads by project/workspace
- Sorts by last modified date
- Works on macOS, Linux, and Windows
Usage:
python3 recover_cursor_notepads.py [output_directory]
If no output directory is specified, files will be saved to the current directory.
"""
import sqlite3
import json
import os
import sys
import urllib.parse
import re
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Tuple, Any
def get_cursor_storage_path() -> Path:
"""Get the Cursor workspace storage path based on the operating system."""
system = sys.platform
if system == "darwin": # macOS
base_path = Path.home() / "Library/Application Support/Cursor/User/workspaceStorage"
elif system == "linux":
base_path = Path.home() / ".config/Cursor/User/workspaceStorage"
elif system == "win32": # Windows
base_path = Path(os.getenv("APPDATA", "")) / "Cursor/User/workspaceStorage"
else:
raise OSError(f"Unsupported operating system: {system}")
if not base_path.exists():
raise FileNotFoundError(
f"Cursor workspace storage directory not found at: {base_path}\n"
f"Make sure Cursor is installed and you've used notepads before."
)
return base_path
def get_workspace_info(storage_dir: Path) -> Dict[str, Dict[str, str]]:
"""Extract workspace information including project names from workspace.json files."""
workspace_info = {}
for workspace_folder in storage_dir.iterdir():
if not workspace_folder.is_dir():
continue
workspace_id = workspace_folder.name
workspace_json = workspace_folder / "workspace.json"
if workspace_json.exists():
try:
with open(workspace_json, 'r', encoding='utf-8') as f:
ws_data = json.load(f)
folder_path = ws_data.get('folder', '')
if folder_path:
# Handle file:// URLs
if folder_path.startswith('file://'):
folder_path = urllib.parse.unquote(folder_path.replace('file://', ''))
# Get the last part of the path as project name
project_name = Path(folder_path).name
if project_name:
workspace_info[workspace_id] = {
'name': project_name,
'path': folder_path
}
except Exception as e:
# Silently skip workspaces we can't read
pass
return workspace_info
def recover_notepads(storage_dir: Path) -> Tuple[Dict[str, Any], Dict[str, List[Tuple[str, Dict]]]]:
"""
Recover all notepad data from workspace databases.
Returns:
Tuple of (workspace_data, merged_notepads_dict)
"""
workspace_data = {}
all_notepads = {}
workspace_count = 0
found_count = 0
print("Searching for notepad data in workspace databases...\n")
# Find all state.vscdb files
for db_file in storage_dir.rglob("state.vscdb"):
workspace_count += 1
workspace_id = db_file.parent.name
try:
# Get database file modification time
db_mtime = db_file.stat().st_mtime
conn = sqlite3.connect(str(db_file))
cursor = conn.cursor()
cursor.execute("SELECT value FROM ItemTable WHERE key='notepadData';")
result = cursor.fetchone()
conn.close()
if result and result[0]:
found_count += 1
try:
notepad_data = json.loads(result[0])
workspace_data[workspace_id] = {
'notepad_data': notepad_data,
'db_last_modified': db_mtime,
'db_last_modified_iso': datetime.fromtimestamp(db_mtime).isoformat()
}
# Merge notepads
if 'notepads' in notepad_data:
for notepad_id, notepad_info in notepad_data['notepads'].items():
# Use workspace_id + notepad_id as unique key if duplicate
unique_id = f"{workspace_id}_{notepad_id}" if notepad_id in all_notepads else notepad_id
# Add database modification time as proxy for last update
all_notepads[unique_id] = {
**notepad_info,
'workspace_id': workspace_id,
'original_id': notepad_id,
'db_last_modified': db_mtime,
'db_last_modified_iso': datetime.fromtimestamp(db_mtime).isoformat()
}
except json.JSONDecodeError:
print(f" ⚠ Warning: Could not parse JSON from workspace {workspace_id[:12]}...")
except Exception as e:
# Silently skip databases we can't read
pass
print(f"✓ Checked {workspace_count} workspaces")
print(f"✓ Found notepad data in {found_count} workspaces")
print(f"✓ Recovered {len(all_notepads)} total notepads\n")
# Group notepads by workspace
workspace_notepads = {}
for notepad_id, notepad_info in all_notepads.items():
workspace_id = notepad_info.get('workspace_id', 'unknown')
if workspace_id not in workspace_notepads:
workspace_notepads[workspace_id] = []
workspace_notepads[workspace_id].append((notepad_id, notepad_info))
return workspace_data, workspace_notepads
def create_safe_filename(name: str, workspace_id: str) -> str:
"""Create a safe filename from project name."""
safe_name = re.sub(r'[^\w\s-]', '', name).strip()
safe_name = re.sub(r'[-\s]+', '-', safe_name)
if not safe_name:
safe_name = f"workspace-{workspace_id[:12]}"
return f"{safe_name}.md"
def create_project_markdown(
project_name: str,
workspace_id: str,
project_path: str,
notepads: List[Tuple[str, Dict]],
output_file: Path
) -> None:
"""Create a markdown file for a project with all its notepads."""
# Sort by last modified (most recent first)
sorted_notepads = sorted(
notepads,
key=lambda x: x[1].get('db_last_modified', 0),
reverse=True
)
md_content = [f"# {project_name}\n\n"]
md_content.append(f"**Workspace ID:** `{workspace_id}`\n\n")
if project_path != 'Unknown':
md_content.append(f"**Project Path:** `{project_path}`\n\n")
md_content.append(f"**Total Notepads:** {len(notepads)}\n\n")
md_content.append(f"**Last Updated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
md_content.append("> **Note:** Notepads are sorted by last modified date (most recent first).\n\n")
md_content.append("---\n\n")
for idx, (notepad_id, notepad_info) in enumerate(sorted_notepads, 1):
name = notepad_info.get('name', f'Untitled Notepad {idx}')
text = notepad_info.get('text', '')
created_at = notepad_info.get('createdAt', 0)
db_modified = notepad_info.get('db_last_modified', 0)
md_content.append(f"## {idx}. {name}\n\n")
# Last modified (most important, shown first)
if db_modified:
try:
dt = datetime.fromtimestamp(db_modified)
md_content.append(f"**Last Modified:** {dt.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
except:
pass
# Creation date
if created_at:
try:
dt = datetime.fromtimestamp(created_at / 1000 if created_at > 1e10 else created_at)
md_content.append(f"**Created:** {dt.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
except:
if created_at:
md_content.append(f"**Created:** {created_at}\n\n")
md_content.append(f"**Content:**\n\n")
md_content.append(f"{text}\n\n")
md_content.append("---\n\n")
with open(output_file, 'w', encoding='utf-8') as f:
f.write(''.join(md_content))
def create_index_file(project_files: List[Dict], output_file: Path) -> None:
"""Create an index file listing all projects."""
index_content = ["# Recovered Notepads by Project\n\n"]
index_content.append(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
index_content.append(f"**Total Projects:** {len(project_files)}\n\n")
index_content.append("> Projects are sorted by most recently modified notepad.\n\n")
index_content.append("---\n\n")
# Sort projects by most recent modification
sorted_projects = sorted(project_files, key=lambda x: x['most_recent'], reverse=True)
for idx, project in enumerate(sorted_projects, 1):
most_recent_str = "Never"
if project['most_recent']:
try:
dt = datetime.fromtimestamp(project['most_recent'])
most_recent_str = dt.strftime('%Y-%m-%d %H:%M:%S')
except:
pass
index_content.append(f"## {idx}. [{project['name']}]({project['filename']})\n\n")
index_content.append(f"- **Notepads:** {project['notepad_count']}\n")
index_content.append(f"- **Most Recent Update:** {most_recent_str}\n")
index_content.append(f"- **Workspace ID:** `{project['workspace_id']}`\n")
if project['path'] != 'Unknown':
index_content.append(f"- **Path:** `{project['path']}`\n")
index_content.append("\n")
with open(output_file, 'w', encoding='utf-8') as f:
f.write(''.join(index_content))
def main():
"""Main recovery function."""
print("=" * 60)
print("Cursor Notepad Recovery Script")
print("=" * 60)
print()
# Get output directory
if len(sys.argv) > 1:
output_dir = Path(sys.argv[1])
else:
output_dir = Path.cwd()
output_dir = output_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
print(f"Output directory: {output_dir}\n")
try:
# Get Cursor storage path
storage_dir = get_cursor_storage_path()
print(f"Cursor storage directory: {storage_dir}\n")
except Exception as e:
print(f"❌ Error: {e}")
sys.exit(1)
# Get workspace info
print("Reading workspace information...")
workspace_info = get_workspace_info(storage_dir)
print(f"✓ Found {len(workspace_info)} workspaces with project information\n")
# Recover notepads
workspace_data, workspace_notepads = recover_notepads(storage_dir)
if not workspace_notepads:
print("❌ No notepads found. Make sure you've used Cursor notepads before.")
sys.exit(1)
# Calculate content statistics
all_notepads_flat = {}
for notepads in workspace_notepads.values():
for notepad_id, notepad_info in notepads:
all_notepads_flat[notepad_id] = notepad_info
text_stats = {
'total': len(all_notepads_flat),
'empty': sum(1 for n in all_notepads_flat.values() if not n.get('text', '').strip()),
'total_chars': sum(len(n.get('text', '')) for n in all_notepads_flat.values()),
'max_chars': max((len(n.get('text', '')) for n in all_notepads_flat.values()), default=0)
}
print("Content Statistics:")
print(f" Notepads with content: {text_stats['total'] - text_stats['empty']}")
print(f" Empty notepads: {text_stats['empty']}")
print(f" Total characters recovered: {text_stats['total_chars']:,}")
print(f" Longest notepad: {text_stats['max_chars']:,} characters")
print()
# Create raw JSON backup
print("Creating raw JSON backup...")
raw_data = {
'workspaces': workspace_data,
'merged_notepads': all_notepads_flat,
'summary': {
'workspaces_checked': len(list(storage_dir.rglob("state.vscdb"))),
'workspaces_with_data': len(workspace_data),
'total_notepads': len(all_notepads_flat),
'recovery_date': datetime.now().isoformat(),
'content_stats': text_stats,
'note': 'db_last_modified is the database file modification time, used as a proxy for last update since Cursor does not store updatedAt'
}
}
raw_json_file = output_dir / "recovered_notepads_raw.json"
with open(raw_json_file, 'w', encoding='utf-8') as f:
json.dump(raw_data, f, indent=2, ensure_ascii=False)
print(f"✓ Created: {raw_json_file.name}\n")
# Create organized project files
print("Organizing notepads by project...")
projects_dir = output_dir / "recovered_notepads_by_project"
projects_dir.mkdir(exist_ok=True)
project_files = []
for workspace_id, notepads in workspace_notepads.items():
# Get project name
project_info = workspace_info.get(workspace_id, {})
project_name = project_info.get('name', f'Workspace-{workspace_id[:12]}')
project_path = project_info.get('path', 'Unknown')
# Create safe filename
filename = create_safe_filename(project_name, workspace_id)
filepath = projects_dir / filename
# Create markdown file
create_project_markdown(project_name, workspace_id, project_path, notepads, filepath)
# Get most recent modification time for index
most_recent = max(
(n[1].get('db_last_modified', 0) for n in notepads),
default=0
)
project_files.append({
'workspace_id': workspace_id,
'name': project_name,
'filename': filename,
'notepad_count': len(notepads),
'most_recent': most_recent,
'path': project_path
})
print(f" ✓ {filename} ({len(notepads)} notepads)")
# Create index file
print("\nCreating index file...")
index_file = projects_dir / "00-INDEX.md"
create_index_file(project_files, index_file)
print(f"✓ Created: {index_file.name}\n")
# Create single combined markdown file
print("Creating combined markdown file...")
combined_md_file = output_dir / "recovered_notepads.md"
create_combined_markdown(all_notepads_flat, text_stats, combined_md_file)
print(f"✓ Created: {combined_md_file.name}\n")
# Summary
print("=" * 60)
print("Recovery Complete!")
print("=" * 60)
print(f"\nFiles created in: {output_dir}")
print(f" - recovered_notepads_raw.json (raw backup)")
print(f" - recovered_notepads.md (combined file)")
print(f" - recovered_notepads_by_project/ (organized by project)")
print(f" - 00-INDEX.md (project index)")
print(f" - [project-name].md (one file per project)")
print(f"\nTotal: {len(project_files)} projects, {text_stats['total']} notepads recovered")
def create_combined_markdown(all_notepads: Dict, text_stats: Dict, output_file: Path) -> None:
"""Create a single combined markdown file with all notepads."""
md_content = ["# Recovered Notepads\n\n"]
md_content.append(f"**Recovery Date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
md_content.append(f"**Total Notepads Found:** {text_stats['total']}\n\n")
md_content.append(f"**Content Statistics:**\n")
md_content.append(f"- Notepads with content: {text_stats['total'] - text_stats['empty']}\n")
md_content.append(f"- Empty notepads: {text_stats['empty']}\n")
md_content.append(f"- Total characters recovered: {text_stats['total_chars']:,}\n")
md_content.append(f"- Longest notepad: {text_stats['max_chars']:,} characters\n\n")
md_content.append("> **Note:** `Last Modified` shows the database file modification time, which serves as a proxy for when the notepad was last updated. Cursor does not store an `updatedAt` field in the notepad data itself.\n\n")
md_content.append("---\n\n")
# Sort by creation date, then by database modification time
sorted_notepads = sorted(
all_notepads.items(),
key=lambda x: (
x[1].get('createdAt', 0),
x[1].get('db_last_modified', 0)
)
)
for idx, (notepad_id, notepad_info) in enumerate(sorted_notepads, 1):
name = notepad_info.get('name', f'Untitled Notepad {idx}')
text = notepad_info.get('text', '')
created_at = notepad_info.get('createdAt', 0)
db_modified = notepad_info.get('db_last_modified', 0)
workspace_id = notepad_info.get('workspace_id', 'Unknown')
md_content.append(f"## {idx}. {name}\n\n")
# Creation date
if created_at:
try:
dt = datetime.fromtimestamp(created_at / 1000 if created_at > 1e10 else created_at)
md_content.append(f"**Created:** {dt.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
except:
if created_at:
md_content.append(f"**Created:** {created_at}\n\n")
# Last modified (database file modification time)
if db_modified:
try:
dt = datetime.fromtimestamp(db_modified)
md_content.append(f"**Last Modified (DB file):** {dt.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
except:
pass
md_content.append(f"**Workspace ID:** `{workspace_id}`\n\n")
md_content.append(f"**Content:**\n\n")
md_content.append(f"{text}\n\n")
md_content.append("---\n\n")
with open(output_file, 'w', encoding='utf-8') as f:
f.write(''.join(md_content))
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n❌ Recovery cancelled by user.")
sys.exit(1)
except Exception as e:
print(f"\n\n❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
Now, can import existing notepads and use the functions of notepads such as add/modify/delete.
hi @lch great to see community members contributing, I suggest to move the notepads data to new storage, something independent of the main DB.
I know what you mean.
So, the workspace DB (state.vscdb) is used for Read purposes only
I’m using the notepads.json file separately in the same workspace folder as the corresponding DB
To sum up, there is no event that writes in the workspace DB
Crazy that it was removed without anyway way of recovering, this was such a good feature for keeping notes for yourself for the project
I was using them to prepare large prompts nicely structured, and there is no equivalent now
and markdow are not equivalent since notepads were proposing @ import of context. Please provide a better prompt writing area where:
*-*you can structure and visualize markdown like text
-import context as you do in the
-hit enter without sending the prompt
FYI: There’s a feature request that you can vote on to bring notepads back: Please bring back the Notepads
I am strongly in favor of having this as a core feature.



