import type { FieldHook } from 'payload' /** * Field validation for the favicon upload: Google rejects favicons under 48×48, * so warn the editor at save time if the uploaded icon is too small or not * square. This ENFORCES the requirement instead of silently shipping a favicon * Google won't display. * * Attach to the favicon field's validate (or as a beforeValidate hook on the * Media relationship). Non-blocking by default — returns a warning string that * Payload surfaces; make it throw if you want a hard block. * * Note: dimensions come from the related Media doc (Payload stores width/height * for image uploads), so this checks the resolved upload, not the raw file. */ export const validateFaviconField: FieldHook = async ({ req, value }) => { if (!value) {return value} // no favicon set → nothing to validate (optional field) try { const media = await req.payload.findByID({ id: typeof value === 'object' ? (value as { id: string }).id : value, collection: 'media', depth: 0, }) const width = (media as { width?: number }).width const height = (media as { height?: number }).height const mimeType = (media as { mimeType?: string }).mimeType // SVG scales infinitely — skip size checks. if (mimeType === 'image/svg+xml') {return value} if (typeof width === 'number' && typeof height === 'number') { if (width < 48 || height < 48) { req.payload.logger.warn( `[ipal] Favicon is ${width}×${height}px. Google requires ≥48×48 to ` + `display it in search results. Upload a larger square icon (96 or 192px).`, ) } if (width !== height) { req.payload.logger.warn( `[ipal] Favicon is not square (${width}×${height}). Use a square icon ` + `so it isn't cropped in the browser tab or search results.`, ) } } } catch { // Media lookup failed — don't block the save over a validation warning. } return value }