How to create separate Chrome profile apps on macOS

If you use multiple Chrome profiles (work, personal, client projects), you've probably noticed they all share a single Dock icon and a single Cmd+Tab entry. You can't pin "Work Chrome" to one Space and "Personal Chrome" to another. It's annoying.

The fix is actually pretty simple: copy Chrome.app for each profile, give it a unique bundle identifier, and wire it to launch with the right profile. Here's the full walkthrough, plus a free shell script you can grab and run right now.

01

Find your Chrome profiles

Chrome stores all its profile data under ~/Library/Application Support/Google/Chrome/. Each profile lives in a directory like Default, Profile 1, Profile 2, etc. The Local State JSON file maps these directories to human-readable names.

# List all Chrome profiles and their directory names
python3 -c "
import json, os
local_state = os.path.expanduser(
    '~/Library/Application Support/Google/Chrome/Local State'
)
with open(local_state) as f:
    profiles = json.load(f)['profile']['info_cache']
for dir_name, info in profiles.items():
    name = info.get('gaia_name') or info.get('name') or dir_name
    print(f'{dir_name:>12}  →  {name}')
"

You'll see output like Profile 1 → Work. Note down the directory name, that's what you'll pass to the script.

02

Copy Chrome.app

macOS identifies apps by their bundle identifier, not the filename. Two copies of Chrome.app with the same bundle ID still show up as one app in the Dock and Cmd+Tab. So we need a full copy that we can give its own identity.

# ~500MB per copy, unfortunately there's no way around this
cp -R "/Applications/Google Chrome.app" "/Applications/Chrome Work.app"
03

Give it a unique identity

Patch the app's Info.plist to change the bundle identifier, name, and display name. This is what makes macOS see it as a completely separate application in the Dock and app switcher.

PLIST="/Applications/Chrome Work.app/Contents/Info.plist"

# Unique bundle ID - this is the important one
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.chrome.profile.work" "$PLIST"

# Human-readable name (shows in Cmd+Tab, Activity Monitor, etc.)
/usr/libexec/PlistBuddy -c "Set :CFBundleName Chrome Work" "$PLIST"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName Chrome Work" "$PLIST"
04

Disable auto-updates for the copy

Chrome bundles Google's Keystone updater. If you leave it in the copy, it'll try to auto-update on its own and break your launcher. Two things to do: delete the framework and neuter the update URL.

APP="/Applications/Chrome Work.app"

# Remove the updater framework entirely
rm -rf "$APP/Contents/Frameworks/KeystoneRegistration.framework"

# Point the update URL to localhost as a fallback
/usr/libexec/PlistBuddy -c "Set :KSUpdateURL https://localhost/disabled" \
  "$APP/Contents/Info.plist" 2>/dev/null || true
05

Create a launcher wrapper

Here's the fun part. Rename the real Chrome binary and replace it with a shell script that launches the original with --profile-directory. When you double-click the app, macOS runs your script, which starts Chrome pointing at the right profile.

MACOS_DIR="/Applications/Chrome Work.app/Contents/MacOS"

# Stash the real binary
mv "$MACOS_DIR/Google Chrome" "$MACOS_DIR/Google Chrome.orig"

# Write a launcher that passes --profile-directory
cat > "$MACOS_DIR/Google Chrome" << 'EOF'
#!/bin/bash
DIR="$(dirname "$0")"
exec "$DIR/Google Chrome.orig" --profile-directory="Profile 1" "$@"
EOF

chmod +x "$MACOS_DIR/Google Chrome"

Replace Profile 1 with the directory name you found in step 1. The exec replaces the script process with Chrome, so you don't get a lingering shell process.

06

Sign and register

macOS won't run a modified app without re-signing it. An ad-hoc signature (the -s - flag) works fine for this, no Apple Developer account needed. Then register it with Launch Services so Spotlight and the Dock pick it up.

APP="/Applications/Chrome Work.app"

# Strip extended attributes and re-sign
xattr -cr "$APP"
codesign --force -s - "$APP/Contents/MacOS/Google Chrome"
codesign --force -s - --deep "$APP"

# Register with Launch Services
/System/Library/Frameworks/CoreServices.framework/\
Frameworks/LaunchServices.framework/Support/lsregister -f "$APP"

The complete script

Here's everything above wrapped into a single script. Save it, chmod +x, and run it with the profile directory and your desired app name.

#!/bin/bash
# unbundle-profile.sh
# Creates a standalone macOS app for a Chrome profile.
# Usage: ./unbundle-profile.sh "Profile 1" "Chrome Work"

set -euo pipefail

PROFILE_DIR="${1:?Usage: $0 <profile-dir> <app-name>}"
APP_NAME="${2:?Usage: $0 <profile-dir> <app-name>}"
CHROME_APP="/Applications/Google Chrome.app"
NEW_APP="/Applications/${APP_NAME}.app"
SAFE_NAME=$(echo "$APP_NAME" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')

if [ ! -d "$CHROME_APP" ]; then
  echo "Error: Google Chrome not found at $CHROME_APP"
  exit 1
fi

if [ -d "$NEW_APP" ]; then
  echo "Error: $NEW_APP already exists. Remove it first."
  exit 1
fi

echo "Creating $APP_NAME for profile $PROFILE_DIR..."

# 1. Copy Chrome.app
cp -R "$CHROME_APP" "$NEW_APP"

# 2. Patch Info.plist
PLIST="$NEW_APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.chrome.profile.${SAFE_NAME}" "$PLIST"
/usr/libexec/PlistBuddy -c "Set :CFBundleName ${APP_NAME}" "$PLIST"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName ${APP_NAME}" "$PLIST"

# 3. Neuter Keystone auto-updater
rm -rf "$NEW_APP/Contents/Frameworks/KeystoneRegistration.framework"
/usr/libexec/PlistBuddy -c "Set :KSUpdateURL https://localhost/disabled" "$PLIST" 2>/dev/null || true

# 4. Create launcher wrapper
MACOS_DIR="$NEW_APP/Contents/MacOS"
mv "$MACOS_DIR/Google Chrome" "$MACOS_DIR/Google Chrome.orig"
cat > "$MACOS_DIR/Google Chrome" << EOF
#!/bin/bash
DIR="\$(dirname "\$0")"
exec "\$DIR/Google Chrome.orig" --profile-directory="$PROFILE_DIR" "\$@"
EOF
chmod +x "$MACOS_DIR/Google Chrome"

# 5. Sign and register
xattr -cr "$NEW_APP"
codesign --force -s - "$MACOS_DIR/Google Chrome"
codesign --force -s - --deep "$NEW_APP"
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f "$NEW_APP"

echo "Done. $APP_NAME.app is ready in /Applications."
# Example usage
chmod +x unbundle-profile.sh
./unbundle-profile.sh "Profile 1" "Chrome Work"
./unbundle-profile.sh "Profile 2" "Chrome Personal"
./unbundle-profile.sh "Default" "Chrome Client"

Why I built Unbundle

I used a script like this for a while, but I kept running into the same annoyances: Chrome would update and break everything, all the Dock icons looked identical, and I had to re-run the script manually every time. So I built Unbundle to handle the stuff the script can't.

It generates custom badged icons so you can tell profiles apart, automatically rebuilds when Chrome updates, and migrates your bookmarks, passwords, and extensions into each app. Basically the same approach as above, just wrapped in a native Mac app that handles the edge cases for you.

Download Unbundle - $6.99

One-time purchase · macOS 13+