Puppeteer goto() Options Explained
Learn what Puppeteer’s page.goto() options do, how to choose wait conditions and timeouts, and how to handle responses, redirects, and common navigation issues.
page.goto(url, options) navigates a Puppeteer page or frame to a URL. Its options control when Puppeteer considers the navigation complete, how long it waits, whether the call can be cancelled, and optional referrer metadata. The method resolves to the final navigation response, or null for about:blank and same-URL hash-only navigation. A resolved promise does not necessarily mean the HTTP status was successful: inspect the response status when that matters.
This guide follows the official Puppeteer Page.goto() reference and its GoToOptions and WaitForOptions references. Those pages may describe different Puppeteer releases; confirm details against the version installed in your project.
1. The basic call and its return value
const response = await page.goto('https://example.com');
The URL should include a scheme such as https://. The documented signature is goto(url: string, options?: GoToOptions): Promise<HTTPResponse | null>. If redirects occur, the resolved response represents the last redirect in the chain.
Check the response before treating a navigation as an HTTP success:
const response = await page.goto('https://example.com', {
waitUntil: 'load',
timeout: 30_000,
});
if (response === null) {
console.log('Navigation had no main-resource response');
} else {
console.log('Final URL:', response.url());
console.log('HTTP status:', response.status());
console.log('HTTP success:', response.ok());
}
This distinction matters in headless shell mode: documented valid HTTP statuses such as 404 and 500 do not cause goto() to throw. The response status is separate from whether the navigation wait completed.
2. Every goto() option
GoToOptions extends WaitForOptions. That means goto() accepts the inherited wait controls as well as its referrer-related fields.
waitUntil: choose a navigation lifecycle condition
waitUntil controls which browser lifecycle event or events Puppeteer waits for. The default is 'load'. It accepts one event or an array; when you provide an array, all listed events must fire before the wait succeeds.
| Value | When it fits | What to keep in mind |
|---|---|---|
'load' |
Use the default when the page’s load event is an appropriate navigation boundary. | It does not prove a client-rendered widget or app state is ready. |
'domcontentloaded' |
Use when the document has been parsed and you plan to wait for a more specific condition next. | It may be too early for content that appears after scripts run. |
'networkidle0' or 'networkidle2' |
Use when the corresponding network-idle lifecycle condition suits the site and task. | Network activity is not the same as application readiness; persistent requests can also make idleness a poor fit. |
| An array of events | Use when every listed lifecycle event must occur. | All entries must fire, so combining conditions can lengthen the wait or time out on pages that do not reach one of them. |
Use the event names supported by the Puppeteer version in your project. Lifecycle events describe browser navigation progress; they are not interchangeable with a selector appearing or data becoming usable.
timeout: bound the wait
The documented default is 30000 milliseconds. Set a per-navigation value when one page needs a different limit. A value of 0 disables this timeout, which means this wait no longer has that time bound.
await page.goto('https://example.com/report', {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
You can also set the default navigation timeout on the page. It applies to goto() and related navigation methods such as goBack(), goForward(), reload(), setContent(), and waitForNavigation().
page.setDefaultNavigationTimeout(45_000);
await page.goto('https://example.com/report');
page.setDefaultTimeout() can also change the default timeout used by Puppeteer waits. Prefer a per-call timeout when only one navigation needs a different limit; use a page default when the same navigation bound is appropriate for the page’s work.
signal: cancel a navigation wait
Pass an AbortSignal to cancel the call when your application no longer needs to wait for it. For example, an AbortController can impose a cancellation deadline independently of the normal navigation timeout.
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10_000);
try {
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
signal: controller.signal,
});
} finally {
clearTimeout(timer);
}
Handle cancellation in the surrounding code if it is an expected outcome. Do not assume it indicates a server error.
referer and referrerPolicy: supply referrer metadata
referer sets the referer value for this navigation. If supplied, it takes precedence over the referer header configured with page.setExtraHTTPHeaders(). referrerPolicy supplies the navigation’s referrer policy.
await page.goto('https://example.com/landing', {
referer: 'https://example.org/source',
referrerPolicy: 'strict-origin-when-cross-origin',
});
Use valid values for the Puppeteer version and browser environment you target. These fields affect referrer metadata; they do not change the navigation’s readiness condition.
3. Choose the right readiness check
Pick the condition that matches what your script needs to do next:
- Need the browser navigation lifecycle to reach a point? Set
waitUntil, such as'domcontentloaded'or'load'. - Need a particular interface element? Navigate, then wait for its selector with
page.waitForSelector(). - Need a period with little network activity? Use
page.waitForNetworkIdle()when network idleness is the actual requirement. - Need to know whether the HTTP response succeeded? Check the returned response and its status.
Example: wait for a page-specific element after navigation rather than treating a lifecycle event as proof that the interface is ready.
const response = await page.goto('https://example.com/dashboard', {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
if (response && !response.ok()) {
throw new Error(`Navigation returned HTTP ${response.status()}`);
}
await page.waitForSelector('[data-testid="dashboard-ready"]', {
visible: true,
timeout: 15_000,
});
waitForSelector() can wait for a selector to appear and supports visible or hidden conditions. waitForNetworkIdle() is a separate wait that waits at least its configured idle time. Choose the condition that represents readiness for your task; network idleness and application readiness are different things.
Wait for navigation caused by a click without a race
If a click triggers navigation, start waiting for navigation and click together. Starting a separate wait after the click can miss a fast navigation.
const [response] = await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.click('a.next-page'),
]);
console.log('Navigation response:', response?.status() ?? 'none');
The response can still be null for navigation cases that have no main-resource response, so handle that possibility when your code depends on it.
4. Complete runnable Node.js example
This example launches Puppeteer, navigates to a URL, checks the final HTTP status where a response exists, waits for an optional application selector, and closes the browser even if a step fails. Install Puppeteer in your project with npm install puppeteer, save the code as goto-options.js, then run node goto-options.js.
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
page.setDefaultNavigationTimeout(30_000);
const response = await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30_000,
referrerPolicy: 'strict-origin-when-cross-origin',
});
if (response === null) {
console.log('No main-resource response for this navigation.');
} else {
console.log('Final URL:', response.url());
console.log('Status:', response.status());
console.log('HTTP success:', response.ok());
if (!response.ok()) {
throw new Error(`Unexpected HTTP status ${response.status()}`);
}
}
await page.waitForSelector('body', {
visible: true,
timeout: 10_000,
});
console.log('Page body is present.');
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The status check is deliberate: navigation completion and an HTTP 2xx response are separate concerns. Replace the example selector and URL with the condition your own task requires.
5. Troubleshooting common navigation problems
| Symptom | Likely cause | What to do |
|---|---|---|
Navigation timeout of 30000 ms exceeded |
The selected lifecycle condition did not occur within the timeout, or the default 30-second bound is too short for this page. | Choose a lifecycle condition that matches the task, set a suitable per-call or page-level timeout, and wait separately for an application selector if that is the real readiness requirement. Avoid setting timeout: 0 unless an unbounded wait is intended. |
goto() resolves but the page is an error page or not the expected content |
A fulfilled navigation does not itself establish that the HTTP status was successful; a redirect may also have led somewhere unexpected. | Inspect response.status() and response.url() when a response exists. |
The response is null |
The navigation was to about:blank or the same URL with only a hash change. |
Do not call response methods without checking for null. Handle the no-main-resource-response case explicitly. |
| The script continues before an app widget is ready | A navigation lifecycle event fired before the application-specific element or state was available. | Wait for the relevant selector or condition after navigation. Use waitForSelector() for a selector-based requirement. |
| A network-idle wait never completes | The page may keep network requests active, or network idleness may not represent the task’s actual readiness condition. | Use a lifecycle event or selector/state wait that fits the task. Treat waitForNetworkIdle() as a distinct network condition. |
| The click happened but navigation was not observed | The click and navigation wait may have been started in the wrong order, creating a race. | Start waitForNavigation() and the click together with Promise.all(). |
| A 404 or 500 did not throw | In headless shell, valid HTTP error statuses such as these do not make goto() throw. |
Check the returned response status explicitly. |
| Navigation to a PDF document fails in headless shell | Headless shell does not support navigation to a PDF document. | Account for that environment limitation and use a PDF handling approach appropriate to your setup. |
| Changing the page default did not affect this call as expected | A per-call timeout or another relevant default may be in effect; setDefaultNavigationTimeout() and setDefaultTimeout() govern related waits. |
Set the intended navigation bound explicitly on the call, or configure the appropriate page default. Confirm the installed version’s API reference. |
6. Performance, reliability, and cost considerations
- Choose the earliest useful condition. Waiting for
'load'is the default, but a task that only needs the parsed document or one known element may be able to use a more specific next step. The right choice depends on the page and what the script does after navigation. - Keep waits bounded. A finite timeout makes a stalled navigation a handleable failure. Disabling the timeout with
0removes this bound, so use it only when the surrounding system provides another way to stop the work. - Wait for the state you use. A selector wait can be more meaningful than network idleness when the task depends on a particular interface element. Network activity and app readiness are different signals.
- Check outcomes, not just exceptions. Inspect the response status and final URL where applicable; a completed navigation may still lead to an HTTP error status or an unexpected destination.
- Make cleanup reliable. Close the browser in a
finallyblock so an exception during navigation or a later wait does not skip browser cleanup. - Account for the cost of your own browser setup. Puppeteer navigation itself does not specify a service price. The operational cost depends on the browser environment and infrastructure you run; measure those costs in your deployment rather than assuming a particular benchmark.
7. Or skip the browser setup
If the goal is a screenshot rather than browser automation, ScreenshotNeo provides a screenshot API and MCP server. A single GET request returns an image or PDF; see the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes known cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots. See ScreenshotNeo for the product and plans.
Sign up for 1,000 free screenshots a month, with no card required.
8. Frequently asked questions
Does page.goto() return the final page after redirects?
It resolves with the response for the last redirect in the chain. Check response.url() if you need to confirm the final URL.
Does a successful goto() mean the page returned HTTP 200?
No. Navigation completion and HTTP status are separate. Inspect the response status if the status matters to your task.
Can goto() return no response?
Yes. Navigation to about:blank and navigation to the same URL with a different hash resolve with null.
Should I use networkidle for every page?
No. Use it only when network idleness is the condition you need. For a specific widget or app state, wait for that state directly.
Where can I confirm option support for my installed Puppeteer version?
Check the API reference for the version installed in your project. The references cited here identify different release versions, so verify version-specific signatures and supported values before relying on them.


