How to Create and Use Browser Profiles in Puppeteer
Use a dedicated Puppeteer user-data directory to keep browser data across launches, or BrowserContexts to isolate sessions within one browser process.
Use userDataDir when cookies and other browser data should persist across Puppeteer launches. Use a BrowserContext when tasks need separate cookies and local storage during one browser run. A context is not a replacement for a disk-backed profile: it provides session isolation, while userDataDir selects the on-disk user-data directory.
Choose the right kind of profile
| Need | Use | Lifetime |
|---|---|---|
| Keep browser data for a later run | launch({ userDataDir }) |
Data is stored in the designated directory and can be used on later launches. |
| Keep task identities separate in one browser process | browser.createBrowserContext() |
Context and its pages are closed when the context is closed. |
| Open another page with the same browser state | browser.newPage() |
Uses the browser’s default context. |
Puppeteer documents that cookies and local storage are not shared between browser contexts. A context is therefore useful for separating tasks such as testing different accounts. The launch option userDataDir specifies a user-data directory. See the Puppeteer LaunchOptions reference and Browser management guide.
Create a persistent profile with userDataDir
Choose a stable, dedicated directory that the process can write to. Keep it between runs if you want its browser data to remain available. This ES module example opens a page, then closes the browser even if navigation fails:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
userDataDir: './data/puppeteer-profile',
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.close();
}
Install Puppeteer in the project with npm install puppeteer, save the example as an ES module (for example, profile.mjs), and run node profile.mjs. The relative directory is resolved from the process working directory, so use an absolute path if the script may run from different directories. Puppeteer creates a temporary profile by default; set userDataDir when you need a known location. Its troubleshooting guide uses /tmp/.puppeteer-profile as an example and notes that the directory must be writable: Puppeteer troubleshooting.
Preserve cookies and browser history
Use the same dedicated directory on each run where you want to reuse browser state. For example, to save a cookie after an interactive sign-in and use it in a later run, launch Puppeteer with the same userDataDir both times. Browser history is also browser profile data, but whether a particular site session remains valid depends on the site’s own authentication and expiration rules. Do not assume that a cookie guarantees a permanent login.
Keep the profile directory out of temporary cleanup paths if it must survive restarts. For containers, mount persistent storage at the chosen path if the container filesystem is ephemeral. Ensure the user running Chromium can create, read, and update the directory.
Use BrowserContexts for isolated sessions
Create a context for each independent task or identity. Pages created from that context use its isolated storage. Close the context when its work is done; closing it closes its pages.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const context = await browser.createBrowserContext();
try {
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await context.close();
await browser.close();
}
For multiple sessions, create multiple contexts from the same browser and keep each task’s pages inside its own context. Avoid creating pages with browser.newPage() for a task that is meant to use a non-default context; instead call context.newPage(). Closing a context is the documented way to close that context and its pages. Do not treat it as a promise about secure erasure of every temporary byte from the host.
The API reference describes Chrome non-default contexts as incognito contexts. Consult the BrowserContext API for the API details relevant to your installed Puppeteer version.
Use profiles safely in containers and CI
- Pick a writable path. Check that the operating-system user launching Chrome owns the directory or can write to it.
- Use persistent storage for persistent state. A path inside a container that is discarded after a job will not preserve profile data after that container is removed.
- Keep profiles dedicated to automation. Avoid pointing automation at a personal, actively used Chrome profile. A dedicated directory makes the automation’s browser state explicit and avoids mixing it with personal data.
- Keep browser versions compatible. Chromium warns that profiles are not backwards compatible; using a profile with a different Chrome version can cause crashes or data loss. See Chromium: Creating and Using Profiles.
- Close resources reliably. Use
finallyto close contexts and browsers, including when navigation or assertions throw an error.
For a container error about a missing or unwritable home, cache, or configuration directory, set the relevant environment paths to writable locations supported by that deployment and verify ownership. The exact paths depend on the container image and runtime; do not assume every environment uses the same home directory.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser fails to launch with a profile-directory error | The selected user-data path is missing, not writable, or owned by another user. | Create or mount a writable directory and run Puppeteer under the user that can access it. Check permissions inside the actual container or CI job. |
| Cookies appear to disappear after a run | The run used Puppeteer’s temporary default profile, a different userDataDir, or a path that is discarded after the job. |
Pass the same stable path to every launch and persist that path across container or machine restarts. |
| A page cannot see another task’s local storage or cookies | The pages belong to different browser contexts. | Use the same context when state should be shared, or deliberately create separate contexts when isolation is the goal. |
| A context page does not use the context’s session | The page was created through browser.newPage(), which uses the default context. |
Create the page using context.newPage(). |
| Chromium crashes or profile data is damaged after a browser upgrade or downgrade | The profile was opened with a different Chrome version; Chromium documents that profiles are not backwards compatible. | Keep the browser version consistent for a persistent profile. If testing another version, use a separate profile directory. |
| The site still asks the user to sign in | The site may expire or invalidate authentication, require additional verification, or store state outside the data being reused. | Check the site’s authentication requirements and session lifetime. Do not rely on a saved profile as a way to bypass access controls. |
Performance, reliability, and storage trade-offs
A persistent profile avoids starting every run with an empty user-data directory, which can be useful when workflows intentionally reuse state. It also carries state forward: old cookies, service workers, cache entries, and site preferences can affect later runs. Use a fresh profile when reproducibility matters more than retaining state, and use contexts when you need separate sessions within a launched browser.
BrowserContexts let tasks share a browser process while isolating their cookies and local storage. They do not make a slow or resource-heavy page cheap to load; the page still needs to navigate and render. Persistent profiles consume disk space and may accumulate browser data, so manage their lifecycle and storage as part of the deployment. No universal speed or storage figure applies across sites, workloads, and machines.
Or skip the browser setup
If the goal is to capture a website rather than automate a persistent browser session, ScreenshotNeo provides a screenshot API and MCP server. Its one-request API can return a screenshot or PDF. See the ScreenshotNeo API docs.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed; response headers identify the page verdict and billing status. Its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.
FAQ
Does Puppeteer save browser data by default?
Puppeteer uses a temporary profile by default. Set userDataDir to choose a known profile directory when you need to reuse browser data.
Are BrowserContexts the same as separate profile folders?
No. Contexts isolate storage within a browser process. userDataDir selects the on-disk user-data directory used when launching the browser.
Can I use a profile created by a different Chrome version?
Chromium warns that profiles are not backwards compatible. Keep the browser version consistent for a persistent profile, and use a separate directory when testing another version.
How do I keep a profile between CI runs?
Use a stable userDataDir located on storage that survives between runs, and ensure the CI process has access to it.


