{"record":{"id":"004dc0d76493449c","repo":"apify/crawlee","slug":"an-extracted-url-href-is-relative-and-options","errorCode":null,"errorMessage":"An extracted URL: ${href} is relative and options.baseUrl is not set. Use options.baseUrl in enqueueLinks() to automatically resolve relative URLs.","messagePattern":"An extracted URL: (.+?) is relative and options\\.baseUrl is not set\\. Use options\\.baseUrl in enqueueLinks\\(\\) to automatically resolve relative URLs\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/browser-crawler/src/internals/browser-crawler.ts","lineNumber":907,"sourceCode":"    baseUrl: string,\n): Promise<string[]> {\n    const urls =\n        (await page.$$eval(selector, (linkEls: HTMLLinkElement[]) =>\n            linkEls.map((link) => link.getAttribute('href')).filter((href) => !!href),\n        )) ?? [];\n    const [base] = await page.$$eval('base', (els: HTMLLinkElement[]) => els.map((el) => el.getAttribute('href')));\n    const absoluteBaseUrl = base && tryAbsoluteURL(base, baseUrl);\n\n    if (absoluteBaseUrl) {\n        baseUrl = absoluteBaseUrl;\n    }\n\n    return urls\n        .map((href: string) => {\n            // Throw a meaningful error when only a relative URL would be extracted instead of waiting for the Request to fail later.\n            const isHrefAbsolute = /^[a-z][a-z0-9+.-]*:/.test(href); // Grabbed this in 'is-absolute-url' package.\n            if (!isHrefAbsolute && !baseUrl) {\n                throw new Error(\n                    `An extracted URL: ${href} is relative and options.baseUrl is not set. ` +\n                        'Use options.baseUrl in enqueueLinks() to automatically resolve relative URLs.',\n                );\n            }\n\n            return baseUrl ? tryAbsoluteURL(href, baseUrl) : href;\n        })\n        .filter((href: string | undefined) => !!href);\n}\n","sourceCodeStart":889,"sourceCodeEnd":917,"githubUrl":"https://github.com/apify/crawlee/blob/dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c/packages/browser-crawler/src/internals/browser-crawler.ts#L889-L917","documentation":"`extractUrlsFromPage()` validates each href extracted from the page via a `enqueueLinks` selector. If a href is relative (fails the `^[a-z][a-z0-9+.-]*:` absolute-URL test) and no `baseUrl` option was provided, it throws immediately with a clear message instead of letting the later `Request` constructor fail with a confusing invalid-URL error. `baseUrl` is what tells the crawler how to resolve relative links against the page's origin.","triggerScenarios":"Calling `enqueueLinks({ selector: 'a' })` (or the crawler's link extraction) without `options.baseUrl` on a page whose anchors use relative hrefs like `/about` or `../page.html`. The page's `<base>` tag is absent or itself relative, so no absolute base can be derived.","commonSituations":"Scraping sites that use root-relative or relative hrefs in navigation menus (very common); calling `enqueueLinks` manually from `requestHandler` and forgetting `baseUrl`; pages rendered from string templates or file:// contexts where relative paths abound.","solutions":["Pass `baseUrl` to `enqueueLinks()`: `await enqueueLinks({ baseUrl: request.loadedUrl, ... })` — the loaded request URL is the usual base","Use the convenience `context.enqueueLinks()` inside the requestHandler where `baseUrl` resolution is wired up; on the raw `extractUrlsFromPage` path, always supply the current page URL as third argument","Preprocess hrefs with `new URL(href, pageUrl).href` if you extract links yourself before enqueueing"],"exampleFix":"// before\nawait enqueueLinks({ selector: 'a.product' }); // relative hrefs on page -> throws\n// after\nawait enqueueLinks({\n    selector: 'a.product',\n    baseUrl: request.loadedUrl ?? request.url,\n    strategy: EnqueueStrategy.SameHostname,\n});","handlingStrategy":"validation","validationCode":"// Normalize hrefs before enqueueing and assert a base is available:\nconst base = request.loadedUrl ?? request.url;\nconst hrefs = [...await page.$evalAll('a[href]', els => els.map(e => e.getAttribute('href')))];\nfor (const href of hrefs) {\n    if (!/^[a-z][a-z0-9+.-]*:/i.test(href) && !base) {\n        throw new Error(`Relative href \"${href}\" requires a baseUrl`);\n    }\n}","typeGuard":"function isAbsoluteUrl(href: string): boolean {\n    return /^[a-z][a-z0-9+.-]*:/i.test(href);\n}","tryCatchPattern":"try {\n    await enqueueLinks({ baseUrl: request.loadedUrl });\n} catch (e) {\n    if (e instanceof Error && /is relative and options.baseUrl is not set/.test(e.message)) {\n        log.warning(`Skipping link extraction without baseUrl: ${e.message}`);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Always pass baseUrl (request.loadedUrl) when calling enqueueLinks on arbitrary pages","Prefer context.enqueueLinks() which resolves the base for you","Normalize links with new URL(href, pageUrl) if you extract them manually"],"tags":["enqueue-links","url","relative-url","configuration"],"backgroundTag":"missing-base-url","analyzedSha":"dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c","analyzedAt":"2026-08-30T22:22:28.328Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}