How to Pass a Custom userDataDir Profile Folder to Puppeteer: Fixing the 'Person 1' Profile Issue
Puppeteer, a powerful Node.js library for controlling headless Chrome or Chromium, is widely used for web scraping, automated testing, and browser automation. However, a common frustration among developers is Puppeteer’s default behavior of creating a temporary "Person 1" profile on each launch. This temporary profile resets every time the browser closes, losing critical data like cookies, logged-in sessions, extensions, and user settings.
The solution? Specifying a custom userDataDir profile folder. By defining a persistent directory for user data, you can retain sessions, avoid repetitive logins, and ensure a consistent environment across automation runs. In this guide, we’ll demystify userDataDir, explain why the "Person 1" issue occurs, and walk through a step-by-step tutorial to implement a custom profile—with advanced tips and troubleshooting.
Table of Contents#
- Understanding the 'Person 1' Profile Issue
- What is
userDataDirin Puppeteer? - Step-by-Step Guide to Passing a Custom
userDataDir - Advanced Tips: Managing Multiple Profiles
- Troubleshooting Common Issues
- Conclusion
- References
Understanding the 'Person 1' Profile Issue#
When you launch Puppeteer without any custom configuration, it automatically creates a temporary user profile directory (e.g., tmp/puppeteer_dev_chrome_profile-XXXXXX). This directory is deleted once the browser session ends, ensuring a clean slate for each run. However, Chrome labels this default temporary profile as "Person 1" (visible via chrome://version in the browser), leading to two key problems:
- No Persistence: Cookies, local storage, logged-in sessions, and extensions are lost after the browser closes. This forces repetitive logins or setup steps (e.g., re-enabling extensions) for every automation run.
- Inconsistent Environment: Temporary profiles may behave differently across runs (e.g., due to varying temp directory paths or Chrome’s default settings), leading to flaky tests or scrapers.
The "Person 1" label itself is harmless, but it’s a symptom of Puppeteer’s default ephemeral behavior. By specifying a custom userDataDir, we replace this temporary profile with a persistent one, eliminating both issues.
What is userDataDir in Puppeteer?#
The userDataDir (short for "user data directory") is a Puppeteer launch option that specifies a custom path where Chrome/Chromium stores user-specific data. This includes:
- Cookies and local storage
- Browsing history
- Installed extensions
- User preferences (e.g., default zoom, theme)
- Saved passwords (if enabled)
By default, Puppeteer omits userDataDir, triggering the temporary profile behavior. When you explicitly set userDataDir to a file path (e.g., ./my-puppeteer-profile), Puppeteer:
- Creates the directory if it doesn’t exist.
- Uses it to store all user data for the session.
- Retains the directory (and its contents) across browser launches, ensuring persistence.
Key Benefits of userDataDir:#
- Persistent Sessions: Logged-in accounts remain signed in between runs.
- Consistent Environment: Extensions, settings, and storage are identical across launches.
- Isolation: Use separate profiles for different tasks (e.g., "scraping-profile" vs. "testing-profile").
Step-by-Step Guide to Passing a Custom userDataDir#
Let’s walk through implementing a custom userDataDir in Puppeteer. We’ll use Node.js and assume basic familiarity with JavaScript and npm.
3.1 Set Up a Basic Puppeteer Project#
If you’re starting from scratch, first initialize a Node.js project and install Puppeteer:
# Create a new project folder
mkdir puppeteer-custom-profile && cd puppeteer-custom-profile
# Initialize npm (accept defaults)
npm init -y
# Install Puppeteer (includes a compatible Chromium version)
npm install puppeteer3.2 Define the Custom userDataDir Path#
Choose a path for your persistent profile. We recommend using a relative path (e.g., ./profiles/my-first-profile) for portability, but absolute paths (e.g., /home/user/puppeteer-profiles/scraper) work too.
-
Relative Paths: Resolve relative to your script’s execution directory. Use
path.resolve()(from Node’s built-inpathmodule) to avoid ambiguity:const path = require('path'); const userDataDir = path.resolve('./profiles/my-first-profile'); // Resolves to: <your-project>/profiles/my-first-profile -
Absolute Paths: Use if the profile needs to live in a fixed location (e.g.,
/tmp/puppeteer-profiles/prod).
3.3 Launch Puppeteer with the Custom Profile#
Modify Puppeteer’s launch() options to include userDataDir. Here’s a complete example:
const puppeteer = require('puppeteer');
const path = require('path');
// Define the custom profile path
const userDataDir = path.resolve('./profiles/my-first-profile');
async function launchWithCustomProfile() {
// Launch Puppeteer with userDataDir
const browser = await puppeteer.launch({
headless: false, // Launch in "headful" mode to see the browser (optional)
userDataDir: userDataDir, // Use the custom profile path
args: ['--no-sandbox', '--disable-setuid-sandbox'] // Optional: Fixes permission issues on some systems
});
// Open a new page and navigate to a site (e.g., Google)
const page = await browser.newPage();
await page.goto('https://google.com');
// Keep the browser open for verification (remove in production)
console.log('Browser running with custom profile. Press Ctrl+C to exit.');
}
// Run the function
launchWithCustomProfile().catch(console.error);3.4 Verify the Custom Profile is Active#
To confirm Puppeteer is using your custom userDataDir, follow these steps:
Step 1: Check the Profile Directory#
After running the script, Puppeteer will create the ./profiles/my-first-profile directory (if it didn’t exist). Inspect its contents—you’ll see Chrome-specific subdirectories like Default/ (main profile data), Extensions/ (installed extensions), and Local Storage/.
Step 2: Inspect chrome://version#
In the launched browser, navigate to chrome://version (type this in the address bar). Look for the "Profile Path" field—it should match your custom userDataDir path (e.g., .../puppeteer-custom-profile/profiles/my-first-profile/Default).
Step 3: Test Persistence#
- In the launched browser, log into a website (e.g., Gmail) and close the browser.
- Re-run the script. The browser should reopen with your login session still active—proof the profile is persistent!
Advanced Tips: Managing Multiple Profiles#
For complex workflows (e.g., testing different user roles, scraping with distinct accounts), you may need multiple isolated profiles. Here’s how to manage them:
Example: Switch Between Profiles#
Define a mapping of profile names to userDataDir paths, then dynamically select the profile at runtime:
const path = require('path');
const puppeteer = require('puppeteer');
// Map profile names to directories
const PROFILES = {
scraper: path.resolve('./profiles/scraper-profile'),
tester: path.resolve('./profiles/tester-profile'),
admin: path.resolve('./profiles/admin-profile')
};
async function launchWithProfile(profileName) {
const userDataDir = PROFILES[profileName];
if (!userDataDir) throw new Error(`Profile "${profileName}" not found`);
const browser = await puppeteer.launch({
headless: false,
userDataDir: userDataDir
});
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(`Launched with profile: ${profileName}`);
}
// Launch with the "scraper" profile
launchWithProfile('scraper').catch(console.error);Tip: Clean Up Old Profiles#
If profiles become obsolete (e.g., after a project ends), delete their directories to free space. Use Node’s fs.rm (with recursive: true) for automation:
const fs = require('fs').promises;
const path = require('path');
async function deleteProfile(profileName) {
const profilePath = PROFILES[profileName];
if (!profilePath) return;
await fs.rm(profilePath, { recursive: true, force: true });
console.log(`Deleted profile: ${profileName}`);
}
// Delete the "old-profile" (if defined in PROFILES)
deleteProfile('old-profile').catch(console.error);Troubleshooting Common Issues#
Issue 1: Profile Directory Not Created#
Problem: Puppeteer fails to create the userDataDir path.
Causes:
- The parent directory (e.g.,
./profiles) doesn’t exist. - Permissions: The script lacks write access to the target path.
Fix:
- Ensure parent directories exist (create them with
fs.mkdirSync):const fs = require('fs'); const profileDir = path.resolve('./profiles/my-profile'); if (!fs.existsSync(profileDir)) { fs.mkdirSync(profileDir, { recursive: true }); // Creates parent dirs if needed } - Use a path with write permissions (e.g., avoid system directories like
/root).
Issue 2: "Profile in Use" Error#
Problem: Puppeteer crashes with Error: Failed to launch the browser process! and a message like The profile appears to be in use by another process.
Cause: A previous Puppeteer session didn’t close properly, leaving the profile locked.
Fix:
- Ensure you call
browser.close()in afinallyblock to clean up:let browser; try { browser = await puppeteer.launch({ userDataDir: './my-profile' }); // ... automation logic ... } finally { if (browser) await browser.close(); // Always close the browser } - Manually delete the
SingletonLockfile in the profile directory (e.g.,./my-profile/SingletonLock) to release the lock.
Issue 3: Extensions Not Loading#
Problem: Extensions installed in the custom profile don’t load.
Cause: Puppeteer disables extensions by default in headless mode (prior to Chrome 112). Even in headful mode, extensions must be explicitly enabled.
Fix:
- Use
headless: 'new'(Chrome 112+) to enable extensions in headless mode:puppeteer.launch({ headless: 'new', // Supports extensions userDataDir: './my-profile', args: ['--enable-extensions'] // Explicitly enable extensions });
Conclusion#
By specifying a custom userDataDir, you transform Puppeteer from a tool with ephemeral "Person 1" profiles into a powerful, persistent automation platform. This guide covered:
- The limitations of Puppeteer’s default temporary profiles.
- How
userDataDirenables persistence and consistency. - Step-by-step implementation (including verification).
- Advanced profile management and troubleshooting.
Whether you’re building scrapers, tests, or browser automation tools, userDataDir ensures your workflows are efficient, reliable, and frustration-free.