apify/crawlee · error · Error
At most one of `body`, `form` and `json` may be specified in
Error message
At most one of `body`, `form` and `json` may be specified in sendRequest arguments
What it means
processHttpRequestOptions() converts sendRequest/HttpCrawler request options into a HttpRequest. Only one request payload encoding is allowed: raw `body`, `form`, or `json`. When more than one of these options is defined (not `undefined`), the function aborts because it cannot decide which serialization to use (body stream vs urlencoded vs JSON.stringify) and which content-type to set.
Source
Thrown at packages/http-crawler/src/internals/utils.ts:29
/**
* Converts {@apilink HttpRequestOptions} to a {@apilink HttpRequest}.
*/
export function processHttpRequestOptions({
searchParams,
form,
json,
username,
password,
...request
}: HttpRequestOptions): HttpRequest {
const url = new URL(request.url);
const headers = new Headers(request.headers);
applySearchParams(url, searchParams);
if ([request.body, form, json].filter((value) => value !== undefined).length > 1) {
throw new Error('At most one of `body`, `form` and `json` may be specified in sendRequest arguments');
}
const body = (() => {
if (form !== undefined) {
return Readable.from(new URLSearchParams(form).toString());
}
if (json !== undefined) {
return Readable.from(JSON.stringify(json));
}
if (request.body !== undefined) {
return Readable.from(request.body);
}
return undefined;
})();
View on GitHub (pinned to dbe57fb09c)
Solutions
- Keep exactly one payload option: delete or set to `undefined` the others before calling.
- If you need a custom payload, put it in `body` (as string/Buffer/stream) and set the Content-Type header yourself instead of combining with `form`/`json`.
- Check merged/spread option objects so a previous `json`/`form` is not retained alongside `body`.
- Use `null`-safe checks in your own code — note the library only skips `undefined`, so pass `undefined`, not `null`, for absent options.
Example fix
// before
await crawler.sendRequest({ url, json: { a: 1 }, body: 'raw-data' });
// after
await crawler.sendRequest({ url, json: { a: 1 } });
// or for raw payloads:
await crawler.sendRequest({ url, body: 'raw-data', headers: { 'content-type': 'text/plain' } }); Defensive patterns
Strategy: validation
Validate before calling
const payloadKeys = ['body', 'form', 'json'].filter((k) => opts[k] !== undefined);
if (payloadKeys.length > 1) {
throw new Error(`Only one of body/form/json allowed, got: ${payloadKeys.join(', ')}`);
} Type guard
function hasSinglePayload(o: { body?: unknown; form?: unknown; json?: unknown }): boolean {
return ['body', 'form', 'json'].filter((k) => o[k as keyof typeof o] !== undefined).length <= 1;
} Try / catch
try {
await crawler.sendRequest(opts);
} catch (err) {
if ((err as Error).message.includes('At most one of `body`')) {
logger.error('Conflicting payload options', { opts });
}
throw err;
} Prevention
- Build request options through a single helper that enforces one payload key.
- Always use `undefined` (never `null`) to mark absent options.
- When merging/spreading option objects, explicitly delete stale body/form/json keys.
- Write a unit test per payload combination used in your project.
When it happens
Trigger: Calling sendRequest / HttpCrawler / crawler.run with e.g. both `json` and `body` set, both `form` and `json` set, or a request object that already carries `body` while the caller also passes `json` or `form`. Values of `null` count as defined — only `undefined` is treated as absent.
Common situations: Migrating code that sets a default JSON payload and then adds a raw body override; building request options dynamically and forgetting to clear a previously assigned `form`; spreading merged option objects where two payload keys survive; assuming `null` means 'not set'.
Related errors
- Request with GET method cannot have a payload.
- Dataset.forEach/map/reduce() support only a "json" format.
- Request options are not valid, the 'id' property must not be
- Failed to infer format from the path: '${path}'. Supported f
- Unsupported format: '${format}'. Use one of ${supportedForma
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/47f9e6ed9ea82313.
Report an issue: GitHub.