Hi! I’m loving the power of Vercel & I’m super excited to be apart of it’s growth and Development!
So the feature I am working on right now is uploading resumes, coverletters, and additional attachments to Vercel Blob. To do this I’m using client uploads documented here:
https://vercel.com/docs/storage/vercel-blob/client-upload
Here is my upload request to my handler:
const coverLetterBlobData = await upload(coverLetter.name, coverLetter, {
access: 'public',
handleUploadUrl: '/api/blob/upload',
clientPayload: JSON.stringify({
documentType: 'COVER_LETTER',
startupId: startupId,
filename: coverLetter.name,
})
});
const resumeBlobData = await upload(resume.name, resume, {
access: 'public',
handleUploadUrl: '/api/blob/upload',
clientPayload: JSON.stringify({
documentType: 'RESUME',
startupId: startupId,
filename: resume.name,
})
});
Here is my route handler:
import { handleUpload, HandleUploadBody } from '@vercel/blob/client'
import { getServerSession } from 'next-auth/next'
import { prisma } from '@/src/utils/prisma'
import { z } from 'zod'
import type { NextApiResponse, NextApiRequest } from 'next'
export const uploadPayloadSchema = z.object({
startupId: z.string(),
documentType: z.string(),
filename: z.string(),
})
export const config = {
api: {
bodyParser: false,
},
}
export default async function handler(
request: NextApiRequest,
response: NextApiResponse
) {
const readWriteToken = process.env.BLOB_READ_WRITE_TOKEN
if (!readWriteToken) {
return response.status(500).json({ error: 'Missing BLOB_READ_WRITE_TOKEN' })
}
const body = request.body as HandleUploadBody
if (request.method !== 'POST') {
return response.status(405).json({ error: 'Method not allowed' })
}
try {
const session = await getServerSession(request, response, {
callbacks: {
session: async ({ session, token }) => {
if (session?.user) {
session.user.userId = token.sub
}
return session
},
},
})
if (!session?.user?.userId) {
throw new Error('Unauthorized')
}
const jsonResponse = await handleUpload({
body,
request,
onBeforeGenerateToken: async (
pathname,
clientPayload: string | null,
multipart: boolean) => {
const payload = clientPayload ? uploadPayloadSchema.parse(JSON.parse(clientPayload)) : null
const startup = await prisma.startup.findUnique({
where: { id: payload?.startupId },
select: { blobKey: true },
})
const customPathname = `${startup?.blobKey?.slice(0, 32)}/${session.user.userId
}_${payload?.documentType}_${payload?.filename}`;
// Validate user session and authorization here
return {
pathname: customPathname,
callbackUrl: `/api/blob/upload`,
multipart: multipart,
allowedContentTypes: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
maximumSizeInBytes: 1024 * 1024 * 10, // 10MB
tokenPayload:
JSON.stringify(
clientPayload
// ? {
// ...(typeof clientPayload === 'string'
// ? JSON.parse(clientPayload)
// : {}),
// timestamp: Date.now(),
// }
// : { timestamp: Date.now() }
),
}
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
console.log('Upload completed:', blob)
try {
// Ensure tokenPayload is a valid JSON string
if (typeof tokenPayload !== 'string') {
throw new Error('Invalid token payload format')
}
const payload = JSON.parse(tokenPayload) as {
type: 'COVER_LETTER' | 'RESUME'
startupId: string
timestamp: number
}
// Store metadata in your database here
console.log('Token payload:', payload)
} catch (error) {
console.error('Error processing upload:', error)
throw new Error('Failed to process upload')
}
},
})
return response.status(200).json(jsonResponse)
} catch (error) {
console.error('Upload error:', error)
return response.status(400).json({ error: (error as Error).message })
}
}
The biggest hint to the issue is in my payload and the error it returns.
Payload
{
"type": "blob.generate-client-token",
"payload": {
"pathname": "Mobile Engineer - React native.docx",
"callbackUrl": "https://codeshares-jkc4iemd6-brezzy1337s-projects.vercel.app/api/blob/upload",
"clientPayload": "{\"documentType\":\"COVER_LETTER\",\"startupId\":\"cm45ffb890002rsutnuefuhjo\",\"filename\":\"Mobile Engineer - React native.docx\"}",
"multipart": false
}
}
Error
error: "Cannot read properties of undefined (reading 'type')"
So the type is set to blob.generate-client-token
Which I believe is the type set under the OnBeforeGenerationToken part of the handleUpload function.
Here is that part of the code cut out from the whole function:
onBeforeGenerateToken: async (
pathname,
clientPayload: string | null,
multipart: boolean) => {
const payload = clientPayload ? uploadPayloadSchema.parse(JSON.parse(clientPayload)) : null
const startup = await prisma.startup.findUnique({
where: { id: payload?.startupId },
select: { blobKey: true },
})
const customPathname = `${startup?.blobKey?.slice(0, 32)}/${session.user.userId
}_${payload?.documentType}_${payload?.filename}`;
// Validate user session and authorization here
return {
pathname: customPathname,
callbackUrl: `/api/blob/upload`,
multipart: multipart,
allowedContentTypes: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
maximumSizeInBytes: 1024 * 1024 * 10, // 10MB
tokenPayload:
JSON.stringify(
clientPayload
// ? {
// ...(typeof clientPayload === 'string'
// ? JSON.parse(clientPayload)
// : {}),
// timestamp: Date.now(),
// }
// : { timestamp: Date.now() }
),
}
},
Any help or insights on how vercel blob generates token would be very appreciated since I’m pretty sure its that part of my config that is throwing the error.
Thank you!