quicklook-kit

Build Quick Look previews for macOS and Windows with Vite-based web frameworks
1,252
Install
npm install quicklook-kit
Latest Version:0.7.0
Vue:3
License:MIT
Last Updated:Oct 22, 2026
Links: npm
Maintainer: idleberg

quicklook-kit

License Version Build

Build Quick Look previews for macOS and Windows with Vite-based web frameworks, no Swift or C# required

Your preview is a web page. quicklook-kit builds it with your framework and wraps it in a small native host: a Quick Look extension on macOS, a QuickLook plugin on Windows. You never touch Swift, C# or Xcode.

Built-in support for SvelteKit, Astro, SolidStart, Nuxt and Analog. Any other framework that builds with Vite and writes a static index.html works through an integration.

Requirements

  • Node.js 22.17 or later, which runs the quicklook CLI
  • For macOS: macOS 14 or later, with Xcode and XcodeGen installed
  • For Windows: the .NET SDK, on any OS. Testing the plugin needs Windows with QuickLook and the Microsoft Edge WebView2 Runtime, which ships with Windows 11

Installation

npm install --save-dev quicklook-kit

To start from scratch, run pnpm create quicklook instead.

Setup

1. Configure

Create quicklook.config.ts in your project root:

import { defineConfig } from 'quicklook-kit';

export default defineConfig({
	name: 'Quick Look Example',
	macos: {
		bundleId: 'com.example.QuickLookExample',
		types: [
			{
				id: 'com.example.QuickLookExample.example',
				description: 'Example File',
				extensions: ['example'],
				conformsTo: ['public.plain-text'],
			},
		],
	},
	windows: {
		id: 'QuickLook.Plugin.Example',
		extensions: ['example'],
	},
	framework: 'sveltekit',
});

Each platform group is optional, but you need at least one: leave out windows to build for macOS only, or macos for Windows only.

A new type conforms to public.plain-text for text formats, or public.data for binary ones. To preview a type macOS already knows about, use a plain string like 'public.json' in place of an object.

The config can also be quicklook.config.js, .json, .yaml or .toml, or a .quicklookrc. The keys are the same in every format.

Key Default
name required App name, shown in Finder and System Settings
macos none macOS options, see below
windows none Windows options, see below
framework required 'sveltekit', 'astro', 'solid-start', 'nuxt', 'analog', or an integration
integrations [] See Integrations
csp locked down Directives here replace the defaults of the same name
maxFileSize 10_000_000 Larger files get Quick Look's generic preview
public {} Values the page can read at build time
settings {} Settings the user edits in the app, see Settings
devFile none A file the dev server previews until you drop another, relative to the project

Under macos:

Key Default
bundleId required The extension gets <bundleId>.Extension
types required File types to preview
icon none Path to an .icns file for the app
minimumVersion '14.0' Minimum macOS version

Under windows:

Key Default
id required Plugin name, starting with QuickLook.Plugin., e.g. QuickLook.Plugin.Example
extensions required File extensions to preview, without the dot. They win over QuickLook's own viewers

Upgrading from a version without platform groups? The old top-level bundleId, icon, macOS and types now live under macos, with macOS renamed to minimumVersion.

A framework can take options as a [name, options] pair, e.g. ['sveltekit', { outDir: 'dist' }] when your adapter writes somewhere other than the default.

Each framework needs a static, client-side build:

Framework Requirement
Analog @analogjs/vite-plugin-angular
Astro the default output: 'static'
Nuxt ssr: false
SolidStart ssr: false, Nitro's static preset prerendering /
SvelteKit @sveltejs/adapter-static

2. Add the Vite plugin

import { quicklook } from 'quicklook-kit/vite';

// in the framework's Vite config
plugins: [quicklook()];

For TypeScript, add the types of the virtual module, either in a .d.ts file:

/// <reference types="quicklook-kit/virtual" />

or in tsconfig.json under compilerOptions.types as "quicklook-kit/virtual".

3. Read the file

import { browserFile, config, nativeFile } from 'virtual:quicklook';

const file = nativeFile(); // the previewed file, undefined in the browser
config.public; // values from quicklook.config.*
Field Type
name string "notes.example"
extension string "example", lowercase, no dot
bytes Uint8Array the raw file contents

Decode bytes however your format needs: new TextDecoder().decode(file.bytes) for text, or a parser for binary formats.

In the dev server, nativeFile() returns devFile if you set one, and undefined otherwise. Pass a dropped or picked File to browserFile() to get the same shape.

4. Add scripts

{
	"scripts": {
		"dev": "quicklook dev",
		"build": "quicklook build",
		"register": "quicklook build && quicklook register",
		"unregister": "quicklook unregister"
	}
}

CLI

Command Description
quicklook dev Starts the framework's dev server
quicklook build:web Builds the page only, into .quicklook/web
quicklook build Builds every configured platform into .quicklook/build/<platform>
quicklook register Installs the build on the current OS: the app on macOS, the plugin on Windows
quicklook unregister Removes it again

build takes --platform macos or --platform windows to build just one. The Windows plugin builds on any OS; the macOS app only on a Mac, so elsewhere build skips it. Run build and register again after every change.

