document-uploader
v2.4.1
Published
Web Component for document uploads with QR code mobile integration and real-time sync
Maintainers
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-uploaderCDN
<!-- 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
- Session Creation - Component automatically creates an upload session
- QR Code - Click the QR button to show a scannable code
- Mobile Upload - Users scan and upload from their phone
- Desktop Upload - Drag & drop or click to upload directly
- 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
- Sign up at documentuploader.app
- Go to Dashboard > API Keys
- Create a new key
- Use it in your
api-keyattribute
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
- Website: documentuploader.app
- NPM: npmjs.com/package/document-uploader
- Email: [email protected]
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-removedevent fired when a file is deleted - Deletions persist across page refreshes
v2.2.0
- Custom Payload Support
- New
payloadattribute 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-createdandsession-restorednow include the payload - New
getPayload()method to retrieve the current session's payload
- New
- Event signature updates
session-creatednow emits[sessionId, payload]session-restorednow emits[sessionId, uploads, payload]
v2.1.0
- Multi-field Mode Support
- New
session-idattribute for shared sessions across multiple uploaders - New
categoryattribute to filter uploads per field - Enables forms with multiple upload fields (e.g., ID front, ID back, utility bill)
- New
- 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.
