node-hp-scan-to is a Node.js application that replicates HP's "Scan to Computer" functionality by reverse engineering HP's proprietary protocols and supporting the standardized eSCL protocol, allowing you to scan documents directly from your HP printer's scanner to your Linux, Windows, or macOS computer.
Unlike the original HP program, node-hp-scan-to is cross-platform and can be run on a bare-metal desktop or server, or in a container on Docker or Kubernetes. It can also be integrated with third-party document management solutions such as Paperless-ngx and Nextcloud.
Disclaimer: This project is neither endorsed by nor affiliated with Hewlett-Packard (HP). Any mention or reference to HP is purely descriptive and non-commercial. All reverse engineering of HP's official Windows application and its interaction with devices has been performed independently without cooperation from HP. This software is provided as-is for educational and personal use only.
- Features
- Installation
- Usage
- Troubleshooting
- Build Source Code
- Reverse Engineering
- 💖 Support this project
- 🙏 Special Thanks
- License
See SUPPORTED_DEVICES.md for the HP All-in-One Printers this app has been developed and tested with, the additional devices users have reported working, and the process for adding a new printer report.
There is a good chance it also works on other unlisted HP All-in-One Printers.
- ✔️ JPG and PDF document scan output
- ✔️ Automatic document feeder (ADF) support with dual-side scanning
- ✔️ Multi-page platen scanning
- ✔️ Automatic IP address discovery
- ✔️ Multi-platform: Linux, Windows, and macOS
- ✔️ Prebuilt Docker images (multi-architecture)
- ✔️ Command line (CLI) support
- ✔️ Customizable file names, resolutions, and device labels
- ✔️ Clear all registered targets
- ✔️ Emulated double side scan
- ✔️ Multiple output target support:
- Local folders
- Paperless-ngx API upload
- Nextcloud WebDAV upload
Supports both HP proprietary protocols (WalkupScanToComp, WalkupScan, ScanJob) and the standardized eSCL protocol.
- eSCL-only devices (e.g., HP ScanJet Pro 4500 fn1): Automatically detected and supported (#1307)
- Dual-protocol devices: Uses HP protocols by default; add
--prefer-eSCLflag to use eSCL instead - See eSCL protocol documentation for technical details
The emulated duplex scanning feature allows users to efficiently scan both sides of a document, even on devices that do not natively support duplex scanning. However, please note that this feature is only available in listen mode and is not supported with ADF-autoscan.
When enabled (as an opt-in feature), it adds an extra entry in the list of scan destinations, labeled with the "duplex" suffix. When you select this option for the first time, the device scans the front side of the document.
After the front side is scanned, if you choose the duplex option again, the device will trigger a second scan and produce an assembled output.
If you decide not to scan the back side immediately, the front side scan will be saved in the system and will remain there until you either scan the back side or perform a single side scan instead.
- HP scan-capable devices only. The app drives the scanner by speaking HP's proprietary scan protocols and eSCL. A device used with
listenmust expose Scan to Computer (or eSCL); see SUPPORTED_DEVICES.md. - Reverse-engineered and best-effort. It is provided as-is (see the disclaimer above) and behaviour varies between models.
- It moves scans, it does not process them. Output targets are a local folder, Paperless-ngx, Nextcloud and S3. OCR, document splitting, duplicate detection and blank-page removal are the destination's job (Paperless-ngx features or scripts), not this app's.
- Docker needs the printer IP. mDNS discovery does not work on the default Docker network, so the printer must be given explicitly (or the container put on a macvlan network).
-
You must have NodeJS installed
-
In a Terminal, run:
npm install node-hp-scan-to
Each release ships self-contained executables (no NodeJS required):
| File | Platform |
|---|---|
setup-node-hp-scan-to-v*.exe |
Windows 10/11 x64 installer (per-user autostart or system service) |
node-hp-scan-to-v*-windows-x64.zip |
Windows 10/11 x64 portable |
node-hp-scan-to-v*-macos.pkg |
macOS 11+ universal installer (Intel & Apple Silicon) |
node-hp-scan-to-v*-macos.dmg |
macOS 11+ universal drag-and-drop app bundle |
node-hp-scan-to-v*-darwin-arm64.zip |
Apple Silicon portable binary |
node-hp-scan-to-v*-darwin-x64.zip |
Intel Mac portable binary |
node-hp-scan-to-v*-linux-x64.tar.gz |
Linux x64 |
node-hp-scan-to-v*-linux-arm64.tar.gz |
Linux ARM64 |
Extract the archive anywhere and run node-hp-scan-to from its folder. It automatically reads the config/default.json shipped next to the binary; edit it to your needs.
The Windows installer offers two modes:
- For me (default, no admin rights): installs to
%LOCALAPPDATA%\Programs\node-hp-scan-to, saves scans to thehp-scanfolder inside your Documents (follows OneDrive redirection) and starts hidden at login via a scheduled task, with a persistent log in%APPDATA%\node-hp-scan-to\logs\scan.log - Windows service for all users: installs to
Program Files, runs as a service via WinSW and saves scans toC:\ProgramData\node-hp-scan-to\scans
Both modes also let you pick the startup behaviour: waiting for scan jobs triggered from the printer panel, or scanning automatically each time paper is loaded into the document feeder (adf-autoscan).
The macOS installer (.pkg) installs node-hp-scan-to.app into /Applications; the .dmg offers the same bundle for drag-and-drop installation. The bundled app is a universal2 binary (Intel + Apple Silicon), runs in the background and waits for scan jobs started from the printer panel. To start it at login, install the shipped LaunchAgent (edit the paths first, see packaging/io.github.manuc66.node-hp-scan-to.plist):
cp /Applications/node-hp-scan-to.app/Contents/Resources/io.github.manuc66.node-hp-scan-to.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/io.github.manuc66.node-hp-scan-to.plistNotes:
- Windows: SmartScreen may warn about an unsigned executable — click More info → Run anyway
- macOS: the app is unsigned and un-notarized, so Gatekeeper may block it on first run. Either right-click → Open, or clear the quarantine flag with
xattr -cr /Applications/node-hp-scan-to.app
.deb and .rpm packages (amd64/arm64) are attached to each release. They install /usr/bin/node-hp-scan-to, a node-hp-scan-to.service systemd unit (enabled with systemctl enable --now node-hp-scan-to) and an example configuration in /etc/node-hp-scan-to/default.json:
sudo apt install ./node-hp-scan-to_*_amd64.deb
# or
sudo rpm -i node-hp-scan-to-*.x86_64.rpmScans are saved under /var/lib/node-hp-scan-to by default when running as a service.
Linux networking notes:
- the service only makes outbound connections: mDNS discovery (UDP 5353 multicast), plain HTTP calls to the printer itself on port 80 (discovery tree, scan job creation and polling), and HTTPS uploads to Paperless-ngx/Nextcloud when configured — nothing listens for the scanning flow itself
--health-checklistens on all interfaces (port 3000 by default); restrict it with a local firewall rule if that matters to you, or drop the flag from/usr/lib/systemd/system/node-hp-scan-to.service- the packaged systemd unit runs under a dynamic non-root user with
PrivateTmp, read-only system paths and an empty capability set
.apk packages (x86_64/aarch64) are attached to each release. They ship a musl build, install the binary, an OpenRC service and /etc/node-hp-scan-to/default.json, and create a node-scan system user (scans land in /var/lib/node-hp-scan-to):
apk add ./node-hp-scan-to_*_x86_64.apk
rc-update add node-hp-scan-to default
rc-service node-hp-scan-to startThe system service is sandboxed on purpose, so point it at your home folder this way:
- set
"directory"in/etc/node-hp-scan-to/default.jsonto your folder - allow writes through the sandbox and to the dynamic user:
sudo systemctl edit node-hp-scan-to
# add:
# [Service]
# ReadWritePaths=/home/YOU/Documents/scans
# SupplementaryGroups=users
# then: sudo chmod g+w /home/YOU/Documents/scansSimpler alternative — run the app as yourself with the shipped per-user unit:
mkdir -p ~/.config/node-hp-scan-to
cp /etc/node-hp-scan-to/default.json ~/.config/node-hp-scan-to/
systemctl --user enable --now node-hp-scan-to
# optional: also start at boot without being logged in
sudo loginctl enable-linger "$USER"Every release artifact ships with a Sigstore build provenance attestation bound to the exact commit and workflow that produced it, plus a dependency SBOM (CycloneDX), and a checksum file:
gh attestation verify node-hp-scan-to_*_amd64.deb -R manuc66/node-hp-scan-to
gh attestation verify release/node-hp-scan-to.sbom.json -R manuc66/node-hp-scan-to --predicate-type https://cyclonedx.org/bom
sha256sum -c SHA256SUMS.txt-
You must have Docker installed
-
In a Terminal, run:
docker run -d -e IP="IP_ADDRESS_OF_PRINTER" -e PGID=1000 -e PUID=1000 docker.io/manuc66/node-hp-scan-to -
For more Docker options, see the Run with Docker section
-
For running with Docker Compose, see the Example for Docker Compose section
-
The package is available in the Arch User Repository (AUR) as
node-hp-scan-to -
Install it using your preferred AUR helper, for example:
yay -S node-hp-scan-to
or
paru -S node-hp-scan-to
Running the app with NodeJS using the npx command:
npx node-hp-scan-to
Main options:
-a, --address <ip>: Printer IP (e.g.,192.168.0.5)-d, --directory <path>: Save scans to (e.g.,~/scans)
Example usage:
npx node-hp-scan-to -a 192.168.0.5 -d ~/scans
The command you run decides what triggers a scan:
| Command | A scan starts when… | On the printer panel |
|---|---|---|
listen (default) |
you pick the app in Scan to Computer and start the job | the app appears under its --label (default: hostname) |
adf-autoscan |
paper is loaded into the document feeder | nothing |
single-scan |
you run the command; it scans once, then exits | nothing |
discover |
– it only lists the HP devices found on the network | nothing |
clear-registrations |
– it only clears the targets this app registered on the device | nothing |
With no command, listen runs. In Docker, select the command with the MAIN_COMMAND environment variable.
npx node-hp-scan-to discover
Lists every HP scan-capable device found on the network, one name<TAB>ip pair per line. Devices are discovered through mDNS and verified against the printer's proprietary DiscoveryTree.xml endpoint, so other network gadgets are filtered out. Useful to pick the right -n/--name value (recommended over a fixed IP, since it keeps working when the printer's DHCP lease changes).
Options: --timeout <seconds> (browsing window, default 5), --json (machine-readable array), --ip <address[:port]> (verify a single address), --name <prefix> (only keep matching names). Exit code is 0 when at least one device was found.
Summary of the options shared across commands. Each command's own --help output (see CLI Commands) is the authoritative list; options that only apply to some commands or modes are marked in the description.
| Option | Description | Example/Default |
|---|---|---|
-a, --address |
Printer IP address. | -a 192.168.0.5 (no default) |
-d, --directory |
Directory to save scanned documents. Defaults to /tmp/scan-to-pc<random value> if not set. |
-d /tmp/scan-to-pc1234 |
-D, --debug |
Enable debug logging. | -D (disabled by default) |
-f, --image-format |
Image format for scans (when not PDF). | -f Jpeg (choices: Jpeg, Bmp) |
-h, --height |
Scan height in pixels. Defaults to max (the device maximum). |
-h 3507 |
-k, --keep-files |
Retain scanned files after uploading to Paperless-ngx or Nextcloud (disabled by default). | -k (disabled by default) |
-l, --label |
The name of the computer running this app. Defaults to the hostname. | -l <hostname> (default: system hostname) |
-n, --name |
Printer name (quote if it contains spaces). | -n "Officejet 6500 E710n-z" (no default) |
-o, --paperless-token |
The paperless token. Required unless --paperless-token-file is used. Overrides if both are provided. |
-o xxxxxxxxxxxx (no default) |
--paperless-token-file |
File name that contains the paperless token. Required unless --paperless-token is used. Takes precedence if both are provided. |
--paperless-token-file some/path/to/file (no default) |
-p, --pattern |
Filename pattern (no extension). Use quotes for static text, supports date/time masks (see dateformat docs). Defaults to scan<increasing number>_page<page number>. |
-p scan1_page1 |
-r, --resolution |
Scan resolution in DPI. Defaults to 200. | -r 200 |
--mode <mode> |
Selects the scan mode (default: Color) (choices: "Gray", "Color"). | --mode Gray |
--paper-size <size> |
Paper size preset: A4 (default), Letter, Legal, A5, B5, or Max (case-insensitive). Cannot be used with --paper-dim. |
--paper-size Letter |
--paper-orientation <orientation> |
Paper orientation: portrait (default) or landscape. Applied to --paper-size only. |
--paper-orientation landscape |
--paper-dim <dimensions> |
Custom paper dimensions with unit (e.g., 21x29.7cm, 8.5x11in, 210x297mm). Cannot be used with --paper-size. |
--paper-dim 8.5x11in |
-s, --paperless-post-document-url |
Paperless-ngx API URL for uploading documents. | -s https://domain.tld/api/documents/post_document/ (no default) |
-t, --temp-directory |
Temporary directory for processing. Defaults to /tmp/scan-to-pc<random value> if not set. |
-t /tmp/scan-to-pc5678 |
-w, --width |
Scan width in pixels. Defaults to max (the device maximum). |
-w 2481 |
--device-up-polling-interval |
Polling interval (in milliseconds) to check if the printer is online. | --device-up-polling-interval 5000 (no default) |
--pdf |
Force the scan result to be a PDF document (the default depends on the device choice). single-scan and adf-autoscan only. |
--pdf (disabled by default) |
--duplex |
Scan both sides if the device supports it. single-scan and adf-autoscan only; in listen mode duplex is chosen on the device panel. |
--duplex (disabled by default) |
--prefer-eSCL |
Prefer the eSCL protocol when the device supports both eSCL and the HP protocols. | --prefer-eSCL (disabled by default) |
--add-emulated-duplex [mode] |
Add an extra scan target that emulates duplex on devices without native duplex. listen mode only. Mode: document-wise (default), page-wise, reverse-front or reverse-both. |
--add-emulated-duplex page-wise |
--emulated-duplex-label <label> |
Label of the emulated duplex target. Defaults to the main label with a duplex suffix. Only with --add-emulated-duplex. |
--emulated-duplex-label "Duplex" |
--health-check |
Start an HTTP health-check endpoint (listens on all interfaces). | --health-check (disabled by default) |
--health-check-port <port> |
Port for the HTTP health-check endpoint. | --health-check-port 3000 (default: 3000) |
--pollingInterval <ms> |
Time interval between each lookup for content in the ADF. adf-autoscan only. |
--pollingInterval 1000 (default: 1000) |
--start-scan-delay <ms> |
Wait delay before triggering the scan once paper is detected in the ADF. adf-autoscan only. |
--start-scan-delay 5000 (default: 5000) |
--nextcloud-password |
Nextcloud app password. Required unless --nextcloud-password-file is used. Overrides if both are provided. |
--nextcloud-password mypassword (no default) |
--nextcloud-password-file |
File containing the Nextcloud app password. Required unless --nextcloud-password is used. Takes precedence if both are provided. |
--nextcloud-password-file /path/to/file (no default) |
--nextcloud-upload-folder |
Nextcloud folder for uploads. Defaults to scan. |
--nextcloud-upload-folder scan |
--nextcloud-url |
Nextcloud instance URL. | --nextcloud-url https://domain.tld (no default) |
--nextcloud-username |
Nextcloud username with write access to the upload folder. | --nextcloud-username user (no default) |
--s3-url |
S3-compatible endpoint URL. | --s3-url https://s3.us-east-1.amazonaws.com (no default) |
--s3-region |
S3 region used for request signing. Defaults to us-east-1. |
--s3-region eu-west-1 |
--s3-access-key-id |
S3 access key id. | --s3-access-key-id AKIA... (no default) |
--s3-secret-access-key |
S3 secret access key. Required unless --s3-secret-access-key-file is used. Overrides if both are provided. |
--s3-secret-access-key ... (no default) |
--s3-secret-access-key-file |
File containing the S3 secret access key. Required unless --s3-secret-access-key is used. Takes precedence if both are provided. |
--s3-secret-access-key-file /path/to/file (no default) |
--s3-bucket |
S3 bucket to upload scans into. | --s3-bucket scans (no default) |
--s3-prefix |
Folder (prefix) inside the bucket. Defaults to the bucket root. | --s3-prefix 2026/08 |
--s3-force-path-style |
Force path-style addressing (required for MinIO, Cloudflare R2, Wasabi...). | --s3-force-path-style (disabled by default) |
--s3-session-token |
S3 session token for temporary credentials. | --s3-session-token ... (no default) |
Notes:
-
Date/time patterns for
--patternfollow the dateformat library's "Mask options" section. -
Defaults like
/tmp/scan-to-pc<random value>include a random suffix in practice (e.g.,/tmp/scan-to-pc1234).
By default, this app runs the listen command as the default mode. It will listen to the print for new job and trigger based on the selection on the device.
Run npx node-hp-scan-to listen --help to get the full list of command options.
Usage: listen [options]
Listen the device for new scan job to save to this target
Output Options:
-d, --directory <dir> Directory where scans are saved (default: /tmp/scan-to-pcRANDOM)
-p, --pattern <pattern> Pattern for filename (i.e. "scan"_dd.mm.yyyy_HHMMss, default would be scanPageNUMBER), make sure that the pattern is enclosed in extra quotes, avoid ":" as it is invalid on windows
-f, --image-format <format> Image format for scans (when not PDF): Jpeg (default) or Bmp
-k, --keep-files Keep the scan files on the file system when sent to external systems for local backup and easy access (default: false)
Scan Options:
-r, --resolution <dpi> Resolution in DPI of the scans (default: 200)
--mode <mode> Selects the scan mode (default: Color) (choices: "Gray", "Color")
-w, --width <width> Width in pixels of the scans (default: max)
-h, --height <height> Height in pixels of the scans (default: max)
--paper-size <size> Paper size preset: A4 (default), Letter, Legal, A5, B5, or Max (case-insensitive)
--paper-orientation <orientation> Paper orientation: portrait (default) or landscape. Applied to --paper-size only. (choices: "portrait", "landscape")
--paper-dim <dimensions> Custom paper dimensions with unit (e.g., 21x29.7cm, 8.5x11in, 210x297mm). Cannot be used with --paper-size.
-t, --temp-directory <dir> Temp directory used for processing (default: /tmp/scan-to-pcRANDOM)
--prefer-eSCL Prefer eSCL protocol if available
Options:
--device-up-polling-interval <deviceUpPollingInterval> Device up polling interval in milliseconds
--help display help for command
Paperless Options:
-s, --paperless-post-document-url <paperless_post_document_url> The paperless post document url (example: https://domain.tld/api/documents/post_document/)
-o, --paperless-token <paperless_token> The paperless token. Either this or paperless-token-file is required for paperless integration.
--paperless-token-file <paperless_token_file> File name that contains the paperless token. Either this or paperless-token is required for paperless integration.
--paperless-group-multi-page-scan-into-a-pdf Combine multiple scanned images into a single PDF document
--paperless-always-send-as-pdf-file Always convert scan job to pdf before sending to paperless
Nextcloud Options:
--nextcloud-url <nextcloud_url> The nextcloud url (example: https://domain.tld)
--nextcloud-username <nextcloud_username> The nextcloud username
--nextcloud-password <nextcloud_app_password> The nextcloud app password for username. Either this or nextcloud-password-file is required for nextcloud integration.
--nextcloud-password-file <nextcloud_app_password_file> File name that contains the nextcloud app password for username. Either this or nextcloud-password is required for nextcloud integration.
--nextcloud-upload-folder <nextcloud_upload_folder> The upload folder where documents or images are uploaded (default: scan)
S3 Options:
--s3-url <s3_url> The S3-compatible endpoint url (example: https://s3.us-east-1.amazonaws.com)
--s3-region <s3_region> The S3 region used for request signing (default: us-east-1)
--s3-access-key-id <s3_access_key_id> The S3 access key id
--s3-secret-access-key <s3_secret_access_key> The S3 secret access key. Either this or s3-secret-access-key-file is required for the s3 integration.
--s3-secret-access-key-file <s3_secret_access_key_file> File name that contains the S3 secret access key. Either this or s3-secret-access-key is required for the s3 integration.
--s3-bucket <s3_bucket> The S3 bucket where scans are uploaded
--s3-prefix <s3_prefix> The folder (prefix) inside the bucket where scans are uploaded (default: bucket root)
--s3-force-path-style Force path-style addressing (required for MinIO, Cloudflare R2, Wasabi...)
--s3-session-token <s3_session_token> The S3 session token for temporary credentials (optional)
Device Control Screen Options:
-l, --label <label> The label to display on the device (the default is the hostname)
--add-emulated-duplex [mode] Enable emulated duplex scanning, with optional assembly mode (default: document-wise) (choices: "page-wise", "document-wise", "reverse-front", "reverse-both")
--emulated-duplex-label <label> The emulated duplex label to display on the device (the default is to suffix the main label with duplex)
Global Options:
-a, --address <ip> IP address of the device, when specified, the ip will be used instead of the name
-n, --name <name> Name of the device to lookup for on the network
-D, --debug Enable debug
--health-check Start an http health check endpoint
--health-check-port <health-check-port> Define the port for the HTTP health check endpoint
In listen (and adf-autoscan) mode, scans are delivered on a background queue and the PDF merge runs in a worker thread, so the loop keeps responding to the printer while processing — see the processing pipeline page.
Running npx node-hp-scan-to adf-autoscan will automatically trigger a scan job as soon as the ADF (automatic document feeder) on the printer's scanner is loaded with paper.
You can also set the environment variable MAIN_COMMAND="adf-autoscan" with Docker. Example:
docker run -e MAIN_COMMAND="adf-autoscan" -e CMDLINE=--debug docker.io/manuc66/node-hp-scan-to:latestLogs go to stdout. The default output is backward compatible with the previous plain-text messages (one message per line), so existing scripts that parse stdout keep working unchanged. In an interactive terminal they are formatted for humans instead.
Structured JSON lines are available opt-in via LOG_FORMAT=json — the
recommended choice for docker log drivers and aggregators such as Loki or ELK.
| Env var | Values | Effect |
|---|---|---|
LOG_LEVEL |
trace … fatal |
Minimum level to emit (default info). debug shows HTTP traces too. |
LOG_FORMAT |
auto, pretty, plain, json |
auto (default): pretty in a terminal, legacy plain message text otherwise. pretty forces time/level/module output anywhere (no ANSI in non-TTY). plain forces the legacy message-only text anywhere. json forces structured JSON lines anywhere. |
Sensitive fields (password, token, authToken, Authorization) are
redacted as [Redacted] in every log line.
Run npx node-hp-scan-to adf-autoscan --help to get command line usage help.
Usage: adf-autoscan [options]
Automatically trigger a new scan job to this target once paper is detected in
the automatic document feeder (adf)
Output Options:
-d, --directory <dir> Directory where scans are saved (default: /tmp/scan-to-pcRANDOM)
-p, --pattern <pattern> Pattern for filename (i.e. "scan"_dd.mm.yyyy_HHMMss, default would be scanPageNUMBER), make sure that the pattern is enclosed in extra quotes, avoid ":" as it is invalid on windows
-f, --image-format <format> Image format for scans (when not PDF): Jpeg (default) or Bmp
-k, --keep-files Keep the scan files on the file system when sent to external systems for local backup and easy access (default: false)
--pdf If specified, the scan result will always be a pdf document, the default depends on the device choice
Scan Options:
-r, --resolution <dpi> Resolution in DPI of the scans (default: 200)
--mode <mode> Selects the scan mode (default: Color) (choices: "Gray", "Color")
-w, --width <width> Width in pixels of the scans (default: max)
-h, --height <height> Height in pixels of the scans (default: max)
--paper-size <size> Paper size preset: A4 (default), Letter, Legal, A5, B5, or Max (case-insensitive)
--paper-orientation <orientation> Paper orientation: portrait (default) or landscape. Applied to --paper-size only. (choices: "portrait", "landscape")
--paper-dim <dimensions> Custom paper dimensions with unit (e.g., 21x29.7cm, 8.5x11in, 210x297mm). Cannot be used with --paper-size.
-t, --temp-directory <dir> Temp directory used for processing (default: /tmp/scan-to-pcRANDOM)
--prefer-eSCL Prefer eSCL protocol if available
--duplex If specified, all the scans will be in duplex if the device support it
Options:
--device-up-polling-interval <deviceUpPollingInterval> Device up polling interval in milliseconds
--help display help for command
Paperless Options:
-s, --paperless-post-document-url <paperless_post_document_url> The paperless post document url (example: https://domain.tld/api/documents/post_document/)
-o, --paperless-token <paperless_token> The paperless token. Either this or paperless-token-file is required for paperless integration.
--paperless-token-file <paperless_token_file> File name that contains the paperless token. Either this or paperless-token is required for paperless integration.
--paperless-group-multi-page-scan-into-a-pdf Combine multiple scanned images into a single PDF document
--paperless-always-send-as-pdf-file Always convert scan job to pdf before sending to paperless
Nextcloud Options:
--nextcloud-url <nextcloud_url> The nextcloud url (example: https://domain.tld)
--nextcloud-username <nextcloud_username> The nextcloud username
--nextcloud-password <nextcloud_app_password> The nextcloud app password for username. Either this or nextcloud-password-file is required for nextcloud integration.
--nextcloud-password-file <nextcloud_app_password_file> File name that contains the nextcloud app password for username. Either this or nextcloud-password is required for nextcloud integration.
--nextcloud-upload-folder <nextcloud_upload_folder> The upload folder where documents or images are uploaded (default: scan)
S3 Options:
--s3-url <s3_url> The S3-compatible endpoint url (example: https://s3.us-east-1.amazonaws.com)
--s3-region <s3_region> The S3 region used for request signing (default: us-east-1)
--s3-access-key-id <s3_access_key_id> The S3 access key id
--s3-secret-access-key <s3_secret_access_key> The S3 secret access key. Either this or s3-secret-access-key-file is required for the s3 integration.
--s3-secret-access-key-file <s3_secret_access_key_file> File name that contains the S3 secret access key. Either this or s3-secret-access-key is required for the s3 integration.
--s3-bucket <s3_bucket> The S3 bucket where scans are uploaded
--s3-prefix <s3_prefix> The folder (prefix) inside the bucket where scans are uploaded (default: bucket root)
--s3-force-path-style Force path-style addressing (required for MinIO, Cloudflare R2, Wasabi...)
--s3-session-token <s3_session_token> The S3 session token for temporary credentials (optional)
Auto-scan Options:
--pollingInterval <pollingInterval> Time interval in millisecond between each lookup for content in the automatic document feeder
--start-scan-delay <startScanDelay> Once document are detected to be in the adf, this specify the wait delay in millisecond before triggering the scan
Global Options:
-a, --address <ip> IP address of the device, when specified, the ip will be used instead of the name
-n, --name <name> Name of the device to lookup for on the network
-D, --debug Enable debug
--health-check Start an http health check endpoint
--health-check-port <health-check-port> Define the port for the HTTP health check endpoint
Running npx node-hp-scan-to clear-registrations will clear all registered targets on the device (useful for trial and error and debugging).
Run npx node-hp-scan-to clear-registrations --help to get command line usage help.
You can also set the environment variable MAIN_COMMAND="clear-registrations" with Docker. Example:
docker run -e MAIN_COMMAND="clear-registrations" docker.io/manuc66/node-hp-scan-to:latestUsage: clear-registrations [options]
Clear the list or registered target on the device
Options:
-h, --help display help for command
Global Options:
-a, --address <ip> IP address of the device, when specified, the ip will be used instead of the name
-n, --name <name> Name of the device to lookup for on the network
-D, --debug Enable debug
--health-check Start an http health check endpoint
--health-check-port <health-check-port> Define the port for the HTTP health check endpoint
Running npx node-hp-scan-to single-scan will directly issue a single scan job
Run npx node-hp-scan-to single-scan --help to get command line usage help.
You can also set the environment variable MAIN_COMMAND="single-scan" with Docker. Example:
docker run -e MAIN_COMMAND="single-scan" docker.io/manuc66/node-hp-scan-to:latestUsage: single-scan [options]
Trigger a new scan job
Output Options:
-d, --directory <dir> Directory where scans are saved (default: /tmp/scan-to-pcRANDOM)
-p, --pattern <pattern> Pattern for filename (i.e. "scan"_dd.mm.yyyy_HHMMss, default would be scanPageNUMBER), make sure that the pattern is enclosed in extra quotes, avoid ":" as it is invalid on windows
-f, --image-format <format> Image format for scans (when not PDF): Jpeg (default) or Bmp
-k, --keep-files Keep the scan files on the file system when sent to external systems for local backup and easy access (default: false)
--pdf If specified, the scan result will always be a pdf document, the default depends on the device choice
Scan Options:
-r, --resolution <dpi> Resolution in DPI of the scans (default: 200)
--mode <mode> Selects the scan mode (default: Color) (choices: "Gray", "Color")
-w, --width <width> Width in pixels of the scans (default: max)
-h, --height <height> Height in pixels of the scans (default: max)
--paper-size <size> Paper size preset: A4 (default), Letter, Legal, A5, B5, or Max (case-insensitive)
--paper-orientation <orientation> Paper orientation: portrait (default) or landscape. Applied to --paper-size only. (choices: "portrait", "landscape")
--paper-dim <dimensions> Custom paper dimensions with unit (e.g., 21x29.7cm, 8.5x11in, 210x297mm). Cannot be used with --paper-size.
-t, --temp-directory <dir> Temp directory used for processing (default: /tmp/scan-to-pcRANDOM)
--prefer-eSCL Prefer eSCL protocol if available
--duplex If specified, all the scans will be in duplex if the device support it
Options:
--device-up-polling-interval <deviceUpPollingInterval> Device up polling interval in milliseconds
--help display help for command
Paperless Options:
-s, --paperless-post-document-url <paperless_post_document_url> The paperless post document url (example: https://domain.tld/api/documents/post_document/)
-o, --paperless-token <paperless_token> The paperless token. Either this or paperless-token-file is required for paperless integration.
--paperless-token-file <paperless_token_file> File name that contains the paperless token. Either this or paperless-token is required for paperless integration.
--paperless-group-multi-page-scan-into-a-pdf Combine multiple scanned images into a single PDF document
--paperless-always-send-as-pdf-file Always convert scan job to pdf before sending to paperless
Nextcloud Options:
--nextcloud-url <nextcloud_url> The nextcloud url (example: https://domain.tld)
--nextcloud-username <nextcloud_username> The nextcloud username
--nextcloud-password <nextcloud_app_password> The nextcloud app password for username. Either this or nextcloud-password-file is required for nextcloud integration.
--nextcloud-password-file <nextcloud_app_password_file> File name that contains the nextcloud app password for username. Either this or nextcloud-password is required for nextcloud integration.
--nextcloud-upload-folder <nextcloud_upload_folder> The upload folder where documents or images are uploaded (default: scan)
S3 Options:
--s3-url <s3_url> The S3-compatible endpoint url (example: https://s3.us-east-1.amazonaws.com)
--s3-region <s3_region> The S3 region used for request signing (default: us-east-1)
--s3-access-key-id <s3_access_key_id> The S3 access key id
--s3-secret-access-key <s3_secret_access_key> The S3 secret access key. Either this or s3-secret-access-key-file is required for the s3 integration.
--s3-secret-access-key-file <s3_secret_access_key_file> File name that contains the S3 secret access key. Either this or s3-secret-access-key is required for the s3 integration.
--s3-bucket <s3_bucket> The S3 bucket where scans are uploaded
--s3-prefix <s3_prefix> The folder (prefix) inside the bucket where scans are uploaded (default: bucket root)
--s3-force-path-style Force path-style addressing (required for MinIO, Cloudflare R2, Wasabi...)
--s3-session-token <s3_session_token> The S3 session token for temporary credentials (optional)
Global Options:
-a, --address <ip> IP address of the device, when specified, the ip will be used instead of the name
-n, --name <name> Name of the device to lookup for on the network
-D, --debug Enable debug
--health-check Start an http health check endpoint
--health-check-port <health-check-port> Define the port for the HTTP health check endpoint
By default, scans use A4 paper size (210×297 mm). You can specify a different paper size or custom dimensions via CLI flags, environment variables, or the config file.
The following preset sizes are available (case-insensitive):
| Preset | Dimensions | Use Case |
|---|---|---|
| A4 (default) | 210×297 mm | Standard European paper size |
| Letter | 215.9×279.4 mm | Standard US letter size |
| Legal | 215.9×355.6 mm | Standard US legal size |
| A5 | 148×210 mm | Half of A4 size |
| B5 | 176×250 mm | Between A4 and A5 |
| Max | Device maximum | Uses the device's maximum scannable area (not auto-detect) |
You can specify custom paper dimensions with explicit units using the --paper-dim option:
# Centimeters
npx node-hp-scan-to single-scan --paper-dim 21x29.7cm
# Inches
npx node-hp-scan-to single-scan --paper-dim 8.5x11in
# Millimeters
npx node-hp-scan-to single-scan --paper-dim 210x297mmSupported units: cm, mm, in (inches). Units are required and case-insensitive.
Paper size is resolved in the following order (highest to lowest priority):
- CLI flags:
--paper-sizeor--paper-dim - Environment variables:
PAPER_SIZEorPAPER_DIM - Config file:
paper_sizeorpaper_diminconfig/default.json - Default: A4 (210×297 mm)
- Cannot specify both
--paper-sizeand--paper-dimsimultaneously. You must choose one; attempting to use both will result in an error. - Resolution (DPI) is independent of paper size. You can specify both independently:
npx node-hp-scan-to single-scan --paper-size Letter --resolution 300
- Device maximum is respected. If your custom size exceeds the device's maximum scannable area, the scan will be clamped to the device's limits.
- "Max" uses device capabilities, not auto-detection. The
Maxpreset uses the device's reported maximum scan area; it does not automatically detect the actual paper size in the scanner.
# Use Letter size
npx node-hp-scan-to single-scan --paper-size Letter
# Use A5 (smaller) size
npx node-hp-scan-to single-scan --paper-size A5
# Use maximum scannable area
npx node-hp-scan-to single-scan --paper-size Max
# Use custom size in centimeters
npx node-hp-scan-to single-scan --paper-dim 21x29.7cm
# Use custom size in inches (8.5" × 11")
npx node-hp-scan-to single-scan --paper-dim 8.5x11in
# With Docker (environment variable)
docker run -e PAPER_SIZE=Letter manuc66/node-hp-scan-to
# With Docker Compose
environment:
PAPER_SIZE: "Letter"
PAPER_DIM: null # Must be null if not using custom dimensions
# In config file (config/default.json)
{
"paper_size": "A4",
"paper_dim": null
}If the device can scan both sides by itself (duplex ADF):
single-scanandadf-autoscan: add--duplex.listen: choose the duplex option on the printer panel for the job.
If it can only scan one side, --add-emulated-duplex (listen mode only) adds a second destination, suffixed with duplex. The first time you select it the front side is scanned; selecting it again scans the back side and the app assembles both sides into one document. The pending front side is kept until you scan the back side or start a single-sided job.
--add-emulated-duplex <mode> says how the two passes are assembled (default document-wise). Feeder models differ in the order in which each pass comes out, so pick the mode that matches yours:
| Mode | Front pass | Back pass |
|---|---|---|
page-wise |
in order | in order |
document-wise |
in order | reversed |
reverse-front |
reversed | in order |
reverse-both |
reversed | reversed |
"Reversed" means the sheets come out of the feeder back to front. Whatever the mode, the assembled document is in page order 1, 2, 3, …
https://hub.docker.com/repository/docker/manuc66/node-hp-scan-to
The Docker images follow semantic versioning:
latest: Latest stable release (includes all patch updates)x.y.z: Specific version (e.g.,1.2.3)x.y: Latest patch version of a specific minor version (e.g.,1.2)x: Latest minor.patch version of a specific major version (e.g.,1)master: Latest build from the master branch (development version)
Note: For most users, the latest tag is recommended as it includes all patch updates and bug fixes.
Be aware that with Docker you have to specify the IP address of the printer via the IP environment variable, because the Bonjour service discovery protocol uses multicast network traffic, which by default doesn't work in Docker.
You could however use Docker's macvlan networking, this way you can use service discovery and the NAME environment variable.
All scanned files are written to the volume /scan, the filename can be changed with the PATTERN environment variable. For the correct permissions to the volume set the environment variables PUID and PGID to that of the user running the container (usually PUID=1000 and PGID=1000).
List of supported environment variables and their meaning, or correspondence with command-line flags:
| Environment Variable | Description | Corresponding CLI Flag or Notes |
|---|---|---|
CMDLINE |
Additional command-line flags added at the end of the command | Set to -D to enable debug logs |
DIR |
Directory to use | -d / --directory |
IP |
IP address for the program | -a / --address |
KEEP_FILES |
If set, scanned files are not deleted after uploading to Paperless-ngx or Nextcloud | |
LABEL |
Label to set on the device's display as a scan target | -l / --label |
NAME |
Name of the device to lookup for on the network | -n / --name |
ADD_EMULATED_DUPLEX |
Enable emulated duplex scanning, with optional assembly mode (default: document-wise) | --add-emulated-duplex [mode] |
NEXTCLOUD_PASSWORD |
Password of Nextcloud user (either this or NEXTCLOUD_PASSWORD_FILE is required; file takes precedence) |
|
NEXTCLOUD_PASSWORD_FILE |
File containing Nextcloud user password (either this or NEXTCLOUD_PASSWORD is required; takes precedence) |
Example: ./nextcloud_password.secret (preferred for Docker Compose secrets) |
NEXTCLOUD_UPLOAD_FOLDER |
Upload folder for documents or images (user must have write permission; defaults to scan if not set) |
|
NEXTCLOUD_URL |
Nextcloud URL | Example: https://nextcloud.example.tld |
NEXTCLOUD_USERNAME |
Nextcloud username | |
PAPERLESS_POST_DOCUMENT_URL |
Paperless-ngx post document URL (if provided with token, a PDF is uploaded) | Example: http://<paperless-host>:<port>/api/documents/post_document/ |
PAPERLESS_TOKEN |
Paperless-ngx API token (either this or PAPERLESS_TOKEN_FILE is required; file takes precedence) |
Example: xxxxxxxxxxxx... |
PAPERLESS_TOKEN_FILE |
File containing paperless-ngx API token (either this or PAPERLESS_TOKEN is required; takes precedence) |
Example: ./paperless_token.secret (preferred for Docker Compose secrets) |
PATTERN |
Pattern to use | -p / --pattern |
PGID |
ID of the group that will run the program | |
PUID |
ID of the user that will run the program | |
RESOLUTION |
Resolution setting | -r / --resolution |
FORMAT |
Image format setting | -f / --image-format |
MODE |
Scan mode setting | --mode |
PAPER_ORIENTATION |
Paper orientation: portrait (default) or landscape. Applied to PAPER_SIZE only. |
--paper-orientation |
TEMP_DIR |
Temporary directory | -t / --temp-directory |
S3_URL |
S3-compatible endpoint URL | --s3-url |
S3_REGION |
S3 region used for request signing (default us-east-1) |
--s3-region |
S3_ACCESS_KEY_ID |
S3 access key id | --s3-access-key-id |
S3_SECRET_ACCESS_KEY |
S3 secret access key (either this or S3_SECRET_ACCESS_KEY_FILE is required; file takes precedence) |
|
S3_SECRET_ACCESS_KEY_FILE |
File containing the S3 secret access key (preferred for Docker Compose secrets) | Example: ./s3_secret.secret |
S3_BUCKET |
S3 bucket to upload scans into | --s3-bucket |
S3_PREFIX |
Folder (prefix) inside the bucket (default bucket root) | --s3-prefix |
S3_FORCE_PATH_STYLE |
Force path-style addressing (required for MinIO, Cloudflare R2, Wasabi...) | --s3-force-path-style |
S3_SESSION_TOKEN |
S3 session token for temporary credentials | --s3-session-token |
Additional Notes:
-
The name shown on the printer’s display is the hostname of the Docker container, which defaults to a random value. You can override it by setting the
hostnameor using theLABELenvironment variable. -
To enable debug logs set the environment variable
CMDLINEto-D
To build a local Docker image from this repo:
git clone https://github.com/manuc66/node-hp-scan-to.git
cd node-hp-scan-to
docker build . -t node-hp-scan-to
docker run -e IP=192.168.0.5 -e PGID=1000 -e PUID=1000 --hostname myComputer node-hp-scan-toCreate the following docker-compose.yml file into this directory:
services:
node-hp-scan-to:
image: docker.io/manuc66/node-hp-scan-to:latest
restart: unless-stopped
hostname: node-hp-scan-to
environment:
# REQUIRED - Change the next line to the IP address of your HP printer/scanner:
- IP=192.168.0.5
# Name that your container will appear as to your printer:
- LABEL=node-hp-scan-to
# Set the timezone, such as "Europe/London":
- TZ=UTC
# Set the created filename pattern:
- PATTERN="scan"_dd-mm-yyyy_hh-MM-ss
# Run the Docker container as the same user ID as the host system:
- PGID=1000
- PUID=1000
# Optional - enable autoscanning a document when loaded into the scanner:
# - MAIN_COMMAND=adf-autoscan
# If you need to pass additional configuration flag use the CMDLINE env, thy will be appened to the
# - CMDLINE=--debug --pdf
# If using Paperless-ngx, you can use its API to upload files:
# - PAPERLESS_POST_DOCUMENT_URL=http://<paperless-host>:<port>/api/documents/post_document/
# - PAPERLESS_TOKEN= xxxxxxxxxxxx...
volumes:
- ./scan:/scanThen run docker-compose up -d
Apply the following manifest (the PersistentVolumeClaim must also be deployed beforehand):
apiVersion: apps/v1
kind: Deployment
name: hp-scan-to
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: hp-scan-to
template:
metadata:
labels:
app.kubernetes.io/name: hp-scan-to
spec:
containers:
- image: manuc66/node-hp-scan-to:master
name: hp-scan-to
env:
- name: IP
value: 192.168.0.5
- name: PATTERN
value: '"scan"_dd.mm.yyyy_HHMMss'
- name: PGID
value: "1000"
- name: PUID
value: "1000"
- name: LABEL
value: scan
- name: DIR
value: /scans
- name: TZ
value: Europe/London
resources:
limits:
memory: 256Mi
requests:
cpu: "0"
memory: 64Mi
volumeMounts:
- mountPath: /scans
name: incoming-scans
restartPolicy: Always
volumes:
- name: incoming-scans
persistentVolumeClaim:
claimName: incoming-scansThere are two ways to get scans into Paperless-ngx.
Through the API. Point the app at the Paperless upload endpoint and give it a token:
npx node-hp-scan-to -a 192.168.0.5 \
--paperless-post-document-url https://paperless.example.tld/api/documents/post_document/ \
--paperless-token-file /run/secrets/paperless_tokenEach scan is uploaded to Paperless' post_document endpoint — no shared folder required. How a multi-page scan is turned into documents is controlled by:
| Options | Result |
|---|---|
| (none) | one image document per page |
--paperless-always-send-as-pdf-file |
one PDF document per page |
--paperless-group-multi-page-scan-into-a-pdf |
a single PDF document for the whole scan |
The upload does not go through Paperless' consume folder, so Paperless' preconsume and postconsume scripts do not run on documents uploaded this way.
Through the consume folder. Share Paperless' consume directory (SMB, FTP…) and point --directory at it. The files are ingested like any other drop and the preconsume/postconsume scripts do run — at the price of a shared folder.
--keep-files keeps the local copy after a successful upload. Document splitting (barcode/separator pages), duplicate detection, OCR and blank-page removal are Paperless features or scripts, not options of this app.
Settings can live in a JSON file instead of CLI flags. The file is read with the config package; the shipped default is config/default.json, standalone binaries look for a config folder next to the executable, and NODE_CONFIG_DIR overrides the location.
{
"name": "Officejet 6500 E710n-z",
"directory": "/var/lib/node-hp-scan-to/scans",
"pattern": "scan_yyyy-mm-dd_HHMMss",
"resolution": 300,
"mode": "Gray",
"paper_size": "Letter",
"add_emulated_duplex": true,
"emulated_duplex_assembly_mode": "document-wise",
"paperless_post_document_url": "https://paperless.example.tld/api/documents/post_document/",
"paperless_token_file": "/run/secrets/paperless_token",
"keep_files": false
}Precedence, highest first: CLI flag → config file → built-in default. (The Docker image's environment variables are translated into CLI flags by its entrypoint, so from the app's point of view they are flags; CMDLINE is appended last and therefore wins over them.)
Keys and types are validated at startup against src/type/FileConfig.ts. paper_size and paper_dim are mutually exclusive, as are width/height and paper_size/paper_dim.
The printer no longer lists the app as a destination. HP devices sometimes drop a registered target. Restart the app; if the entry is stale on the printer, clear the registrations and let the app register itself again:
npx node-hp-scan-to clear-registrations --address 192.168.0.5Prefer -n/--name over -a/--address so a changed DHCP lease does not break the link; discover lists the devices currently reachable.
Nothing shows up in "Scan to Computer". In Docker, discovery over multicast does not work on the default network, so the app cannot register itself: pass the printer IP (IP/--address), or attach the container to a macvlan network and use NAME.
Scans land in /tmp. No output directory was configured — set -d/--directory (or DIR). The Docker image defaults to /scan.
Permission denied when writing scans. In Docker, set PUID/PGID to the owner of the mounted volume.
Something else. Re-run with -D/--debug and attach the log to the issue; LOG_FORMAT=json produces structured lines for aggregators.
How to build and run the project's source code:
git clone https://github.com/manuc66/node-hp-scan-to.git
cd node-hp-scan-to
pnpm install
pnpm build
# Start the program with the printer's IP address:
node dist/index.js -a 192.168.1.5
# Or start it with the name of the printer:
# node dist/index.js -n "Officejet 6500 E710n-z"I'm using Visual Studio Code to debug this application, so instead of running tsx, just run code . and press F5 to start debugging.
You may want to set your printers ip or name in .vscode/launch.json
- Capturing the HP driver network traffic in the clear — how the driver/printer exchanges (including HTTPS) are reverse-engineered: native Windows with mitmproxy (IP redirection) and Linux/Wine with the GnuTLS keylog.
- Protocol documentation — the reverse-engineered proprietary HP protocols.
- eSCL protocol documentation.
Thank you so much to everyone who has already supported this project! Your generosity is greatly appreciated, and it motivates me to keep improving and maintaining this project.
If this project has helped you save money or time, or simply made your life easier, you can support me by buying me a cup of coffee:
Thank you for your support!
A special thank you to JetBrains for supporting this project with a free license for their amazing development tools. Their support helps make this project possible.
All product names, logos, and brands are property of their respective owners. HP and Hewlett-Packard are trademarks of HP Inc. All company, product, and service names used in this project are for identification purposes only. Use of these names does not imply any affiliation or endorsement.
All projects · Source on GitHub · npm · Docker Hub · MIT