On macOS, register copies the app to /Applications and registers its extension. Select a file in Finder and press Space. If the preview doesn't show up, check pluginkit -mAv | grep <your macos.bundleId>, and make sure no other app claims the same file type.

On Windows, register copies the plugin to %APPDATA%\pooi.moe\QuickLook\QuickLook.Plugin\<windows.id>. Quit and restart QuickLook, then select a file in Explorer and press Space.

Add .quicklook to your .gitignore.

Things to know

  • Nothing can be loaded from the network. The native host serves your build from disk with a strict Content Security Policy, on both platforms. Import images, fonts and CSS normally and they get bundled. The dev server only reports violations in the console, without blocking them.
  • Inline scripts are allowed by hash. Frameworks that bootstrap with inline <script> tags keep working; the hashes are added to the CSP at build time.
  • Test in Safari and Edge. macOS previews run in WebKit, like Safari; Windows previews in WebView2, like Edge.
  • The light/dark theme follows the system. Use prefers-color-scheme in CSS.
  • The macOS app is signed ad hoc. Other people have to allow it in System Settings → Privacy & Security on first launch. Distributing it without that step requires an Apple Developer account and notarization.
  • Windows users install the .qlplugin from .quicklook/build/windows by previewing it in QuickLook and clicking the install button.

Releasing

Projects made with create quicklook include a release workflow. Push a tag like v1.0.0 and it builds every configured platform and attaches <App>-v1.0.0-macos.zip and <App>-v1.0.0-windows.qlplugin to a GitHub release.

If you publish a Homebrew cask, its URL needs the -macos suffix once you move to a release with platform groups.

Settings

settings declares options the user can change. On macOS, the app shows a control for each entry, picked by its type:

settings: {
  theme: {
    type: "string",
    default: "light",
    enum: [{ value: "light", description: "Light" }, "dark"],
    radio: true,
  },
  wrap: { type: "boolean", default: true, description: "Wrap **long** lines" },
  fontSize: { type: "integer", default: 13, minimum: 9, maximum: 32 },
  editor: {
    type: "object",
    properties: { tabSize: { type: "integer", default: 2, enum: [2, 4, 8] } },
  },
},
Type Control
boolean Checkbox
enum (on string, integer or number) Picker, or radio buttons with radio: true
string Text field
integer, number Number field, clamped to minimum/maximum; integer rejects fractions
object A group with a heading, for nested properties

Every entry except object needs a default. title defaults to the key in words (fontSize becomes "Font Size"), description is inline Markdown, and order sorts entries. enum entries are plain values or { value, description }. A schema with an unknown type or field, a field on the wrong type, a key containing ., or a default that doesn't fit its type, enum or bounds fails the build with the key path, e.g. settings.editor.tabSize: default 0 is below minimum 1.

The page reads the values, nested like the schema, with settings():

import { settings } from 'virtual:quicklook';

const { wrap, editor } = settings();

In the dev server, settings() returns the defaults.

Windows has no settings window. Users set values in %APPDATA%\pooi.moe\QuickLook\<windows.id>.config, with nested keys joined by dots, and restart QuickLook. Values that don't fit the schema fall back to the default.

<?xml version="1.0" encoding="utf-8"?>
<Settings>
  <theme>dark</theme>
  <editor.tabSize>4</editor.tabSize>
</Settings>

Integrations

An integration is an object with a name and any of these hooks. They run in order: the framework first, then each entry in integrations.

import type { Integration } from 'quicklook-kit';

export default (options: { key: string }): Integration => ({
	name: 'my-integration',
	hooks: {
		// Adjust the resolved config.
		'config:resolved'(config) {},

		// Post-process the framework's static output before it's bundled.
		async 'build:done'({ outDir, config }) {},

		// Edit the generated XcodeGen spec: Info.plist keys, entitlements, targets. macOS only.
		'xcode:spec'(spec, config) {
			spec.targets.QuickLookExampleExtension.info.properties.MyKey = options.key;
		},

		// Add Vite plugins wherever quicklook() is used.
		vite: () => [],
	},
});

Add it as an object, or by name so JSON and YAML configs can use it too. A name is imported from the project: a package or a relative path, whose default export is the integration or a function returning it.

{
	"integrations": ["quicklook-some-package", ["./my-integration.ts", { "key": "value" }]]
}

A framework is an integration with a build field:

framework: {
	name: 'my-framework',
	build: {
		dev: ['vite', 'dev'],
		command: ['vite', 'build'],
		outDir: 'dist'
	}
}

The commands run with the project's own binaries.

License

This work is licensed under The MIT License.

Related Packages

ziko-wrapper

integrate zikojs elements within other ui framework like vue react solidjs svelt...

5,604 49
worldality

Make your website global with Worldality, a framework-agnostic internationalizat...

600 0
@passlock/browser

Flexible passkey authentication for Astro, SvelteKit, NextJS and other framework...

5,281 192
consentlayer

Consent-first analytics for the browser and server: one API in front of GA4, Met...

0 1
@eslint-sets/eslint-config

Modern ESLint config with flat config support for Vue, React, Svelte, TypeScript...

6,835 7

Version History

Version Released License
0.7.0 MIT
0.6.0 MIT
0.5.0 MIT
0.4.0 MIT
0.3.1 MIT
Pre-releases (1)
Version Released License
0.0.0-stage