Everything you need to integrate pre-upload quality validation into your application.
npm install blurry-check
Privacy-first design
'use client'.The package requires a browser environment with canvas support. For Node.js server-side use, install the optional canvas peer dependency.
npm install canvas # optional, for Node.js environments
The fastest way to validate an upload before it reaches your server:
import { validateUpload } from 'blurry-check'
const input = document.querySelector('input[type="file"]')
input.addEventListener('change', async (e) => {
const file = e.target.files[0]
// All analysis runs client-side — the file never leaves the browser
const result = await validateUpload(file, {
mode: 'document',
strictness: 'high',
minScore: 75,
})
if (!result.ok) {
showError(result.message) // "Image is blurry and too dark."
showTips(result.recommendations) // Specific fix suggestions
return
}
uploadToServer(file)
})The main class. Create one instance and reuse it across multiple file checks.
import { BlurryCheck } from 'blurry-check'
const checker = new BlurryCheck({
method: 'both',
edgeWidthThreshold: 0.3,
laplacianThreshold: 150,
debug: false,
})
const isBlurry = await checker.isImageBlurry(imageFile)
const analysis = await checker.analyzeImage(imageFile)
const pdfResult = await checker.analyzePDF(pdfFile)
const validation = await checker.validateUpload(file, { preset: 'general' })
}Low-level blur detection. Exposes edge detection and OpenCV Laplacian methods directly.
import { BlurDetector } from 'blurry-check'
const detector = new BlurDetector({ method: 'both' })
const result = await detector.analyzeImage(imageInput)
console.log(result.isBlurry, result.confidence, result.metrics)Full PDF quality analysis with multi-scale rendering, text sharpness, and document frame detection.
import { PDFAnalyzer } from 'blurry-check'
const analyzer = new PDFAnalyzer({ method: 'edge', edgeWidthThreshold: 0.25 })
const result = await analyzer.analyzePDF(pdfFile)
console.log(result.isQualityGood, result.isScanned, result.pagesAnalyzed)High-level upload validation with friendly user feedback.
import { validateImage, validateUpload } from 'blurry-check'
// Image-only validation
const imageResult = await validateImage(imageInput, {
preset: 'profile-photo',
minWidth: 400,
minHeight: 400,
})
// Auto-detect image or PDF
const uploadResult = await validateUpload(file, {
preset: 'document-scan',
minScore: 75,
})All available configuration options for BlurryCheck and the validator functions.
| Property | Type | Default |
|---|---|---|
| method | "edge" | "laplacian" | "both" | "both" |
| edgeWidthThreshold | number | 0.3 |
| laplacianThreshold | number | 150 |
| openCvUrl | string | OpenCV CDN |
| canvas | HTMLCanvasElement | auto-created |
| debug | boolean | false |
| minScore | number | 70 |
| minWidth | number | 600 |
| minHeight | number | 600 |
| maxWidth | number | none |
| maxHeight | number | none |
| maxSizeMB | number | 10 |
| mode | ValidationMode | 'general' |
| strictness | "low" | "medium" | "high" | 'medium' |
| maxPages | number | no limit |
| samplePages | "first" | "all" | "smart" | number[] | "all" |
| maxRenderScale | number | 2.0 |
| timeoutMs | number | 30000 |
Modes apply calibrated defaults for common upload workflows. Use strictness to shift thresholds per mode. The deprecated preset option is mapped to the closest mode internally.
| Property | Type | Default |
|---|---|---|
| general | mode | — |
| document | mode | — |
| receipt | mode | — |
| invoice | mode | — |
| id-card | mode | — |
| passport | mode | — |
| profile-photo | mode | — |
| ocr | mode | — |
| ai-input | mode | — |
Each image check returns its own QualityCheckResult with status, score, and a user-facing message.
| Property | Type | Default |
|---|---|---|
| blur | ||
| brightness | ||
| contrast | ||
| resolution | ||
| fileSize | ||
| format |
PDF validation includes per-page metrics and document-level checks.
| Property | Type | Default |
|---|---|---|
| scanned | ||
| sharpness | ||
| textDensity | ||
| orientation | ||
| corruptedPages | ||
| mobileCapture | ||
| pageResolution | ||
| brightness | ||
| contrast |
import { validateImage } from 'blurry-check'
async function handleProfilePhoto(file: File) {
// Runs client-side — file never leaves the browser
const result = await validateImage(file, {
mode: 'profile-photo',
strictness: 'high',
minWidth: 400,
minHeight: 400,
maxSizeMB: 8,
})
if (!result.ok) {
return { error: result.message, tips: result.recommendations }
}
return { success: true }
}import { validateUpload } from 'blurry-check'
async function handleDocument(file: File) {
const result = await validateUpload(file, {
mode: 'document',
strictness: 'high',
minScore: 78,
})
if (!result.ok) {
return { error: result.message, issues: result.issues }
}
return { success: true }
}import { validateUpload } from 'blurry-check'
async function handleReceipt(file: File) {
const result = await validateUpload(file, {
mode: 'receipt',
strictness: 'medium',
minScore: 72,
})
if (!result.ok) {
showError(result.message)
showRecommendations(result.recommendations)
return
}
uploadReceipt(file)
}import { validateImage } from 'blurry-check'
async function handleIDCard(file: File) {
const result = await validateImage(file, {
mode: 'id-card',
strictness: 'high',
minScore: 80,
})
if (!result.ok) {
return { rejected: true, reason: result.message }
}
return { accepted: true }
}import { validateUpload } from 'blurry-check'
async function preflightPDF(file: File) {
const result = await validateUpload(file, {
mode: 'ocr',
strictness: 'high',
samplePages: 'smart',
timeoutMs: 10000,
})
if (result.type !== 'pdf') {
return { error: 'Not a PDF file' }
}
// Check for issues that would block OCR
const ocrBlockers = result.issues.filter(i =>
['blurry', 'too_dark', 'low_contrast', 'rotated', 'corrupted_page'].includes(i)
)
return {
ready: ocrBlockers.length === 0,
score: result.score,
issues: result.issues,
pages: result.pages,
}
}