A macOS native print module for Electron, using NSPrintPanel to display the system print dialog for PDF files.
Install directly from GitHub via SSH:
npm install git+ssh://git@github.com:xxxxxccc/electron-macos-print.git
# or
pnpm add git+ssh://git@github.com:xxxxxccc/electron-macos-print.gitOr add to package.json manually:
{
"dependencies": {
"electron-macos-print": "git+ssh://git@github.com:xxxxxccc/electron-macos-print.git"
}
}Note: This is a native addon that compiles from source during installation. Requires Xcode Command Line Tools on macOS.
git clone git@github.com:xxxxxccc/electron-macos-print.git
cd electron-macos-print
pnpm install
pnpm buildImportant: This module only works on macOS. When used in a cross-platform Electron app, always check
process.platformbefore importing to avoid loading the native binary on unsupported platforms. Use dynamicimport()with a try-catch so that missing or incompatible modules fail gracefully.
if (process.platform === 'darwin') {
try {
const { PrintRequest } = await import('electron-macos-print');
if (PrintRequest.isAvailable()) {
const request = new PrintRequest({
pdfPath: '/path/to/document.pdf',
title: 'My Document' // optional
});
await request.print();
}
} catch {
// Module not available, fall back to other printing method
}
}This module prints PDF files, not HTML directly. In a typical Electron app, you need to convert HTML to PDF first using BrowserWindow.webContents.printToPDF(), then pass the PDF path to PrintRequest:
import { app, BrowserWindow } from 'electron';
import { writeFile } from 'fs/promises';
import { join } from 'path';
import { PrintRequest } from 'electron-macos-print';
async function printHTML(html: string, title?: string): Promise<boolean> {
if (!PrintRequest.isAvailable()) return false;
// 1. Create a hidden BrowserWindow to render HTML
const win = new BrowserWindow({ show: false, width: 800, height: 600 });
// 2. Load HTML and generate PDF
const tempHtmlPath = join(app.getPath('temp'), `print-${Date.now()}.html`);
await writeFile(tempHtmlPath, html, 'utf-8');
await win.loadFile(tempHtmlPath);
const pdfData = await win.webContents.printToPDF({
printBackground: true,
pageSize: 'A4',
});
win.close();
// 3. Save PDF to a temp file
const pdfPath = join(app.getPath('temp'), `print-${Date.now()}.pdf`);
await writeFile(pdfPath, pdfData);
// 4. Print using native dialog
const request = new PrintRequest({ pdfPath, title });
await request.print();
return true;
}Returns true if native printing is available (macOS only). Always returns false on other platforms.
Create a print request.
Parameters:
params.pdfPath: string— Absolute path to the PDF file to printparams.title?: string— Print window title (optional, defaults to"Print")
Opens the system print dialog. The promise resolves to true immediately after the dialog is presented — it does not wait for the user to confirm or cancel.
The print operation runs asynchronously on the macOS main thread via
dispatch_async. This means you cannot determine from the return value whether the user actually printed or cancelled.
On macOS, the module loads the PDF via PDFDocument (Quartz framework) and presents the native NSPrintPanel as a modal sheet on the active Electron window. If no window is available, it falls back to an application-modal dialog.
On unsupported platforms, PrintRequest.isAvailable() returns false and print() throws an error.
When packaging your Electron app for the Mac App Store, the native addon's build artifacts (debug symbols, Makefiles, config files, etc.) may cause MAS validation failures. You need to clean them up before signing.
Here's an example using Electron Forge — add this to your forge.config.ts in a packagerConfig.afterCopy hook:
import { join } from 'path'
import { existsSync, readdirSync, statSync, unlinkSync, rmdirSync } from 'fs'
// In packagerConfig.afterCopy:
async (buildPath: string) => {
const nativePrintPath = join(buildPath, 'node_modules', 'electron-macos-print')
if (!existsSync(nativePrintPath)) return
// Remove node-addon-api (not needed at runtime)
const nodeAddonApiPath = join(nativePrintPath, 'node-addon-api')
if (existsSync(nodeAddonApiPath)) {
rmdirSync(nodeAddonApiPath, { recursive: true })
}
// Remove dSYM debug symbols
const dSYMPath = join(
nativePrintPath, 'build', 'Release',
'electron_native_print.node.dSYM',
)
if (existsSync(dSYMPath)) {
rmdirSync(dSYMPath, { recursive: true })
}
// Clean build directory — keep only .o, .node, .stamp files
const buildDir = join(nativePrintPath, 'build')
if (existsSync(buildDir)) {
const cleanBuildDir = (dir: string) => {
for (const item of readdirSync(dir)) {
const itemPath = join(dir, item)
const stats = statSync(itemPath)
if (stats.isDirectory()) {
cleanBuildDir(itemPath)
if (readdirSync(itemPath).length === 0) rmdirSync(itemPath)
} else if (stats.isFile()) {
const ext = item.split('.').pop()?.toLowerCase()
if (ext !== 'o' && ext !== 'node' && ext !== 'stamp') {
unlinkSync(itemPath)
}
}
}
}
cleanBuildDir(buildDir)
}
}What this cleans up and why:
| Artifact | Reason to remove |
|---|---|
node-addon-api/ |
Header files only needed at compile time, not at runtime |
*.dSYM |
Debug symbols inflate bundle size and are rejected by MAS |
Makefiles, config.gypi, etc. |
Build system files that trigger MAS validation errors |
Only .o (object), .node (native binary), and .stamp files are kept — these are required for the addon to function.
- macOS 10.15+
- Xcode Command Line Tools
- Node.js 16+