Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

QLJupyter icon

QLJupyter

Quick Look preview for Jupyter notebooks on macOS.
Select an .ipynb in Finder and press Space — rendered markdown, syntax-highlighted code, and cell outputs.

Download macOS 13+ MIT License Stars

QLJupyter preview of a notebook

Why it's built this way

macOS deprecated the old .qlgenerator Quick Look plugins (the C/Obj-C generator API). The supported replacement is a Quick Look Preview Extension (QLPreviewingController) shipped as an app extension inside a host app, written in Swift. So this project is:

  • QLJupyter.app — a tiny host app. Its only job is to carry the extension and to declare the .ipynb file type to the system.
  • JupyterPreview.appex — the Quick Look extension. It parses the notebook JSON, builds an HTML representation, converts that to an NSAttributedString, and displays it in an NSTextView.

Why NSTextView and not WKWebView? Inside the sandboxed Quick Look extension, WKWebView's out-of-process web content renderer never completes navigation, so the preview comes up blank. Rendering the HTML into an attributed string is fully in-process and reliable. A custom NSLayoutManager draws the rounded code/output "cards".

What it renders

  • Markdown cells — headings, bold/italic, inline & fenced code, lists, blockquotes, links, images, and tables (self-contained converter, no JS).
  • Code cellsIn [n]: / Out[n]: prompts and Python syntax highlighting (keywords, strings, comments, numbers, builtins, functions).
  • Outputs — stdout/stderr streams, text/plain, embedded text/html (scripts stripped), inline PNG/JPEG/GIF/SVG images (base64 data URIs), and tracebacks with ANSI color codes stripped for readability.
  • Rounded, equal-width code/output cards; light/dark aware; fully offline.

Install (prebuilt)

Grab QLJupyter.zip from the latest release, then:

# The app is ad-hoc signed (not notarized), so macOS quarantines downloads.
# Remove the quarantine flag or the Quick Look extension won't load:
xattr -dr com.apple.quarantine ~/Downloads/QLJupyter.app

mv ~/Downloads/QLJupyter.app /Applications/
open /Applications/QLJupyter.app     # launch once to register the extension
qlmanage -r                          # refresh Quick Look

Then select an .ipynb file in Finder and press Space. If you'd rather not run a prebuilt binary, build it yourself below — a local build is trusted automatically and needs no xattr step.

Build & install

Requires Xcode 15+ on macOS 13 (Ventura) or later.

# Open in Xcode and Run the "QLJupyter" scheme, or from the terminal:
xcodebuild -project QLJupyter.xcodeproj -scheme QLJupyter -configuration Release build

# Move the built app somewhere permanent so the extension stays registered:
cp -R build/Build/Products/Release/QLJupyter.app /Applications/
open /Applications/QLJupyter.app   # launch once to register the extension

The extension is registered with the system the first time the host app is launched from /Applications. To force a refresh:

qlmanage -r
qlmanage -r cache

Notes

  • The extension supports the org.jupyter.ipynb and com.jupyter.notebook content types (whichever a given Mac resolves .ipynb to). It deliberately does not claim public.json, so it won't hijack previews of every JSON file.
  • If a preview doesn't appear, confirm the app is in /Applications, launch it once, then run qlmanage -r. Gatekeeper may require you to allow the app on first launch since it's ad-hoc signed.

Q&A / Troubleshooting

"QLJupyter can't be opened" / I don't want to use Terminal

Because the app isn't notarized, the first launch is blocked by Gatekeeper. Instead of the xattr command you can approve it from the UI:

  1. Double-click the app (macOS shows a "can't be opened" / "not opened" dialog — just dismiss it).
  2. Open System Settings → Privacy & Security, scroll to the Security section, and click Open Anyway next to "QLJupyter was blocked".
  3. Confirm with Open. This clears the quarantine, so the Quick Look extension can load. Then run qlmanage -r (or log out/in) and press Space on an .ipynb.

You only have to do this once.

