How Puppeteer Resolves the Default User Data Directory
Puppeteer uses a temporary profile when you omit userDataDir. Learn how to set a persistent profile and how channel-based profile lookup differs.
Short answer: When you call Puppeteer’s launch() without userDataDir, Puppeteer creates a temporary browser profile beneath the operating system’s temporary directory. There is no single documented path that applies to every operating system. To choose a stable profile location, pass userDataDir explicitly and ensure the account running Chrome can write to it. Puppeteer documents this launch option, and its troubleshooting guide describes the temporary default.
1. What “default user data directory” means
A user data directory is the root of a browser profile: it stores browser state such as cookies, local storage, preferences, and other profile data. For ordinary Puppeteer launches, the documented behavior is simple: if you do not set userDataDir, Puppeteer creates a temporary profile under the operating system’s temporary directory. Puppeteer’s documentation does not specify one universal literal path, so avoid relying on a path copied from another machine or operating system.
The profile is temporary in this launch scenario. If your workflow needs browser state to survive process termination, configure a directory yourself. The browser process needs write access to that directory.
2. Launch with a temporary profile or choose your own
Install Puppeteer in a Node.js project with npm install puppeteer. The following complete example launches a browser with Puppeteer’s temporary default profile, opens a page, and closes the browser:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.close();
}
})();
To select a profile directory explicitly, pass a writable path:
const puppeteer = require('puppeteer');
const path = require('node:path');
(async () => {
const profileDir = path.resolve('./puppeteer-profile');
const browser = await puppeteer.launch({ userDataDir: profileDir });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.close();
}
})();
The relative path is resolved by Node.js against the process working directory. Choose a location appropriate to your deployment and make sure it is writable by the process account. The directory is profile storage, not a selector for which Chrome executable Puppeteer runs.
3. Keep profile storage separate from browser selection
Three settings are easy to confuse:
| Setting | Purpose |
|---|---|
userDataDir |
Selects the browser profile directory used for profile data. |
channel |
Selects a Chrome release channel where supported. |
executablePath |
Points to a browser executable. |
The launch API documents Chrome as the default browser. Selecting a profile directory does not make an arbitrary browser binary compatible with Puppeteer. Puppeteer says it works best with the Chrome for Testing version it downloads by default and does not guarantee operation with arbitrary Chrome versions. See the launch method documentation.
4. Distinguish launch defaults from the channel directory resolver
The @puppeteer/browsers package has a helper named resolveDefaultUserDataDir(browser, platform, channel). It computes the expected directory for the supplied browser, platform, and channel. Its documentation explicitly says it does not check whether that directory exists. This helper is for channel-specific directory discovery; it does not describe the temporary profile created by an ordinary launch() call that omits userDataDir.
There is a related but distinct connection behavior: Puppeteer’s connect() channel option looks for a WebSocket at the well-known user data directory for that channel. The API labels this option experimental and limits it to Chrome and Node.js. Do not treat this lookup as launch-time profile creation.
The resolver documentation is marked Next, while the stable launch and connection API pages identify Puppeteer 25.12.0. Check the documentation matching your installed package before depending on version-specific behavior. The reviewed documentation does not provide a reliable cross-platform path table or expose the resolver’s algorithm.
5. Practical choices and edge cases
- One-off automation: omit
userDataDirand allow Puppeteer to create its temporary profile. - State across runs: set an explicit profile directory and preserve it between runs.
- Multiple concurrent browsers: use a distinct profile directory for each browser process. Sharing a live profile directory can cause profile locking or state conflicts.
- Containers and CI: ensure the runtime user can write to the selected directory and that the path is available in that environment.
- Channel-based lookup: use the resolver or experimental connection behavior only when you specifically need channel-specific profile discovery; do not infer that the path exists.
- Browser compatibility: align the executable with the Puppeteer version, preferably using its downloaded Chrome for Testing browser as recommended by Puppeteer.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Chrome cannot create or write profile files | The browser process lacks write permission, or the parent directory is unavailable. | Choose a writable location and verify permissions for the account that launches Chrome. |
| Profile state disappears after the run | The launch omitted userDataDir, so Puppeteer used a temporary profile. |
Pass an explicit directory and retain it between runs. |
| The resolver returns a path that is missing | resolveDefaultUserDataDir computes an expected path but does not check existence. |
Verify installation and channel setup separately; do not assume resolution created the directory. |
| Channel connection cannot find a WebSocket | No compatible Chrome process is exposing the expected channel endpoint, or the wrong channel/platform was supplied. | Check the channel, Chrome process, and connection setup. Remember this connect option is experimental and Chrome-only in Node.js. |
| Puppeteer fails with an arbitrary system Chrome | The executable version may not be compatible with the installed Puppeteer version. | Use the Chrome for Testing version Puppeteer downloads by default or verify compatibility for the selected executable. |
7. Performance, reliability, and cost
Profile choice primarily affects state and isolation. A persistent profile can avoid repeating setup that is stored in browser state, but it also carries state from earlier runs and needs lifecycle management. Temporary profiles reduce persistent state between runs, at the cost of starting without that saved profile data. The cited Puppeteer documentation gives no benchmark or fixed performance difference, so measure your own workload if startup time matters.
For reliable automation, use a writable path, avoid simultaneous use of a single profile by multiple browser processes, and pin compatible Puppeteer and browser versions. Puppeteer’s documentation provides no usage pricing for these choices; browser execution costs depend on where you run the process.
Or skip the browser setup
If your goal is a screenshot rather than browser-profile control, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF, without managing Puppeteer or a local browser profile.
See the ScreenshotNeo API documentation. Example using cURL:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Does Puppeteer use my normal Chrome profile by default?
No. The documented default is a temporary profile under the operating system’s temporary directory unless you provide userDataDir.
Does resolveDefaultUserDataDir create a profile?
No. It returns an expected path and does not check whether the directory exists.
Will userDataDir select the Chrome version?
No. It configures profile storage. Use channel or executablePath for browser selection.
Can I use Puppeteer without managing a local browser profile for screenshots?
Yes. ScreenshotNeo offers a screenshot API and MCP server; see its documentation for request options.


