A Chrome MV3 extension that clips web pages into Craft via the Craft Space API. Content extraction uses Defuddle (the same engine as the Obsidian Web Clipper). YouTube transcript, description, and metadata capture comes from Defuddle's built-in YouTube extractor.
Highlights:
- Clean Markdown extraction from any page (or just your current text selection).
- YouTube transcript capture through Defuddle's site-specific extractor.
- A folder picker sourced from your Craft space, with the last-used folder remembered.
- An editable title + Markdown preview before anything is saved — what's in the box is exactly what gets sent to Craft.
- Documents are structured to match the official Craft web clipper's output (title +
source/site/author/published callouts, a
#clippingstag, a saved-at caption, and a divider before the body).
See DESIGN.md for the full architecture and design rationale.
- Build the extension (see Development below) so
dist/exists. - Open
chrome://extensionsin Chrome. - Enable Developer mode (top right).
- Click Load unpacked and select the
dist/folder in this repo. - Pin the CraftClipper icon to your toolbar if you'd like quick access.
CraftClipper talks to Craft through a Space API link — a secret URL that is itself the credential (no separate login/API key).
- In Craft, open Settings → Connect / API and create a new API connection link for your space.
- Copy the link Craft gives you. It looks like
https://connect.craft.do/links/<id>/api/v1(a plainhttps://connect.craft.do/links/<id>link, or just the bare<id>, also work — CraftClipper normalizes whatever you paste). - Right-click the CraftClipper toolbar icon → Options (or open the popup while unconfigured and click Open settings).
- Paste the link into Craft Space API link, click Test connection to confirm it resolves to your space, then Save.
Your link is stored in chrome.storage.sync (so it follows you across Chrome installs
signed into the same Google account) and is never sent anywhere except
https://connect.craft.do.
- Open the page you want to clip (or select some text on it first, to clip just that selection).
- Click the CraftClipper toolbar icon.
- Edit the title if you like, pick a destination folder (or leave it on Unsorted), and review/edit the Markdown preview.
- Click Save to Craft. On success you'll see a link to open the new document directly in the Craft app.
On a youtube.com/watch page, CraftClipper still uses Defuddle directly. Defuddle's
YouTube extractor fetches the video's transcript when captions are available and includes
the description and metadata in the extracted content.
- A normal article page clips with a clean Markdown body.
- Selecting a paragraph before opening the popup clips only that selection.
- A YouTube video with captions produces a Transcript section.
- A YouTube video without captions still clips metadata/description.
- The folder picker lists your space's folders (indented as
Parent / Child), remembers your last pick, and its refresh button re-fetches from Craft. - An invalid/expired API link produces a readable error (not a silent failure) both when testing the connection in Options and when saving from the popup.
chrome://pages and the Chrome Web Store show a friendly "can't be clipped" message instead of a dead popup.
Requires Node.js v22+ and npm 10+.
npm install # install typescript, esbuild, @types/chrome, defuddle
npm run build # bundle content/background/popup/options into dist/, copy static files
npm run watch # same, but rebuilds on file changes (esbuild incremental context)
npm run typecheck # tsc --noEmit across src/After npm run build, reload the unpacked extension at chrome://extensions (the reload
icon on the CraftClipper card) to pick up changes.
src/
content.ts # ISOLATED world: extraction orchestrator
lib/extract.ts # Defuddle wrapper: parse, selection fragment, html→md, fallbacks
lib/markdown.ts # clip payload → Craft markdown (header callouts + body)
lib/craft.ts # Craft API client (fetch wrapper, typed endpoints)
lib/storage.ts # chrome.storage.sync settings + last-used folder / folder cache
background.ts # service worker: save flow, MAIN-world shadow-DOM flatten
popup/ # popup.html/ts/css — preview + folder picker + save
options/ # options.html/ts/css — API link setup + connection test
manifest.json # copied to dist/ by build.mjs
build.mjs # esbuild-based build script
scripts/gen-icons.mjs # one-off script that generated icons/*.png
createMarkdownContentis imported as a named export fromdefuddle/full(import { createMarkdownContent } from "defuddle/full") rather than destructured off the default export. At runtime the installeddefuddle@0.19package attachescreateMarkdownContentas a static property of the default-exportedDefuddleclass (soDefuddleFull.createMarkdownContentdoes work), but its shipped.d.tsonly declares it as a named export, so the destructuring-off-default form failstsc --noEmit. The named import is behaviorally identical and type-checks cleanly.
Images are left as remote URLs in the clipped Markdown (Craft's POST /upload requires
per-file binary uploads, which is out of scope for v1 — see DESIGN.md "Non-goals").