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 🙏

© 2025 – Pkg Stats / Ryan Hefner

document-uploader

v2.4.1

Published

Web Component for document uploads with QR code mobile integration and real-time sync

Readme

Features

  • Web Component - Works with any framework (React, Vue, Angular, Svelte) or vanilla HTML
  • QR Code Upload - Generate QR codes for mobile document uploads
  • Direct Desktop Upload - Drag & drop or click to upload files
  • Real-time Sync - Files appear instantly via WebSocket
  • Session Persistence - Resume uploads after page refresh
  • Customizable Theme - CSS custom properties for easy styling
  • TypeScript Support - Full type definitions included
  • Lightweight - ~123KB gzipped

Installation

npm

npm install document-uploader

CDN

<!-- ES Module (recommended) -->
<script type="module" src="https://cdn.jsdelivr.net/npm/document-uploader@2/dist/document-uploader.js"></script>

<!-- Or from unpkg -->
<script type="module" src="https://unpkg.com/document-uploader@2/dist/document-uploader.js"></script>

Quick Start

<!DOCTYPE html>
<html>
<head>
  <title>Document Upload</title>
</head>
<body>
  <!-- Just add the Web Component -->
  <document-uploader
    api-key="dk_your_api_key_here"
    max-files="5"
    placeholder="Drop files or click to browse"
  ></document-uploader>

  <!-- Load the component -->
  <script type="module" src="https://cdn.jsdelivr.net/npm/document-uploader@2/dist/document-uploader.js"></script>

  <!-- Listen for events -->
  <script>
    const uploader = document.querySelector('document-uploader');

    uploader.addEventListener('file-uploaded', (e) => {
      console.log('File uploaded:', e.detail);
    });

    uploader.addEventListener('session-created', (e) => {
      console.log('Session ID:', e.detail);
    });

    uploader.addEventListener('error', (e) => {
      console.error('Error:', e.detail);
    });
  </script>
</body>
</html>

Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | api-key | string | required | Your API key from the dashboard | | max-files | number | 10 | Maximum number of files per session | | accepted-types | string | image/*,application/pdf | Accepted MIME types | | placeholder | string | Drop files or click to browse | Dropzone placeholder text | | persist-session | boolean | true | Save session to localStorage | | storage-key | string | documentuploader_session | localStorage key for session | | show-new-session-button | boolean | false | Show button to start new session | | upload-app-url | string | https://documentuploader.app | Mobile upload app URL | | session-id | string | - | External session ID for shared sessions | | category | string | - | Category/field ID to filter uploads (for multi-field forms) | | payload | string | - | Custom JSON payload for associating sessions with external entities (e.g., user_id, order_id) |

Events

file-uploaded

Fired when a file is successfully uploaded (from mobile or desktop).

uploader.addEventListener('file-uploaded', (e) => {
  const file = e.detail;
  console.log('Name:', file.name);
  console.log('URL:', file.url);
  console.log('Source:', file.source); // 'desktop' or 'mobile'
  console.log('Created:', file.createdAt);
});

file-removed

Fired when a file is removed (deleted from storage and database).

uploader.addEventListener('file-removed', (e) => {
  const file = e.detail;
  console.log('Removed:', file.name);
  console.log('ID:', file.id);
});

session-created

Fired when a new upload session is created.

uploader.addEventListener('session-created', (e) => {
  const [sessionId, payload] = e.detail;
  console.log('Session ID:', sessionId);
  console.log('Payload:', payload); // Custom payload if provided
});

session-restored

Fired when a previous session is restored from localStorage.

uploader.addEventListener('session-restored', (e) => {
  const [sessionId, uploads, payload] = e.detail;
  console.log('Session restored:', sessionId);
  console.log('Previous uploads:', uploads);
  console.log('Payload:', payload); // Custom payload if provided
});

error

Fired when an error occurs.

uploader.addEventListener('error', (e) => {
  console.error('Error:', e.detail.message);
});

Theming

Customize the appearance using CSS custom properties:

document-uploader {
  /* Colors */
  --du-accent: #3b82f6;           /* Accent/primary color */
  --du-bg: #ffffff;                /* Background color */
  --du-bg-secondary: #f9fafb;      /* Secondary background */
  --du-border: #e5e7eb;            /* Border color */
  --du-text: #374151;              /* Primary text color */
  --du-text-secondary: #6b7280;    /* Secondary text color */
  --du-success: #10b981;           /* Success color */
  --du-error: #ef4444;             /* Error color */

  /* Appearance */
  --du-radius: 8px;                /* Border radius */
}

