[{"data":1,"prerenderedAt":448},["ShallowReactive",2],{"blog-best-practices/input-validation":3},{"id":4,"title":5,"body":6,"category":423,"date":424,"dateModified":424,"description":425,"draft":426,"extension":427,"faq":428,"featured":426,"headerVariant":433,"image":434,"keywords":434,"meta":435,"navigation":436,"ogDescription":437,"ogTitle":434,"path":438,"readTime":439,"schemaOrg":440,"schemaType":441,"seo":442,"sitemap":443,"stem":444,"tags":445,"twitterCard":446,"__hash__":447},"blog/blog/best-practices/input-validation.md","Input Validation Best Practices: Sanitization, Schema Validation, and Security",{"type":7,"value":8,"toc":405},"minimark",[9,16,25,30,33,48,52,55,64,68,71,116,126,130,135,144,148,157,161,170,174,177,186,190,193,202,206,209,218,222,294,317,345,349,352,374,393],[10,11,12],"tldr",{},[13,14,15],"p",{},"The #1 input validation best practice is to never trust user input. Validate on both client (for UX) and server (for security). Use schema validation libraries like Zod, check data types, enforce length limits, and sanitize output. This prevents 79% of injection and data corruption vulnerabilities.",[17,18,19],"quotable-box",{},[20,21,22],"blockquote",{},[13,23,24],{},"\"All input is evil until proven otherwise. Validate everything, trust nothing.\"",[26,27,29],"h2",{"id":28},"the-cardinal-rule-never-trust-user-input","The Cardinal Rule: Never Trust User Input",[13,31,32],{},"Every piece of data from users, URL parameters, cookies, and headers can be malicious:",[34,35,37],"code-block",{"label":36},"Input sources to validate",[38,39,44],"pre",{"className":40,"code":42,"language":43},[41],"language-text","// All of these need validation:\nreq.body          // Form data, JSON payloads\nreq.params        // URL parameters (/users/:id)\nreq.query         // Query strings (?page=1)\nreq.headers       // Headers (authorization, content-type)\nreq.cookies       // Cookies\nformData.get()    // FormData from forms\nevent.target.value // User input in forms\n","text",[45,46,42],"code",{"__ignoreMap":47},"",[26,49,51],{"id":50},"best-practice-1-use-schema-validation-3-min","Best Practice 1: Use Schema Validation 3 min",[13,53,54],{},"Schema validation ensures data matches expected structure and types:",[34,56,58],{"label":57},"Zod schema validation",[38,59,62],{"className":60,"code":61,"language":43},[41],"import { z } from 'zod';\n\n// Define the expected shape\nconst userSchema = z.object({\n  email: z.string().email('Invalid email format'),\n  name: z.string()\n    .min(2, 'Name must be at least 2 characters')\n    .max(100, 'Name must be under 100 characters')\n    .regex(/^[a-zA-Z\\s'-]+$/, 'Name contains invalid characters'),\n  age: z.number()\n    .int('Age must be a whole number')\n    .min(13, 'Must be at least 13 years old')\n    .max(120, 'Invalid age'),\n  website: z.string().url().optional(),\n});\n\n// Validate input\nfunction validateUser(input: unknown) {\n  const result = userSchema.safeParse(input);\n\n  if (!result.success) {\n    return {\n      valid: false,\n      errors: result.error.issues.map(i => ({\n        field: i.path.join('.'),\n        message: i.message,\n      })),\n    };\n  }\n\n  return { valid: true, data: result.data };\n}\n\n// Usage in API route\napp.post('/api/users', (req, res) => {\n  const validation = validateUser(req.body);\n\n  if (!validation.valid) {\n    return res.status(400).json({ errors: validation.errors });\n  }\n\n  // validation.data is now typed and safe to use\n  createUser(validation.data);\n});\n",[45,63,61],{"__ignoreMap":47},[26,65,67],{"id":66},"best-practice-2-validate-on-client-and-server-2-min","Best Practice 2: Validate on Client AND Server 2 min",[13,69,70],{},"Both are necessary for different reasons:",[72,73,74,90],"table",{},[75,76,77],"thead",{},[78,79,80,84,87],"tr",{},[81,82,83],"th",{},"Validation Location",[81,85,86],{},"Purpose",[81,88,89],{},"Can Be Bypassed?",[91,92,93,105],"tbody",{},[78,94,95,99,102],{},[96,97,98],"td",{},"Client-side",[96,100,101],{},"User experience (instant feedback)",[96,103,104],{},"Yes (easily)",[78,106,107,110,113],{},[96,108,109],{},"Server-side",[96,111,112],{},"Security (actual protection)",[96,114,115],{},"No",[117,118,119],"warning-box",{},[13,120,121,125],{},[122,123,124],"strong",{},"Never rely on client-side validation alone."," Attackers can easily bypass JavaScript validation by sending requests directly to your API. Server-side validation is required for security.",[26,127,129],{"id":128},"best-practice-3-validate-specific-types-3-min","Best Practice 3: Validate Specific Types 3 min",[131,132,134],"h3",{"id":133},"email-validation","Email Validation",[34,136,138],{"label":137},"Email validation",[38,139,142],{"className":140,"code":141,"language":43},[41],"const emailSchema = z.string()\n  .email('Invalid email format')\n  .max(254, 'Email too long')\n  .toLowerCase()\n  .trim();\n\n// Or with custom regex for stricter validation\nconst strictEmailSchema = z.string()\n  .regex(\n    /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/,\n    'Invalid email format'\n  );\n",[45,143,141],{"__ignoreMap":47},[131,145,147],{"id":146},"url-validation","URL Validation",[34,149,151],{"label":150},"URL validation (preventing javascript: URLs)",[38,152,155],{"className":153,"code":154,"language":43},[41],"const urlSchema = z.string()\n  .url('Invalid URL format')\n  .refine(\n    (url) => {\n      try {\n        const parsed = new URL(url);\n        return ['http:', 'https:'].includes(parsed.protocol);\n      } catch {\n        return false;\n      }\n    },\n    'Only HTTP and HTTPS URLs allowed'\n  );\n",[45,156,154],{"__ignoreMap":47},[131,158,160],{"id":159},"id-validation","ID Validation",[34,162,164],{"label":163},"ID validation",[38,165,168],{"className":166,"code":167,"language":43},[41],"// UUID validation\nconst uuidSchema = z.string().uuid('Invalid ID format');\n\n// Numeric ID (for SQL databases)\nconst numericIdSchema = z.coerce.number()\n  .int('ID must be an integer')\n  .positive('ID must be positive');\n\n// Slug validation\nconst slugSchema = z.string()\n  .regex(/^[a-z0-9-]+$/, 'Invalid slug format')\n  .min(1)\n  .max(100);\n",[45,169,167],{"__ignoreMap":47},[26,171,173],{"id":172},"best-practice-4-sanitize-for-output-context-2-min","Best Practice 4: Sanitize for Output Context 2 min",[13,175,176],{},"Different contexts require different sanitization:",[34,178,180],{"label":179},"Context-specific sanitization",[38,181,184],{"className":182,"code":183,"language":43},[41],"import DOMPurify from 'dompurify';\n\n// HTML context: escape or sanitize\nfunction sanitizeForHtml(input: string) {\n  // Option 1: Escape (safest, no HTML allowed)\n  return input\n    .replace(/&/g, '&')\n    .replace(//g, '>')\n    .replace(/\"/g, '\"')\n    .replace(/'/g, ''');\n\n  // Option 2: Sanitize (allow some HTML)\n  return DOMPurify.sanitize(input, {\n    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],\n    ALLOWED_ATTR: ['href'],\n  });\n}\n\n// SQL context: use parameterized queries (not string escaping)\n// See database best practices\n\n// URL context: encode\nfunction sanitizeForUrl(input: string) {\n  return encodeURIComponent(input);\n}\n\n// JSON context: stringify handles escaping\nJSON.stringify({ userInput: input });\n",[45,185,183],{"__ignoreMap":47},[26,187,189],{"id":188},"best-practice-5-enforce-length-limits-1-min","Best Practice 5: Enforce Length Limits 1 min",[13,191,192],{},"Prevent denial of service and database issues:",[34,194,196],{"label":195},"Length limit validation",[38,197,200],{"className":198,"code":199,"language":43},[41],"const commentSchema = z.object({\n  content: z.string()\n    .min(1, 'Comment cannot be empty')\n    .max(10000, 'Comment too long (max 10,000 characters)'),\n});\n\nconst fileUploadSchema = z.object({\n  name: z.string().max(255, 'Filename too long'),\n  size: z.number().max(10 * 1024 * 1024, 'File too large (max 10MB)'),\n});\n\n// Also limit array lengths\nconst tagsSchema = z.array(z.string().max(50))\n  .max(20, 'Too many tags (max 20)');\n",[45,201,199],{"__ignoreMap":47},[26,203,205],{"id":204},"best-practice-6-validate-file-uploads-2-min","Best Practice 6: Validate File Uploads 2 min",[13,207,208],{},"Files need special validation:",[34,210,212],{"label":211},"File upload validation",[38,213,216],{"className":214,"code":215,"language":43},[41],"const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];\nconst MAX_SIZE = 5 * 1024 * 1024; // 5MB\n\nfunction validateFile(file: File) {\n  const errors = [];\n\n  // Check MIME type\n  if (!ALLOWED_TYPES.includes(file.type)) {\n    errors.push('Invalid file type. Allowed: JPEG, PNG, WebP');\n  }\n\n  // Check file size\n  if (file.size > MAX_SIZE) {\n    errors.push('File too large. Maximum size: 5MB');\n  }\n\n  // Check file extension matches MIME type\n  const ext = file.name.split('.').pop()?.toLowerCase();\n  const validExtensions = ['jpg', 'jpeg', 'png', 'webp'];\n  if (!ext || !validExtensions.includes(ext)) {\n    errors.push('Invalid file extension');\n  }\n\n  return {\n    valid: errors.length === 0,\n    errors,\n  };\n}\n",[45,217,215],{"__ignoreMap":47},[26,219,221],{"id":220},"common-input-validation-mistakes","Common Input Validation Mistakes",[72,223,224,237],{},[75,225,226],{},[78,227,228,231,234],{},[81,229,230],{},"Mistake",[81,232,233],{},"Risk",[81,235,236],{},"Prevention",[91,238,239,250,261,272,283],{},[78,240,241,244,247],{},[96,242,243],{},"Client-only validation",[96,245,246],{},"Complete bypass",[96,248,249],{},"Always validate server-side",[78,251,252,255,258],{},[96,253,254],{},"Type coercion issues",[96,256,257],{},"Unexpected behavior",[96,259,260],{},"Explicit type checking",[78,262,263,266,269],{},[96,264,265],{},"Missing length limits",[96,267,268],{},"DoS, buffer issues",[96,270,271],{},"Set reasonable maximums",[78,273,274,277,280],{},[96,275,276],{},"Trusting file extensions",[96,278,279],{},"Malicious uploads",[96,281,282],{},"Check MIME type too",[78,284,285,288,291],{},[96,286,287],{},"Accepting javascript: URLs",[96,289,290],{},"XSS attacks",[96,292,293],{},"Whitelist allowed protocols",[295,296,297],"info-box",{},[13,298,299,302,303,310,311,316],{},[122,300,301],{},"External Resources:"," For comprehensive input validation guidelines, see the ",[304,305,309],"a",{"href":306,"rel":307},"https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html",[308],"nofollow","OWASP Input Validation Cheat Sheet"," and the ",[304,312,315],{"href":313,"rel":314},"https://owasp.org/www-community/OWASP_Validation_Regex_Repository",[308],"OWASP Validation Regex Repository",".",[318,319,320,327,333,339],"faq-section",{},[321,322,324],"faq-item",{"question":323},"Which validation library should I use?",[13,325,326],{},"Zod is the most popular choice for TypeScript projects due to its excellent type inference. Yup is also good, especially with Formik. For simple cases, you can use built-in methods, but libraries provide better error messages and composability.",[321,328,330],{"question":329},"Should I sanitize input or output?",[13,331,332],{},"Validate and sanitize input, but also sanitize output based on context. Input validation catches obvious issues early. Output encoding/sanitization prevents injection in the specific context (HTML, SQL, URL) where the data is used.",[321,334,336],{"question":335},"How strict should validation be?",[13,337,338],{},"Be as strict as your use case allows without hurting legitimate users. For security-critical fields (passwords, IDs), be very strict. For user content, balance security with usability. Allow international characters in names, for example.",[321,340,342],{"question":341},"Is TypeScript enough for validation?",[13,343,344],{},"No. TypeScript types exist only at compile time. Runtime data (API requests, form submissions) is not type-checked by TypeScript. You need runtime validation like Zod to ensure data matches expected types.",[26,346,348],{"id":347},"further-reading","Further Reading",[13,350,351],{},"Put these practices into action with our step-by-step guides.",[353,354,355,362,368],"ul",{},[356,357,358],"li",{},[304,359,361],{"href":360},"/blog/how-to/add-security-headers","Add security headers to your app",[356,363,364],{},[304,365,367],{"href":366},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[356,369,370],{},[304,371,373],{"href":372},"/blog/getting-started/first-scan","Run your first security scan",[375,376,377,383,388],"related-articles",{},[378,379],"related-card",{"description":380,"href":381,"title":382},"Secure API design patterns","/blog/best-practices/api-design","API Security",[378,384],{"description":385,"href":386,"title":387},"Secure file handling","/blog/best-practices/file-uploads","File Upload Security",[378,389],{"description":390,"href":391,"title":392},"Frontend validation patterns","/blog/best-practices/react","React Best Practices",[394,395,398,402],"cta-box",{"href":396,"label":397},"/","Start Free Scan",[26,399,401],{"id":400},"verify-your-input-validation","Verify Your Input Validation",[13,403,404],{},"Scan your application for input validation vulnerabilities.",{"title":47,"searchDepth":406,"depth":406,"links":407},2,[408,409,410,411,417,418,419,420,421,422],{"id":28,"depth":406,"text":29},{"id":50,"depth":406,"text":51},{"id":66,"depth":406,"text":67},{"id":128,"depth":406,"text":129,"children":412},[413,415,416],{"id":133,"depth":414,"text":134},3,{"id":146,"depth":414,"text":147},{"id":159,"depth":414,"text":160},{"id":172,"depth":406,"text":173},{"id":188,"depth":406,"text":189},{"id":204,"depth":406,"text":205},{"id":220,"depth":406,"text":221},{"id":347,"depth":406,"text":348},{"id":400,"depth":406,"text":401},"best-practices","2026-01-27","Input validation security best practices. Learn to validate user input, prevent injection attacks, and implement schema validation in JavaScript and TypeScript.",false,"md",[429,430,431,432],{"question":323,"answer":326},{"question":329,"answer":332},{"question":335,"answer":338},{"question":341,"answer":344},"vibe-green",null,{},true,"Secure your application with proper input validation, sanitization, and schema checking.","/blog/best-practices/input-validation","12 min read","[object Object]","Article",{"title":5,"description":425},{"loc":438},"blog/best-practices/input-validation",[],"summary_large_image","73n1NW8OSCrs5cSxPz9V2BYV6FFikdOS1eEoz4Z508E",1775843925939]