Skip to main content

Overview

The ProfilesController manages user profiles - isolated environments with separate browsing data, settings, cookies, and extensions. Each profile can contain multiple spaces for organizing tabs.
Import: import { profilesController } from '@/controllers/profiles-controller'The controller is a singleton instance exported as profilesController.

Key Concepts

What is a Profile?

A profile in Flow Browser is similar to Chrome’s profiles or Firefox’s containers:
  • Isolated browsing data: Separate cookies, cache, history, and local storage
  • Independent settings: Each profile has its own preferences
  • Unique extensions: Install different extensions per profile
  • Multiple spaces: Each profile can have multiple workspace spaces
  • File system storage: Profile data is stored in separate directories

Work Profile

Company accounts, work extensions

Personal Profile

Personal accounts, entertainment

Development Profile

Dev tools, testing environments

Research Profile

Clean environment for research

Data Structure

interface ProfileData {
  name: string;          // Display name
  icon: string | null;   // Icon/emoji for UI
  createdAt: number;     // Creation timestamp
}

type ProfileDataWithId = ProfileData & { id: string };

Initialization

The controller automatically creates a default “Main” profile on first run:
// Automatically called on startup
profilesController._setupInitialProfile();
This creates:
  • Profile ID: "main"
  • Profile name: "Main"
  • No initial space (handled separately)
Don’t call _setupInitialProfile() manually. It’s called automatically via queueMicrotask() on controller initialization.

Core Methods

Creating Profiles

create()

Create a new profile with a generated unique ID.
public async create(
  profileName: string,
  shouldCreateSpace?: boolean
): Promise<boolean>
profileName
string
required
Display name for the profile
shouldCreateSpace
boolean
default:"true"
Whether to create a default space in the profile
return
Promise<boolean>
true if profile was created successfully
Example:
// Create profile with default space
const success = await profilesController.create('Work Profile');

if (success) {
  console.log('Profile created successfully');
}

// Create profile without initial space
const success2 = await profilesController.create(
  'Development',
  false // Don't create space
);

Retrieving Profiles

get()

Get a profile by its ID.
public async get(profileId: string): Promise<ProfileData | null>
Example:
const profile = await profilesController.get('main');
if (profile) {
  console.log(`Profile: ${profile.name}`);
  console.log(`Created: ${new Date(profile.createdAt)}`);
}

getAll()

Get all profiles, sorted by creation date.
public async getAll(): Promise<ProfileDataWithId[]>
Example:
const profiles = await profilesController.getAll();

profiles.forEach(profile => {
  console.log(`${profile.name} (ID: ${profile.id})`);
});

Updating Profiles

update()

Update profile properties.
public async update(
  profileId: string,
  profileData: Partial<ProfileData>
): Promise<boolean>
profileId
string
required
Profile ID to update
profileData
Partial<ProfileData>
required
Fields to update (name, icon, etc.)
Example:
// Rename profile
await profilesController.update('main', {
  name: 'Personal'
});

// Update icon
await profilesController.update('work-profile', {
  icon: '💼'
});

// Update multiple fields
await profilesController.update('dev-profile', {
  name: 'Development',
  icon: '⚡'
});

Deleting Profiles

delete()

Delete a profile permanently.
public async delete(profileId: string): Promise<boolean>
Deleting a profile does not automatically delete its spaces or tabs. Handle cleanup separately.
Example:
const deleted = await profilesController.delete('old-profile');
if (deleted) {
  console.log('Profile deleted');
}

File System Integration

getProfilePath()

Get the file system path for a profile’s data directory.
public getProfilePath(profileId: string): string
Example:
const profilePath = profilesController.getProfilePath('main');
console.log(`Profile data stored at: ${profilePath}`);

// Typical path: /Users/username/Library/Application Support/Flow/profiles/main
This path contains:
  • Session data (cookies, storage)
  • Extensions
  • Cache
  • Preferences

Events

The ProfilesController extends TypedEventEmitter with these events:
profile-created
[profileId: string, profileData: ProfileData]
Emitted when a new profile is created
profile-updated
[profileId: string, updatedFields: Partial<ProfileData>]
Emitted when a profile is modified
profile-deleted
[profileId: string]
Emitted when a profile is deleted
requested-all-profiles
[]
Emitted when all profiles are loaded into cache
Example:
profilesController.on('profile-created', (profileId, profileData) => {
  console.log(`New profile created: ${profileData.name}`);
  console.log(`Profile ID: ${profileId}`);
});

profilesController.on('profile-updated', (profileId, updatedFields) => {
  console.log(`Profile ${profileId} updated:`, updatedFields);
});

profilesController.on('profile-deleted', (profileId) => {
  console.log(`Profile ${profileId} deleted`);
});

Caching Strategy