A different preview shows up (plugin conflicts)

.ipynb is a popular file type, and several tools register their own Quick Look handlers for it (Syntax Highlight / sbarex, ipynb-quicklook and other legacy generators, JSON previewers, …). When more than one handler claims the same file type, macOS picks one, and there is no public API to force yours to win. If Space shows raw JSON, a syntax-highlighted blob, or the wrong style, another handler is winning. Here's how to find and fix it.

1. What type does macOS think .ipynb is?

mdls -name kMDItemContentType -name kMDItemContentTypeTree /path/to/notebook.ipynb

This tells you the resolved UTI (commonly org.jupyter.ipynb, sometimes public.json). Whatever it is, the winning handler is one that claims that type or a parent of it.

2. Which handlers are installed, and which is ours?

# All modern Quick Look preview extensions (ours is com.example.QLJupyter.JupyterPreview):
pluginkit -mAvvv -p com.apple.quicklook.preview

# Is ours registered and enabled?  A leading "+" means user-enabled.
pluginkit -m -i com.example.QLJupyter.JupyterPreview

To see which extension actually runs, trigger a preview and look for the extension process that spawns:

qlmanage -p /path/to/notebook.ipynb >/dev/null 2>&1 &
sleep 2
ps -Axo pid,comm | grep -iE 'JupyterPreview|Quick Look|quicklook' | grep -v grep
killall qlmanage 2>/dev/null

If you see e.g. Syntax Highlight Quick Look Extension, that's your culprit. Legacy .qlgenerator plugins don't spawn a separate process (they load inside quicklookd), so also check for those:

# Legacy generators live in these folders and inside app bundles:
ls -d /Library/QuickLook/*.qlgenerator ~/Library/QuickLook/*.qlgenerator 2>/dev/null
find /Applications -maxdepth 4 -name '*.qlgenerator' 2>/dev/null | grep -i ipynb
# What claims org.jupyter.ipynb in Launch Services:
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -dump | grep -B3 -i 'org.jupyter.ipynb'

3. Fixing it

A competing modern extension (e.g. Syntax Highlight) — disable its Quick Look, which is fully reversible:

pluginkit -e ignore -i org.sbarex.SourceCodeSyntaxHighlight.QuickLookExtension
# re-enable later with:
pluginkit -e use    -i org.sbarex.SourceCodeSyntaxHighlight.QuickLookExtension

You can also toggle handlers in System Settings → General → Login Items & Extensions → Quick Look.

A legacy .qlgenerator — it can't be toggled with pluginkit; move it out of the way (reversible) and unregister it:

GEN="/Applications/Jupyter Notebook Viewer.app/Contents/Library/QuickLook/ipynb-quicklook.qlgenerator"
mkdir -p ~/QL-disabled && mv "$GEN" ~/QL-disabled/
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -u "$GEN"

After any change, make sure ours is enabled and the app is in /Applications, then refresh the cache:

pluginkit -e use -i com.example.QLJupyter.JupyterPreview
qlmanage -r ; qlmanage -r cache ; killall Finder

Re-open the preview (close any stale Quick Look window first). If it's still wrong, disable competitors one at a time using step 2 to confirm which one takes over each time.

License

MIT © 2026 Yike Ye. See LICENSE.

Layout

Host/                  Host app (SwiftUI) + Info.plist declaring the UTI
JupyterPreview/        Quick Look extension
  PreviewViewController.swift          QLPreviewingController + NSTextView
  NotebookRenderer.swift               nbformat JSON -> inline-styled HTML
  Markdown.swift                       minimal Markdown -> HTML (incl. tables)
  Highlighter.swift                    Python syntax highlighter
  Palette.swift                        light/dark color palettes
  ANSI.swift                           strip ANSI escapes from tracebacks
  RoundedBackgroundLayoutManager.swift rounded, equal-width code/output cards

About

Quick Look preview extension for Jupyter notebooks (.ipynb) on macOS — markdown, syntax-highlighted code, and outputs.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages