Every day, thousands of people upload PDFs to websites — job applications, academic submissions, government forms, business proposals. And every day, some of those uploads fail. Not because the PDF is wrong, but because the uploader didn't know the PDF had issues.
Building a browser-based PDF upload checker lets users validate their files locally before submitting them anywhere. Here's how to build one with Vue 3 and pdf-lib.
The problem with post-upload validation
Most platforms validate PDFs after you've already uploaded them. The workflow is:
- Upload PDF
- Wait for server processing
- Get error: "File too large" or "Invalid PDF structure"
- Fix the issue
- Re-upload
- Repeat
This is slow, frustrating, and wastes bandwidth. A client-side checker lets you validate before the upload, eliminating the retry loop.
The stack
- Vue 3 with Composition API
- pdf-lib for PDF parsing and analysis
- Vite for bundling
The core implementation
<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument } from 'pdf-lib'
const file = ref<File | null>(null)
const analysis = ref<UploadCheckResult | null>(null)
const checking = ref(false)
const error = ref<string | null>(null)
interface UploadCheckResult {
fileName: string
fileSize: number
fileSizeFormatted: string
pageCount: number
isEncrypted: boolean
hasPassword: boolean
hasBlankPages: boolean
blankPageIndices: number[]
hasCorruptedPages: boolean
corruptedPageIndices: number[]
hasUnsupportedFonts: boolean
isReady: boolean
issues: string[]
}
async function checkPdf() {
if (!file.value) return
checking.value = true
error.value = null
try {
const arrayBuffer = await file.value.arrayBuffer()
const pdf = await PDFDocument.load(arrayBuffer, {
ignoreEncryption: true,
updateMetadata: false,
})
const pageCount = pdf.getPageCount()
const isEncrypted = pdf.isEncrypted()
const hasPassword = false // pdf-lib can't detect password requirement directly
let blankPages: number[] = []
let corruptedPages: number[] = []
for (let i = 0; i < pageCount; i++) {
try {
const page = pdf.getPage(i)
// Check if page has any content
const content = page.getTextContent?.() ?? []
if (content.length === 0) {
// Page might be blank — verify by checking size
const { width, height } = page.getSize()
if (width === 0 || height === 0) {
corruptedPages.push(i + 1)
} else {
blankPages.push(i + 1)
}
}
} catch {
corruptedPages.push(i + 1)
}
}
const issues: string[] = []
if (isEncrypted) issues.push('PDF is encrypted')
if (blankPages.length > 0) issues.push(`${blankPages.length} blank page(s): ${blankPages.join(', ')}`)
if (corruptedPages.length > 0) issues.push(`${corruptedPages.length} corrupted page(s): ${corruptedPages.join(', ')}`)
analysis.value = {
fileName: file.value.name,
fileSize: file.value.size,
fileSizeFormatted: formatFileSize(file.value.size),
pageCount,
isEncrypted,
hasPassword,
hasBlankPages: blankPages.length > 0,
blankPageIndices: blankPages,
hasCorruptedPages: corruptedPages.length > 0,
corruptedPageIndices: corruptedPages,
hasUnsupportedFonts: false, // Would need font analysis for this
isReady: issues.length === 0,
issues,
}
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to analyze PDF'
} finally {
checking.value = false
}
}
</script>
Key implementation details
1. ignoreEncryption: true
When loading a potentially problematic PDF, we set ignoreEncryption: true to prevent the loader from failing on encryption metadata. If the PDF is encrypted, we detect this separately via pdf.isEncrypted() rather than letting the load fail.
2. Blank page detection
pdf-lib doesn't have a built-in "is this page blank?" method. We approximate by checking if a page has any text content and has non-zero dimensions:
const content = page.getTextContent?.() ?? []
if (content.length === 0) {
const { width, height } = page.getSize()
if (width === 0 || height === 0) {
corruptedPages.push(i + 1)
} else {
blankPages.push(i + 1)
}
}
This isn't perfect — a page could have images but no text and still appear blank to this check. For production use, rendering to canvas and checking pixel content is more accurate but more expensive.
3. Corrupted page detection
If getPage(i) throws an exception, that page is corrupted or unreadable. We catch these individually so one bad page doesn't prevent analysis of the rest:
for (let i = 0; i < pageCount; i++) {
try {
const page = pdf.getPage(i)
// ... check page content
} catch {
corruptedPages.push(i + 1)
}
}
4. File size formatting
Human-readable file sizes are important for the report:
function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
}
5. What we can't check client-side
Some validation requires server-side analysis:
- Exact page count limits (we can count pages, but the platform's limit is unknown)
- Font compatibility (requires deep font analysis)
- PDF/A compliance (requires professional validation)
- Platform-specific rules (each platform has different requirements)
The tool should communicate these limitations clearly to the user.
Summary
Building a browser-based PDF upload checker involves:
- Loading the PDF with error-tolerant settings
- Analyzing file size, page count, encryption status
- Checking each page for blank or corrupted content
- Generating a clear issue report
- Presenting a readiness verdict
The result: users can validate their PDFs locally before uploading, saving time and avoiding submission errors. Try it at en.sotool.top/pdf-upload-checker.
Top comments (0)