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

  1. Wrap a single request in an array: `addRequests([request])`.
  2. Await promises before passing: `addRequests(await getRequests())`.
  3. Convert other collection types (Set/Map values/generator/observable) to an array or async iterable first.
  4. 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

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


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/d84d409c5dd5f51f. Report an issue: GitHub.