For more than 500 items, the assistant gives you parts; import each part once, one after another. Rows from another app are new every time, so import each file only once.
Notes and files don't fit in this file: drag them in as a folder instead.
Bring your bookmarks, notes and images over from Raindrop, Pocket, your browser, Notion, Eagle, Apple Notes, Readwise or mymind.
For bookmarks and saved links from any app, let ChatGPT, Claude or another AI assistant turn the app's export into a file Resurf imports. It keeps titles, notes, tags, favorites and the original dates, and you don't install anything.
Turn the export file I've attached into a CSV that the Resurf app can import. If you can create files, give me a file called captures.csv. Otherwise reply with only the CSV in one code block.
Follow these rules exactly:
1. The first line is exactly:
type,title,source_url,description,user_note,tags,spaces,created_at,pinned_at
2. Write one row for every saved item that has a web address starting with http:// or https://. Skip folders, separators, bookmarklets and items without a web address, and tell me after the CSV how many you skipped.
3. type: always link, in lower case.
4. title: the item's title or name. Empty if it has none.
5. source_url: the item's full web address, exactly as in the file.
6. description: the excerpt or summary the app saved for the item. Empty if none.
7. user_note: my own note or comment on the item, including any highlights. Empty if none.
8. tags: the item's tags plus the name of the folder, collection or board it was in, separated by semicolons (;) with no spaces around them, for example design;inspiration;Travel. Don't add generic folders like "Bookmarks bar", "Unsorted" or "Other bookmarks". Empty if none.
9. spaces: leave empty.
10. created_at: when the item was saved, in UTC, as YYYY-MM-DDTHH:MM:SSZ, for example 2024-03-01T09:30:00Z. Convert Unix timestamps (seconds, milliseconds or microseconds) and written dates. If only the day is known, use T00:00:00Z. If no date is known, leave it empty.
11. pinned_at: if the item was a favorite, starred or pinned, copy its created_at here. Otherwise empty.
12. Separate columns with commas. Wrap any value that contains a comma, a double quote or a line break in double quotes, and write each double quote inside it twice.
13. Keep every item and keep the original words. Don't invent, translate or shorten anything.
If there are more than 500 items, split the CSV into parts of 500 rows, each starting with the same first line, and give me one part at a time.Export your bookmarks from the other app as a file, in any format it offers.
Open ChatGPT, Claude or another assistant, attach the export, and paste the prompt above.
Save what it gives you as captures.csv, or download the file it makes.
In Resurf, open Settings → Data and click Import… beside Import from CSV…, pick captures.csv and click Preview.
Check that Rows matches the number of items, then click Import.
For more than 500 items, the assistant gives you parts; import each part once, one after another. Rows from another app are new every time, so import each file only once.
Notes and files don't fit in this file: drag them in as a folder instead.
Each way in keeps different things.
| Route | Keeps | Good for |
|---|---|---|
| CSV import | Titles, tags, spaces, Notes and the original dates. Doesn't skip links you already saved. | Raindrop, Pocket, browser bookmarks |
| Drag a folder | Images, PDFs, video, audio, Markdown and text files, and .webloc links, one capture each. Folder names, tags and dates stay behind. | Notion, Eagle, Apple Notes, any folder of files |
| Paste links (⌘V) | One link per line. Skips links you already have. Titles and covers are fetched; tags and dates aren't kept. | A quick list |
| The resurf command | Links, notes, quotes and files from a script. Skips links you already have. | Readwise, anything you can script |
In the app, the free plan stops at 50 captures, whether you import, drag or paste. A CSV import that would pass the limit adds nothing. Spaces stop at 4.
The CSV recipes below use a short Python script that turns another app's export into a captures.csv Resurf reads. Save the script in a folder, open Terminal in that folder, and run it with your export file:
python3 raindrop-to-resurf.py ~/Downloads/export.csvIt writes captures.csv next to the script. Import that from Settings → Data.
The first time you run python3, macOS may offer to install its command line developer tools. Click Install, then run the command again.
Export your bookmarks from Raindrop as CSV and use the AI prompt above, or run this script on it and import captures.csv.
| Raindrop column | Becomes |
|---|---|
title | title |
url | source_url |
excerpt | description |
note | user_note, your Note |
tags | tags, commas changed to semicolons |
folder | spaces |
created | created_at |
favorite true | pinned_at, so it's pinned |
import csv, sys
from datetime import datetime, timezone
def iso(value):
if not value:
return ""
when = datetime.fromisoformat(value.replace("Z", "+00:00"))
return when.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
columns = ["type", "title", "source_url", "description", "user_note", "tags", "spaces", "created_at", "pinned_at"]
with open(sys.argv[1], newline="", encoding="utf-8-sig") as source, \
open("captures.csv", "w", newline="", encoding="utf-8") as target:
writer = csv.DictWriter(target, fieldnames=columns)
writer.writeheader()
for row in csv.DictReader(source):
created = iso(row.get("created", ""))
tags = [tag.strip() for tag in row.get("tags", "").split(",") if tag.strip()]
writer.writerow({
"type": "link",
"title": row.get("title", ""),
"source_url": row.get("url", ""),
"description": row.get("excerpt", ""),
"user_note": row.get("note", ""),
"tags": ";".join(tags),
"spaces": row.get("folder", ""),
"created_at": created,
"pinned_at": created if row.get("favorite") == "true" else "",
})Covers and highlights stay behind; Resurf fetches its own cover for each link. On the free plan only 4 spaces can exist, so delete the spaces column first if you have more collections.
Pocket closed in 2025, but if you downloaded your export, it holds one or more CSV files with title, url, time_added, tags and status. Use the AI prompt above on each file, or run this script on it and import captures.csv.
time_added is a Unix timestamp. The script turns it into a date.|. The script changes them to semicolons.import csv, sys
from datetime import datetime, timezone
columns = ["type", "title", "source_url", "tags", "created_at"]
with open(sys.argv[1], newline="", encoding="utf-8-sig") as source, \
open("captures.csv", "w", newline="", encoding="utf-8") as target:
writer = csv.DictWriter(target, fieldnames=columns)
writer.writeheader()
for row in csv.DictReader(source):
added = row.get("time_added", "")
created = datetime.fromtimestamp(int(added), timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") if added else ""
tags = [tag.strip() for tag in row.get("tags", "").split("|") if tag.strip()]
writer.writerow({
"type": "link",
"title": row.get("title", ""),
"source_url": row.get("url", ""),
"tags": ";".join(tags),
"created_at": created,
})Resurf doesn't read a bookmarks file directly. For a handful, drag them from your browser's bookmarks bar or bookmarks manager onto Resurf; each becomes a link. For all of them, export your bookmarks as an HTML file and use the AI prompt above, or turn it into a CSV with this script.
import csv, sys
from datetime import datetime, timezone
from html.parser import HTMLParser
class Bookmarks(HTMLParser):
def __init__(self):
super().__init__()
self.folders, self.pending, self.rows, self.link, self.text = [], None, [], None, ""
def handle_starttag(self, tag, attrs):
attrs = dict(attrs)
if tag == "dl":
self.folders.append(self.pending or "")
self.pending = None
elif tag in ("a", "h3"):
self.link = attrs if tag == "a" else None
self.text = ""
def handle_data(self, data):
self.text += data
def handle_endtag(self, tag):
if tag == "h3":
self.pending = self.text.strip()
elif tag == "dl" and self.folders:
self.folders.pop()
elif tag == "a" and self.link and self.link.get("href", "").startswith("http"):
added = self.link.get("add_date", "")
created = datetime.fromtimestamp(int(added[:10]), timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") if added.isdigit() else ""
folders = [name for name in self.folders if name]
self.rows.append({"type": "link", "title": self.text.strip(), "source_url": self.link["href"],
"tags": ";".join(folders), "created_at": created})
self.link = None
parser = Bookmarks()
with open(sys.argv[1], encoding="utf-8") as source:
parser.feed(source.read())
with open("captures.csv", "w", newline="", encoding="utf-8") as target:
writer = csv.DictWriter(target, fieldnames=["type", "title", "source_url", "tags", "created_at"])
writer.writeheader()
writer.writerows(parser.rows)
print(f"{len(parser.rows)} bookmarks written to captures.csv")The script keeps each bookmark's title, address and the date you added it, and turns the folders it sits in into tags. Bookmarklets and other addresses that don't start with http are skipped.
In Notion, export the pages you want as Markdown & CSV, with subpages included, and unzip the file. Drag the folder onto Resurf. Each page becomes a note, and each image in the folder becomes its own image capture.
Database tables are CSV files, which Resurf skips; the receipt says how many files it skipped. Images inside a page don't show in its note, because they're separate files.
There are two ways.
import csv, json, sys
with open(sys.argv[1], newline="", encoding="utf-8-sig") as source:
rows = [row for row in csv.DictReader(source) if row.get("Highlight", "").strip()]
items = []
for number, row in enumerate(rows, start=1):
author, book = row.get("Book Author", "").strip(), row.get("Book Title", "").strip()
item = {
"id": str(number),
"intent": "quote",
"content": {"kind": "text", "text": row["Highlight"].strip()},
"metadata": {"quoteAttribution": ", ".join(part for part in (author, book) if part)},
"organization": {"tags": [tag.strip() for tag in row.get("Tags", "").split(",") if tag.strip()]},
}
if row.get("Note", "").strip():
item["organization"]["userNote"] = row["Note"].strip()
items.append(item)
for start in range(0, len(items), 100):
page = start // 100 + 1
with open(f"readwise-{page}.json", "w", encoding="utf-8") as target:
json.dump({"requestID": f"readwise-import-{page}", "items": items[start:start + 100]}, target, ensure_ascii=False)
print(f"readwise-{page}.json")python3 readwise-to-resurf.py ~/Downloads/readwise-data.csv
for request in readwise-*.json; do
resurf capture submit --request "$request" --json | jq -c '.data.counts'
doneThe script writes files of up to 100 highlights each. Running the loop again with the same files adds nothing. If you export again later, change readwise-import in the script to a new name first.
Through a CSV import, yes, from the created_at column. Dragged files and pasted links are dated when you add them.
Not by CSV: rows from other apps are added again each time. Pasting links or saving them with the resurf command skips links you already have.
In the app, up to 50 captures and 4 spaces in all. A CSV import that would pass 50 stops before it adds anything, so you can activate a license first and run it again.