Theme Examples

Dark Theme

document-uploader.dark {
  --du-accent: #60a5fa;
  --du-bg: #1f2937;
  --du-bg-secondary: #374151;
  --du-border: #4b5563;
  --du-text: #f3f4f6;
  --du-text-secondary: #9ca3af;
}

Purple Theme

document-uploader.purple {
  --du-accent: #8b5cf6;
  --du-radius: 12px;
}

Minimal Theme

document-uploader.minimal {
  --du-accent: #000000;
  --du-radius: 4px;
  --du-border: #e5e5e5;
}

Framework Integration

React

import { useEffect, useRef } from 'react';
import 'document-uploader';

function UploadComponent({ apiKey, onUpload }) {
  const uploaderRef = useRef(null);

  useEffect(() => {
    const uploader = uploaderRef.current;

    const handleUpload = (e) => onUpload(e.detail);
    uploader.addEventListener('file-uploaded', handleUpload);

    return () => {
      uploader.removeEventListener('file-uploaded', handleUpload);
    };
  }, [onUpload]);

  return (
    <document-uploader
      ref={uploaderRef}
      api-key={apiKey}
      max-files="5"
    />
  );
}

Vue

<template>
  <document-uploader
    :api-key="apiKey"
    max-files="5"
    @file-uploaded="handleUpload"
    @error="handleError"
  />
</template>

<script setup>
import 'document-uploader';

const props = defineProps(['apiKey']);
const emit = defineEmits(['upload']);

const handleUpload = (e) => {
  emit('upload', e.detail);
};

const handleError = (e) => {
  console.error('Error:', e.detail);
};
</script>

Angular

// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import 'document-uploader';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}
<!-- component.html -->
<document-uploader
  [attr.api-key]="apiKey"
  max-files="5"
  (file-uploaded)="onUpload($event)"
></document-uploader>

Svelte

<script>
  import 'document-uploader';

  export let apiKey;

  function handleUpload(e) {
    console.log('Uploaded:', e.detail);
  }
</script>

<document-uploader
  api-key={apiKey}
  max-files="5"
  on:file-uploaded={handleUpload}
/>

Complete Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document Uploader Demo</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      max-width: 600px;
      margin: 40px auto;
      padding: 20px;
    }

    .container {
      background: white;
      padding: 24px;
      border-radius: 12px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }

    /* Custom dark theme */
    document-uploader.dark {
      --du-accent: #60a5fa;
      --du-bg: #1f2937;
      --du-bg-secondary: #374151;
      --du-border: #4b5563;
      --du-text: #f3f4f6;
      --du-text-secondary: #9ca3af;
    }

    .uploads {
      margin-top: 20px;
      padding: 16px;
      background: #f9fafb;
      border-radius: 8px;
    }

    .upload-item {
      display: flex;
      align-items: center;
      gap: 12px;
      padding: 8px;
      background: white;
      border-radius: 6px;
      margin-bottom: 8px;
    }

    .upload-item img {
      width: 40px;
      height: 40px;
      object-fit: cover;
      border-radius: 4px;
    }
  </style>
</head>
<body>
  <h1>Document Uploader</h1>

  <div class="container">
    <document-uploader
      id="uploader"
      api-key="dk_your_api_key_here"
      max-files="5"
      accepted-types="image/*,application/pdf"
      placeholder="Drop files here or click to browse"
      persist-session="true"
      show-new-session-button="true"
    ></document-uploader>
  </div>

  <div class="uploads" id="uploads">
    <h3>Uploaded Files</h3>
    <div id="upload-list"></div>
  </div>

  <script type="module" src="https://cdn.jsdelivr.net/npm/document-uploader@2/dist/document-uploader.js"></script>

  <script>
    const uploader = document.getElementById('uploader');
    const uploadList = document.getElementById('upload-list');

    function addFileToList(file) {
      const div = document.createElement('div');
      div.className = 'upload-item';
      div.innerHTML = `
        ${file.preview ? `<img src="${file.preview}" alt="${file.name}">` : ''}
        <div>
          <strong>${file.name}</strong>
          <div style="font-size: 12px; color: #666;">
            ${file.source.toUpperCase()} - ${new Date(file.createdAt).toLocaleString()}
          </div>
        </div>
      `;
      uploadList.appendChild(div);
    }

    uploader.addEventListener('file-uploaded', (e) => {
      console.log('File uploaded:', e.detail);
      addFileToList(e.detail);
    });

    uploader.addEventListener('session-created', (e) => {
      console.log('New session created:', e.detail);
      uploadList.innerHTML = '';
    });

    uploader.addEventListener('session-restored', (e) => {
      const [sessionId, uploads] = e.detail;
      console.log('Session restored:', sessionId);
      uploads.forEach(addFileToList);
    });

    uploader.addEventListener('error', (e) => {
      console.error('Error:', e.detail);
      alert('Error: ' + e.detail.message);
    });
  </script>
