npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lipecode/barcode-scanner-payloadcms

v0.1.1

Published

Barcode scanning for Payload CMS 3 admin fields, with camera, USB keyboard-wedge and manual entry.

Readme

@lipecode/barcode-scanner-payloadcms

Barcode scanning for Payload CMS 3 admin fields. Adds a scan button to an existing text field and fills it with a value read by camera, USB keyboard-wedge reader or manual entry.

Status

Pre-release (0.1.0), published as @lipecode/barcode-scanner-payloadcms. Repository: https://github.com/LipeMachado/barcode-scanner-payloadcms.

  • Payload CMS: 3.91.x (peer ^3.91.0)
  • React: 19+
  • Next.js: the versions officially supported by Payload 3.91
  • Browser support for the camera path: secure context (HTTPS or localhost) and navigator.mediaDevices.getUserMedia; Chromium/Android and Safari/iOS/iPadOS. USB readers work wherever keyboard input works.

Install

pnpm add @lipecode/barcode-scanner-payloadcms

For local development beside this repository (the folder name follows your checkout; the GitHub repo is barcode-scanner-payloadcms):

pnpm add file:../barcode-scanner-payloadcms

Configure

// payload.config.ts
import { buildConfig } from 'payload'
import { barcodeScannerPlugin } from '@lipecode/barcode-scanner-payloadcms'

export default buildConfig({
  collections: [
    {
      slug: 'products',
      fields: [
        { name: 'codeBar', type: 'text', required: true, unique: true },
      ],
    },
  ],
  plugins: [
    barcodeScannerPlugin({
      fields: [
        {
          collection: 'products',
          field: 'codeBar',
          allowManualEntry: true,
          usbTerminator: 'Enter',
          afterScan: { focusField: 'name' },
        },
      ],
    }),
  ],
})

The target field must already exist and must be a text field. The plugin preserves its validation, hooks, access control and other admin components. It only appends an afterInput component and, when allowManualEntry is false, sets admin.readOnly on that field.

After changing the config, run:

pnpm payload generate:importmap

Options

| Option | Type | Default | Description | | --- | --- | --- | --- | | enabled | boolean | true | Disable without removing the plugin. | | fields | FieldOptions[] | required | Fields to enhance. Multiple collections/fields are supported. | | fields[].collection | string | required | Collection slug. | | fields[].field | string | required | Existing text field name. | | fields[].buttonLabel | string | Escanear código de barras | Button label. | | fields[].allowManualEntry | boolean | true | When false, marks the field read-only in the Admin UI. | | fields[].autoStartCamera | boolean | true | Camera starts when the user opens the scanner. | | fields[].facingMode | 'environment' \| 'user' | 'environment' | Preferred camera. | | fields[].mirrorPreview | boolean | true | Mirrors the camera preview horizontally. | | fields[].validation | { minLength?, maxLength?, pattern? } | printable ASCII, 1–256 chars | Client-side guard. Server validation is still required. | | fields[].usbTerminator | 'Enter' \| 'Tab' \| 'Timeout' | 'Enter' | Delimiter sent by the reader. | | fields[].afterScan.focusField | string | – | Focus an input by name after the read. | | fields[].afterScan.submit | boolean | false | Submit the containing form after the read. | | fields[].eventName | string | payload:barcode-scan | Custom event dispatched after a successful read. |

Custom post-scan behaviour

Every successful scan writes the field first, then dispatches a browser event:

window.addEventListener('payload:barcode-scan', (event) => {
  const { code, source, collection, field, path } = (event as CustomEvent).detail
  console.log({ code, source, collection, field, path })
})

For custom screens, import the underlying primitives:

'use client'
import {
  ScannerDialog,
  UsbScannerInput,
  useCameraScanner,
} from '@lipecode/barcode-scanner-payloadcms/client'

<ScannerDialog
  open={open}
  onClose={() => setOpen(false)}
  onScan={(result) => console.log(result.code)}
  onStatusChange={(status, error) => console.log(status, error?.code)}
/>

The app in this repository uses useCameraScanner for its PWA, showing that the scanner primitives are not tied to Payload Admin.

Scanner states

All primitives expose the same status union:

idle, starting, scanning, success, error, cancelled, permission-denied, unavailable.

Errors carry a code: permission-denied, camera-unavailable, camera-in-use, insecure-context, not-supported, invalid-code, unknown. The camera is stopped when the dialog closes, when a read succeeds, when the caller stops it, and on unmount. Duplicate camera results are ignored for 1.5 seconds by default. USB duplicates are ignored for the same window.

Known limitations

  • A USB keyboard-wedge reader is indistinguishable from a person typing. When manual typing must be prevented in the main form, set allowManualEntry: false; the scanner dialog still needs a keyboard input to receive USB scans.
  • Camera access requires a secure context. On iOS, camera access also requires a modern iOS/Safari or a browser using the same WebKit APIs. The OS may suspend the camera when the tab is backgrounded.
  • allowManualEntry: false sets admin.readOnly on the target field. It is a UI restriction, not a security boundary; validate barcodes on the server.
  • The plugin uses useFieldPath from @payloadcms/ui, which Payload marks as experimental. It is used to support nested fields without hard-coding a path.
  • The camera reader uses @zxing/browser; decoding is performed in JavaScript. Very small, curved or damaged barcodes may need better lighting or a USB reader.
  • The plugin does not add server-side uniqueness validation. A barcode field that must be unique needs unique: true in the collection or a custom validation hook.
  • A Payload custom component cannot receive arbitrary client-side callback functions through config. Custom post-scan code runs through the exported primitives (onScan) or the browser event.

Development and tests

pnpm install
pnpm typecheck
pnpm test
pnpm build
pnpm pack:check

pnpm pack:check creates a tarball and verifies the published file list. The host app (facilities-stock) additionally tests the plugin inside a real Payload collection with Playwright, including a real ZXing decode from a fake camera video, USB keyboard input, permission denial and cancellation.

The package is ESM-only, matching Payload 3.

Publishing

npm view @lipecode/barcode-scanner-payloadcms version
pnpm typecheck
pnpm test
pnpm build
pnpm pack:check
npm publish --access public

To release a new version, bump version before publishing. After consumers migrate from the old name, deprecate it instead of deleting it:

npm deprecate @lipecode/barcode-scanner-payload \
  "Renamed to @lipecode/barcode-scanner-payloadcms"

Do not publish without the package owner's explicit approval. After publishing, add the payload-plugin topic to the repository.

License

MIT