apify/crawlee · error · Error
Expected an iterable or async iterable, got ${getObjectType(
Error message
Expected an iterable or async iterable, got ${getObjectType(requests)} What it means
`crawler.addRequests()` accepts a plain iterable or async iterable of requests; anything else (a single object, a string, a promise of an array, etc.) cannot be consumed by the internal batching logic, so it throws after reporting the detected type via getObjectType. The check runs before option parsing so the whole call fails fast.
Source
Thrown at packages/basic-crawler/src/internals/basic-crawler.ts:2071
*
* Optionally, the requests can be filtered using `include`/`exclude` glob or regexp patterns and an
* enqueue `strategy` (both AND-ed together, same as {@apilink CrawlingContext.enqueueLinks|`enqueueLinks`}),
* relative to `baseUrl`. Unlike `enqueueLinks`, there is no implicit "current page" to anchor the strategy
* to, so `strategy` defaults to {@apilink EnqueueStrategy.All|`all`} here.
*
* This is an alias for calling `addRequestsBatched()` on the implicit `RequestQueue` for this crawler instance.
*
* @param requests The requests to add
* @param options Options for the request queue
*/
async addRequests(
requests: ReadonlyDeep<TypedRequestsLike<Routes>>,
options: CrawlerAddRequestsOptions = {},
): Promise<CrawlerAddRequestsResult> {
await this.getRequestManager();
if (!isIterable(requests) && !isAsyncIterable(requests)) {
throw new Error(`Expected an iterable or async iterable, got ${getObjectType(requests)}`);
}
parseArgument(options, addRequestsOptionsSchema, 'EnqueueUrlsOptions');
// `label`/`userData` apply to every request this call produces, so a single upfront validation
// against the label's schema covers them all and fails the whole call fast, rather than failing
// lazily once the generator below is drained. Skipped when neither is set - each item still gets
// its own per-item validation below, and validating an absent label/userData here would spuriously
// check them against a registered default-route schema.
if (options.label !== undefined || options.userData !== undefined) {
await this.validateRequestUserData({ label: options.label, userData: options.userData });
}
const requestLimit = await this.#calculateEnqueuedRequestLimit(options.limit);
const strategy = options.strategy ?? EnqueueStrategy.All;
const urlExcludePatternObjects: UrlPatternObject[] = options.exclude?.length
? constructUrlPatternObjects(options.exclude)View on GitHub (pinned to dbe57fb09c)
Solutions
- Wrap a single request in an array: `addRequests([request])`.
- Await promises before passing: `addRequests(await getRequests())`.
- Convert other collection types (Set/Map values/generator/observable) to an array or async iterable first.
- Check the reported type in the message to see what was actually passed and convert accordingly.
Example fix
// before
await crawler.addRequests({ url: 'https://example.com' });
// after
await crawler.addRequests([{ url: 'https://example.com' }]); Defensive patterns
Strategy: type-guard
Validate before calling
const input = { url: 'https://example.com' };
await crawler.addRequests(Array.isArray(input) ? input : [input]); Type guard
function isRequestsIterable(v) {
return v != null && (typeof v[Symbol.iterator] === 'function' || typeof v[Symbol.asyncIterator] === 'function');
} Try / catch
try {
await crawler.addRequests(maybeSingle);
} catch (err) {
if (err.message.startsWith('Expected an iterable or async iterable')) {
await crawler.addRequests([maybeSingle]);
} else throw err;
} Prevention
- Always pass an array of request objects (or an async generator) to addRequests.
- Await any promise producing requests before calling addRequests.
- Normalize inputs through a small helper: `const asList = (x) => isRequestsIterable(x) ? x : [x];`.
- Type the call site as Request[] | AsyncIterable<Request> so TypeScript rejects single objects.
When it happens
Trigger: Calling `crawler.addRequests(request)` with a single Request object (not wrapped in an array), a plain object, a string URL, a Promise, or any non-iterable value instead of an array/iterable/async iterable of requests.
Common situations: Forgetting to wrap a single request in an array; passing an await-less Promise<Array> or an RxJS stream; migrating code where addRequests previously accepted a single request.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- Cannot create Request from type: ${type}
- Request options are not valid, the 'url' property is not a s
- Request options are not valid, the 'requestsFromUrl' propert
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/d84d409c5dd5f51f.
Report an issue: GitHub.