</body>
</html>

TypeScript

Type definitions are included. For custom element types in TypeScript:

// types.d.ts
declare namespace JSX {
  interface IntrinsicElements {
    'document-uploader': React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLElement> & {
        'api-key': string;
        'max-files'?: string;
        'accepted-types'?: string;
        'placeholder'?: string;
        'persist-session'?: string;
        'show-new-session-button'?: string;
      },
      HTMLElement
    >;
  }
}

File Object

The file-uploaded event provides this object:

interface UploadedFile {
  id: string;           // Unique upload ID
  name: string;         // File name
  size: number;         // File size in bytes
  type: string;         // MIME type
  url: string;          // Public URL
  preview?: string;     // Preview URL (for images)
  source: 'desktop' | 'mobile';
  createdAt: Date;
  path?: string;        // Storage path
}

How It Works

  1. Session Creation - Component automatically creates an upload session
  2. QR Code - Click the QR button to show a scannable code
  3. Mobile Upload - Users scan and upload from their phone
  4. Desktop Upload - Drag & drop or click to upload directly
  5. Real-time Sync - Files appear instantly via WebSocket

Security

  • API Key Authentication
  • Rate Limiting per IP
  • Turnstile CAPTCHA for abuse prevention
  • Session-based upload quotas
  • File type validation (client + server)

Browser Support

  • Chrome 67+
  • Firefox 63+
  • Safari 13.1+
  • Edge 79+

Getting Your API Key

  1. Sign up at documentuploader.app
  2. Go to Dashboard > API Keys
  3. Create a new key
  4. Use it in your api-key attribute

Pricing

| Plan | Sessions/Month | Uploads/Session | Price | |------|----------------|-----------------|-------| | Free | 10 | 5 | $0/mo | | Starter | 100 | 20 | $9/mo | | Professional | 1,000 | 50 | $29/mo | | Enterprise | Unlimited | Unlimited | $99/mo |

Support

License

MIT License - see LICENSE file.

Changelog

v2.3.0

  • File Deletion Support
    • Clicking the X button on uploaded files now permanently deletes them
    • Files are removed from both Supabase Storage and the database
    • New file-removed event fired when a file is deleted
    • Deletions persist across page refreshes

v2.2.0

  • Custom Payload Support
    • New payload attribute to associate sessions with external entities (e.g., user_id, order_id)
    • Payload is stored in the database and returned on session creation/restoration
    • Events session-created and session-restored now include the payload
    • New getPayload() method to retrieve the current session's payload
  • Event signature updates
    • session-created now emits [sessionId, payload]
    • session-restored now emits [sessionId, uploads, payload]

v2.1.0

  • Multi-field Mode Support
    • New session-id attribute for shared sessions across multiple uploaders
    • New category attribute to filter uploads per field
    • Enables forms with multiple upload fields (e.g., ID front, ID back, utility bill)
  • Improved session handling
    • External session ID takes precedence over localStorage
    • Watcher for dynamic session ID changes

v2.0.0 (Breaking Change)

  • Complete Rewrite as Web Component
    • No more JavaScript class instantiation
    • Use as HTML element: <document-uploader>
    • Works with any framework or vanilla HTML
    • CSS custom properties for theming
    • Simplified API with HTML attributes
  • Removed
    • Mustache templates (now built-in)
    • Multi-field mode (may return in future version)
    • JavaScript API methods (use DOM events instead)
    • Theme presets (use CSS custom properties)

v1.x (Legacy)

See previous releases for v1.x changelog.