{"record":{"id":"de9119eac2af1693","repo":"remotion-dev/remotion","slug":"name-must-be-an-integer-but-got-json-strin-de9119","errorCode":null,"errorMessage":"\"${name}\" must be an integer, but got ${JSON.stringify(value)}","messagePattern":"\"(.+?)\" must be an integer, but got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/effects/src/pattern.ts","lineNumber":241,"sourceCode":"\t\treturn;\n\t}\n\n\tif (\n\t\t!Array.isArray(value) ||\n\t\tvalue.length !== 2 ||\n\t\tvalue.some((item) => typeof item !== 'number' || !Number.isFinite(item))\n\t) {\n\t\tthrow new TypeError(`\"${name}\" must be a [number, number] tuple`);\n\t}\n};\n\nconst assertOptionalInteger = (value: unknown, name: string): void => {\n\tif (value === undefined) {\n\t\treturn;\n\t}\n\n\tif (!Number.isInteger(value)) {\n\t\tthrow new TypeError(\n\t\t\t`\"${name}\" must be an integer, but got ${JSON.stringify(value)}`,\n\t\t);\n\t}\n};\n\nconst validatePositive = (value: number, name: string): void => {\n\tif (value <= 0) {\n\t\tthrow new TypeError(`\"${name}\" must be > 0`);\n\t}\n};\n\nconst validateAtLeast = (value: number, min: number, name: string): void => {\n\tif (value < min) {\n\t\tthrow new TypeError(\n\t\t\t`\"${name}\" must be >= ${min}, but got ${JSON.stringify(value)}`,\n\t\t);\n\t}\n};","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/effects/src/pattern.ts#L223-L259","documentation":"pattern() rejects non-integer values for props that are semantically whole-pixel counters (the row/column offset 'every' divisors and similar integer-step fields). assertOptionalInteger throws when such a prop is defined but not an integer, so fractional values cannot silently corrupt the tiling math.","triggerScenarios":"Calling pattern() with rowOffsetEvery, columnOffsetEvery, rowOffset, columnOffset, gapX, gapY, or a crop prop set to a non-integer such as 2.5, or a numeric string like '3'. Validation runs in validatePatternParams before the effect renders.","commonSituations":"Driving integer props from a slider/animation that produces fractional values; reading counts from computed floats without rounding; passing strings from URL/config params.","solutions":["Round the value before passing: Math.round(x) or Math.trunc(x).","Use the prop only where a fractional value is allowed (offsetU/offsetV accept floats); keep integer fields whole.","Coerce untyped config inputs with Number() then validate with Number.isInteger yourself first."],"exampleFix":"// before\npattern({ rowOffsetEvery: 2.5 })\n\n// after\npattern({ rowOffsetEvery: Math.round(animatedEvery) })","handlingStrategy":"validation","validationCode":"// Validate integer props before calling pattern().\nfunction asInt(v: unknown, name: string): number | undefined {\n  if (v === undefined) return undefined;\n  const n = Number(v);\n  if (!Number.isInteger(n)) throw new Error(`${name} must be an integer`);\n  return n;\n}\npattern({\n  rowOffsetEvery: asInt(raw.rowOffsetEvery, 'rowOffsetEvery'),\n  columnOffsetEvery: asInt(raw.columnOffsetEvery, 'columnOffsetEvery'),\n});","typeGuard":"const isOptionalInteger = (v: unknown): v is number =>\n  v === undefined || (typeof v === 'number' && Number.isInteger(v));","tryCatchPattern":null,"preventionTips":["Round slider/animation outputs with Math.round before passing to integer props.","Keep a typed config schema that rejects fractional values at the boundary.","Route string inputs through Number() and an integer check before pattern().","Reserve float values for props that accept them (offsetU/offsetV), not the integer fields."],"tags":["validation","typescript","pattern-effect","integer","params"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}