{"record":{"id":"b4290de6efc9f9cf","repo":"apify/crawlee","slug":"the-value-parameter-must-be-a-string-buffer-ar","errorCode":null,"errorMessage":"The \"value\" parameter must be a String, Buffer, ArrayBuffer, TypedArray, or Stream when \"options.contentType\" is specified.","messagePattern":"The \"value\" parameter must be a String, Buffer, ArrayBuffer, TypedArray, or Stream when \"options\\.contentType\" is specified\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/storages/key_value_store.ts","lineNumber":523,"sourceCode":"     * otherwise the crawler process might finish before the value is stored!\n     *\n     * @param key\n     *   Unique key of the record. It can be at most 256 characters long and only consist\n     *   of the following characters: `a`-`z`, `A`-`Z`, `0`-`9` and `!-_.'()`\n     * @param value\n     *   Record data, which can be one of the following values:\n     *    - If `null`, the record in the key-value store is deleted.\n     *    - If no `options.contentType` is specified, `value` can be any JavaScript object and it will be stringified to JSON.\n     *    - If `options.contentType` is set, `value` is taken as is and it must be a `String` or [`Buffer`](https://nodejs.org/api/buffer.html).\n     *   For any other value an error will be thrown.\n     * @param [options] Record options.\n     */\n    async setValue<T>(key: string, value: T | null, options: RecordOptions = {}): Promise<void> {\n        const transaction = activeStorageTransaction();\n\n        parseArgument(key, setValueKeySchema);\n        if (options.contentType && !(typeof value === 'string' || isBuffer(value) || isStream(value))) {\n            throw new Error(\n                'The \"value\" parameter must be a String, Buffer, ArrayBuffer, TypedArray, or Stream when \"options.contentType\" is specified.',\n            );\n        }\n        // The parse result is a fresh copy, so we never update what user passed.\n        const optionsCopy = parseArgument(options, recordOptionsSchema);\n\n        // The whole transaction branch sits *above* the auto-saved cache update below, so a buffered\n        // write touches nothing outside the journal. That cache is shared, process-lifetime frontend\n        // state, so mutating it here would survive a rollback and later be persisted by `persistState`.\n        // The commit replay re-enters this method with no active transaction and updates it then.\n        if (transaction) {\n            if (isStream(value)) {\n                // A stream cannot serve both a read-your-own-writes read and the commit replay. The\n                // transaction is known-active here, so throw directly rather than via the conditional guard.\n                throw operationRejectedInTransaction(\n                    `KeyValueStore.setValue() with a stream value (key \"${key}\")`,\n                    'a stream can only be consumed once, so it cannot be buffered until commit.',\n                );","sourceCodeStart":505,"sourceCodeEnd":541,"githubUrl":"https://github.com/apify/crawlee/blob/dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c/packages/core/src/storages/key_value_store.ts#L505-L541","documentation":"KeyValueStore.setValue() only accepts a non-string content type option when the value is a string, Buffer, stream, or similar binary-serializable value. If you pass options.contentType with a plain object/number/boolean, the library cannot know how to apply that content type, so it throws instead of silently mis-encoding the value.","triggerScenarios":"Calling store.setValue('key', { some: 'object' }, { contentType: 'image/png' }) or any non-string value combined with options.contentType. Only string, Buffer, ArrayBuffer/TypedArray, or Stream values are valid with contentType.","commonSituations":"Trying to store parsed JSON as an image/binary content type; copying a setValue call written for Buffer values and swapping the value for an object; typos where contentType was meant for a different key.","solutions":["Pass a string, Buffer, ArrayBuffer, TypedArray, or Stream as value when specifying options.contentType","If the value is an object, drop options.contentType and let it be JSON-serialized automatically","If you need a custom content type for an object, pre-serialize it yourself: setValue(key, JSON.stringify(obj), { contentType: 'application/json' })"],"exampleFix":"// before\nawait store.setValue('logo', { url: 'x' }, { contentType: 'image/png' });\n// after\nawait store.setValue('logo', await fs.readFile('logo.png'), { contentType: 'image/png' });\n// or for objects, no contentType:\nawait store.setValue('config', { url: 'x' });","handlingStrategy":"type-guard","validationCode":"function assertStorableWithValueType(value, options = {}) {\n  const binary = value == null || typeof value === 'string' || Buffer.isBuffer(value) || value instanceof ArrayBuffer || ArrayBuffer.isView(value) || (value && typeof value.pipe === 'function');\n  if (options.contentType && !binary) throw new TypeError('contentType requires a string/Buffer/TypedArray/Stream value');\n}","typeGuard":"const canHaveContentType = (v) => typeof v === 'string' || Buffer.isBuffer(v) || v instanceof ArrayBuffer || ArrayBuffer.isView(v) || (v && typeof v.pipe === 'function');","tryCatchPattern":"try {\n  await store.setValue(key, value, { contentType });\n} catch (e) {\n  if (e.message.includes('must be a String, Buffer')) {\n    await store.setValue(key, JSON.stringify(value), { contentType: 'application/json' });\n  } else throw e;\n}","preventionTips":["Only set contentType for string/binary values","Let objects be auto-serialized to JSON without contentType","Read the value back with getValue to confirm round-trip"],"tags":["validation","key-value-store","content-type"],"backgroundTag":"invalid-value-type","analyzedSha":"dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c","analyzedAt":"2026-08-30T22:22:28.328Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}