The ProfilesController uses an efficient caching system:
  • Profiles are loaded on-demand from the database
  • Once loaded, they’re cached in memory
  • Cache is automatically updated on modifications
  • requestedAllProfiles flag tracks full cache status

Cache Status

Check if all profiles are loaded:
if (profilesController.requestedAllProfiles) {
  // All profiles are cached
  const profiles = await profilesController.getAll();
} else {
  // First call will load from database
  const profiles = await profilesController.getAll();
}

Usage Patterns

Profile Switcher

Implement a profile switcher UI:
// Get all profiles for UI
const profiles = await profilesController.getAll();

// Render profile list
profiles.forEach(profile => {
  renderProfileButton({
    id: profile.id,
    name: profile.name,
    icon: profile.icon,
    isActive: currentProfileId === profile.id,
    onClick: () => switchToProfile(profile.id)
  });
});

function switchToProfile(profileId: string) {
  // Load profile session and spaces
  loadedProfilesController.load(profileId);
  
  // Switch to last used space in profile
  const lastSpace = await spacesController.getLastUsedFromProfile(profileId);
  if (lastSpace) {
    tabsController.setCurrentWindowSpace(windowId, lastSpace.id);
  }
}

Creating a New Profile

// Create profile with wizard
async function createNewProfile(name: string, icon: string) {
  // Create profile
  const success = await profilesController.create(name);
  
  if (!success) {
    console.error('Failed to create profile');
    return null;
  }
  
  // Get the newly created profile
  const profiles = await profilesController.getAll();
  const newProfile = profiles[profiles.length - 1];
  
  // Update icon
  await profilesController.update(newProfile.id, { icon });
  
  // Load the profile
  await loadedProfilesController.load(newProfile.id);
  
  return newProfile;
}

// Usage
const profile = await createNewProfile('Work', '💼');
if (profile) {
  console.log(`Created profile: ${profile.name}`);
}

Profile Management UI

// List all profiles with metadata
async function getProfilesWithMetadata() {
  const profiles = await profilesController.getAll();
  
  return Promise.all(profiles.map(async profile => {
    // Get spaces count
    const spaces = await spacesController.getAllFromProfile(profile.id);
    
    // Get tabs count
    const tabs = tabsController.getTabsInProfile(profile.id);
    
    return {
      ...profile,
      spacesCount: spaces.length,
      tabsCount: tabs.length,
      path: profilesController.getProfilePath(profile.id)
    };
  }));
}

// Usage
const profilesWithMeta = await getProfilesWithMetadata();
profilesWithMeta.forEach(profile => {
  console.log(`${profile.name}: ${profile.tabsCount} tabs in ${profile.spacesCount} spaces`);
});

Safe Profile Deletion

async function deleteProfileSafely(profileId: string) {
  // Don't delete the main profile
  if (profileId === 'main') {
    console.error('Cannot delete main profile');
    return false;
  }
  
  // Close all tabs in this profile
  const tabs = tabsController.getTabsInProfile(profileId);
  for (const tab of tabs) {
    tab.destroy();
  }
  
  // Delete all spaces
  const spaces = await spacesController.getAllFromProfile(profileId);
  for (const space of spaces) {
    await spacesController.delete(profileId, space.id);
  }
  
  // Unload profile if loaded
  await loadedProfilesController.unload(profileId);
  
  // Finally delete the profile
  return await profilesController.delete(profileId);
}

Data Validation

The controller uses Zod schemas for data validation:
import { ProfileDataSchema } from '@/controllers/profiles-controller';

// Validate profile data
const result = ProfileDataSchema.safeParse(profileData);
if (result.success) {
  console.log('Valid profile data');
} else {
  console.error('Invalid profile data:', result.error);
}

Integration with Other Controllers

With LoadedProfilesController

import { loadedProfilesController } from '@/controllers/loaded-profiles-controller';

// Create and load profile
const success = await profilesController.create('Work');
if (success) {
  const profiles = await profilesController.getAll();
  const newProfile = profiles[profiles.length - 1];
  
  // Load the profile session
  await loadedProfilesController.load(newProfile.id);
  
  // Profile is now ready for tabs
}

With SpacesController

// Get profile with all its spaces
async function getProfileWithSpaces(profileId: string) {
  const profile = await profilesController.get(profileId);
  const spaces = await spacesController.getAllFromProfile(profileId);
  
  return { profile, spaces };
}

Best Practices

Always create at least one space when creating a new profile for better user experience.
Profile IDs are permanent. Don’t expose profile IDs directly in URLs or shareable content.
// Good - create with space
await profilesController.create('Work Profile', true);

// Avoid - creates orphan profile
await profilesController.create('Work Profile', false);

Spaces Controller

Manage spaces within profiles

Loaded Profiles Controller

Session management for profiles

Tabs Controller

Tab management within profiles

Build docs developers (and LLMs) love