{"record":{"id":"1817454a4ac87682","repo":"unclecode/crawl4ai","slug":"domain-or-domains-must-be-a-string-or-a-list-of","errorCode":null,"errorMessage":"`domain_or_domains` must be a string or a list of strings.","messagePattern":"`domain_or_domains` must be a string or a list of strings\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"crawl4ai/async_webcrawler.py","lineNumber":1217,"sourceCode":"                params={\"domain\": domain_or_domains}\n            )\n            return await self.url_seeder.urls(\n                domain_or_domains,\n                seeding_config\n            )\n        elif isinstance(domain_or_domains, (list, tuple)):\n            self.logger.info(\n                message=\"Starting URL seeding for {count} domains\",\n                tag=\"SEED\",\n                params={\"count\": len(domain_or_domains)}\n            )\n            # AsyncUrlSeeder.many_urls directly accepts a list of domains and individual params.\n            return await self.url_seeder.many_urls(\n                domain_or_domains,\n                seeding_config\n            )\n        else:\n            raise ValueError(\"`domain_or_domains` must be a string or a list of strings.\")\n\n    async def amap_domain(\n        self,\n        domain: str,\n        config: Optional[DomainMapperConfig] = None,\n        **kwargs,\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n        Discover all URLs under a domain without deep crawling.\n\n        Uses DomainMapper to combine sitemap, Common Crawl, Wayback Machine,\n        certificate transparency, path probing, robots.txt mining, feed discovery,\n        and homepage link extraction.\n\n        Args:\n            domain: Domain to map (e.g., \"example.com\")\n            config: DomainMapperConfig object. kwargs override config fields.\n","sourceCodeStart":1199,"sourceCodeEnd":1235,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/crawl4ai/async_webcrawler.py#L1199-L1235","documentation":"In AsyncWebCrawler's URL-seeding entry point, the domain_or_domains argument must be a str or a list/tuple of str; anything else (None, int, dict) hits the final else branch and raises this ValueError. Valid inputs are forwarded to AsyncUrlSeeder.many_urls().","triggerScenarios":"Calling the seeding helper with domain_or_domains=None because a config value was missing; passing a dict like {'domains': [...]}; passing an int or a single Path object; passing a nested list [['example.com']].","commonSituations":"Programmatic config where the domain field is optional and defaults to None; JSON/YAML configs parsed to dicts instead of lists; refactors that changed the parameter type.","solutions":["Pass a plain string: await crawler.seeding_urls_like('example.com', config).","Or pass a list/tuple of strings: ['example.com', 'example.org'].","Default missing config values to [] and skip the call when empty."],"exampleFix":"# before\n domains = cfg.get('domains')            # may be None -> ValueError\n urls = await crawler.acontent_seeder(domains, seeding_config)\n\n# after\ndomains = cfg.get('domains') or []\nif domains:\n    urls = await crawler.acontent_seeder(domains, seeding_config)","handlingStrategy":"type-guard","validationCode":"def normalize_domains(v):\n    if isinstance(v, str):\n        return [v]\n    if isinstance(v, (list, tuple)) and all(isinstance(d, str) for d in v):\n        return list(v)\n    raise TypeError('domains must be str or list[str]')","typeGuard":"def is_domain_arg(v) -> bool:\n    return isinstance(v, str) or (isinstance(v, (list, tuple)) and all(isinstance(d, str) for d in v))","tryCatchPattern":"try:\n    urls = await crawler.acontent_seeder(domains, seeding_config)\nexcept ValueError as e:\n    if 'must be a string or a list of strings' in str(e):\n        domains = [str(domains)]\n        urls = await crawler.acontent_seeder(domains, seeding_config)\n    else:\n        raise","preventionTips":["Default config domains to [] and skip when empty.","Normalize YAML/JSON domain configs through normalize_domains at load time.","Never pass nested lists or dicts."],"tags":["validation","url-seeder","input-validation"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}