> ## Documentation Index
> Fetch the complete documentation index at: https://www.kernel.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Save and Reuse Profile State

> Create a browser profile, save state to it, and load it in later browser sessions

This guide creates a profile, writes browser state to it, and reuses the saved state in a later browser session.

## 1. Create a profile

Give the profile a meaningful `name` that is unique within your [project](https://www.kernel.sh/docs/info/projects.md).

```typescript TypeScript theme={null}
import Kernel, { ConflictError } from '@onkernel/sdk';

const kernel = new Kernel();

try {
  await kernel.profiles.create({ name: 'checkout-session' });
} catch (err) {
  if (!(err instanceof ConflictError)) {
    throw err;
  }
}
```

```python Python theme={null}
from kernel import Kernel, ConflictError

kernel = Kernel()

try:
    await kernel.profiles.create(name="checkout-session")
except ConflictError:
    pass
```

```go Go theme={null}
package main

import (
	"context"
	"errors"
	"net/http"

	"github.com/kernel/kernel-go-sdk"
)

func main() {
	ctx := context.Background()
	client := kernel.NewClient()

	_, err := client.Profiles.New(ctx, kernel.ProfileNewParams{
		Name: kernel.String("checkout-session"),
	})
	if err != nil {
		var apiErr *kernel.Error
		if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict {
			return
		}
		panic(err)
	}
}
```

## 2. Start a writer browser

Attach the profile by `name` or `id`. Set `save_changes` to `true` only when this browser owns writes to the profile.

```typescript TypeScript theme={null}
const browser = await kernel.browsers.create({
  profile: {
    name: 'checkout-session',
    save_changes: true,
  },
});
```

```python Python theme={null}
browser = await kernel.browsers.create(
    profile={
        "name": "checkout-session",
        "save_changes": True,
    }
)
```

```go Go theme={null}
browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{
	Profile: shared.BrowserProfileParam{
		Name:        kernel.String("checkout-session"),
		SaveChanges: kernel.Bool(true),
	},
})
if err != nil {
	panic(err)
}
```

Before starting more than one writer, read [Sharing and concurrency](https://www.kernel.sh/docs/browsers/profiles/concurrency.md). Profile saves replace the complete stored snapshot and don't merge concurrent changes.

## 3. Use and delete the browser

Navigate, authenticate, or complete the part of the workflow whose state you want to retain. Then delete the Kernel browser to save the state.

To save without ending the browser, see [Save a running browser to a profile](#save-a-running-browser-to-a-profile).

> [!WARNING]
>
> Calling `browser.close()` only closes the Playwright connection. It doesn't save the profile. You must delete the Kernel browser or let it [time out](https://www.kernel.sh/docs/browsers/termination#automatic-deletion-via-timeout).

```typescript TypeScript theme={null}
console.log('Live view:', browser.browser_live_view_url);

// Navigate and create the state you want to preserve.

await kernel.browsers.deleteByID(browser.session_id);
```

```python Python theme={null}
print("Live view:", browser.browser_live_view_url)

# Navigate and create the state you want to preserve.

await kernel.browsers.delete_by_id(browser.session_id)
```

```go Go theme={null}
fmt.Println("Live view:", browser.BrowserLiveViewURL)

// Navigate and create the state you want to preserve.

if err := client.Browsers.DeleteByID(ctx, browser.SessionID); err != nil {
	panic(err)
}
```

## 4. Load the saved state

Create another browser with the same profile. Omit `save_changes` to keep the stored profile unchanged during this run.

```typescript TypeScript theme={null}
const nextBrowser = await kernel.browsers.create({
  profile: { name: 'checkout-session' },
});
```

```python Python theme={null}
next_browser = await kernel.browsers.create(
    profile={"name": "checkout-session"}
)
```

```go Go theme={null}
nextBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{
	Profile: shared.BrowserProfileParam{
		Name: kernel.String("checkout-session"),
	},
})
if err != nil {
	panic(err)
}
```

## Save a running browser to a profile

You can save a running browser's current state to a profile without deleting the browser. Send `POST /browsers/{id_or_name}/profiles` with either:

* `id` to overwrite an existing profile, or
* `name` to overwrite the profile with that name in the browser's project, or create it when it doesn't exist.

The response is the saved profile. It returns `201` when the request created the profile and `200` when it overwrote one.

```bash cURL theme={null}
curl -X POST "https://api.onkernel.com/browsers/$BROWSER_ID/profiles" \
  -H "Authorization: Bearer $KERNEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "checkout-session"}'
```

```typescript TypeScript theme={null}
const profile = await kernel.browsers.saveProfile(
  browser.session_id,
  { name: 'checkout-session' },
  { timeout: 5 * 60 * 1000 },
);
```

```python Python theme={null}
profile = await kernel.browsers.save_profile(
    browser.session_id,
    name="checkout-session",
    timeout=300,
)
```

```go Go theme={null}
profile, err := client.Browsers.SaveProfile(ctx, browser.SessionID, kernel.BrowserSaveProfileParams{
	SaveProfileRequest: kernel.SaveProfileRequestParam{
		Name: kernel.String("checkout-session"),
	},
}, option.WithRequestTimeout(5*time.Minute))
if err != nil {
	panic(err)
}
```

Saving copies the browser's data while Chromium is stopped, restarts Chromium, then uploads the copy. The request returns once the restarted browser accepts CDP connections again and the profile is stored. Large profiles can take longer than the TypeScript and Python SDKs' default one-minute timeout, so the examples above set five minutes. A longer timeout also stops the SDK from retrying a slow save that is still running.

### What restarting the browser means for your automation

* **Connections close.** Existing CDP, Playwright, Puppeteer and WebDriver connections disconnect. Reconnect to the browser's `cdp_ws_url` after the request returns, and create a new WebDriver session.
* **Pages reload.** Open tabs are restored, but each page reloads. In-page state that wasn't persisted, such as JavaScript variables, unsent form input, and in-flight requests, is lost.

Call the endpoint at a stable point in your workflow, for example right after a login completes, rather than while a page is mid-action. The browser keeps the same session ID and connection URLs.

### What's saved

The saved profile contains the same state as a profile saved when the browser is deleted: cookies, local storage and other site data, open tabs, and preferences. Data that's large or that Chromium can rebuild is left out, including caches, service worker registrations and their storage, and some large offline storage that web apps keep. That data stays in the running browser; it's only left out of the saved profile.

Saving doesn't attach the profile to the browser or change the browser's `save_changes` setting. Other browsers already using the profile keep the state they loaded, and new browsers get the saved state the next time they load the profile.

### Overlapping saves

When saves to the same profile overlap, the save that started most recently wins, even if an earlier one finishes after it. If the profile is also attached to a browser with `save_changes: true`, the save made when that browser is deleted replaces the stored profile with that browser's state.

### Inactivity timeout

A successful save counts as activity: it restarts the browser's [inactivity timeout](https://www.kernel.sh/docs/browsers/termination#automatic-deletion-via-timeout). A failed save leaves the timeout unchanged.

### Errors

| Status | Code | Meaning | What to do |
| - | - | - | - |
| `409` | `browser_busy` | The browser briefly couldn't accept a save | Retry the request |
| `409` | `browser_lease_changed` | The [pooled](https://www.kernel.sh/docs/browsers/pools.md) browser was released or acquired again after you made the request | Don't retry against this browser; save from a browser you currently hold |
| `409` | `browser_not_active` | The browser is no longer running | Don't retry |
| `400` | `pooled_session_not_leased` | The pooled browser isn't currently acquired | Acquire the browser before saving |
| `404` | `not_found` | The browser, or the profile `id`, doesn't exist | Check the ID or name |

A failed save never stores a partial or corrupt profile. When a `name` request created a new profile and the save was rejected with a `409`, that empty profile is removed so the name stays free.

## Control the starting tab

By default, a profile restores its saved tabs. Pass `start_url` to discard those restored tabs and open a specific page when the browser starts.

```typescript TypeScript theme={null}
const browser = await kernel.browsers.create({
  profile: { name: 'checkout-session' },
  start_url: 'https://example.com/dashboard',
});
```

```python Python theme={null}
browser = await kernel.browsers.create(
    profile={"name": "checkout-session"},
    start_url="https://example.com/dashboard",
)
```

```go Go theme={null}
browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{
	Profile: shared.BrowserProfileParam{
		Name: kernel.String("checkout-session"),
	},
	StartURL: kernel.String("https://example.com/dashboard"),
})
if err != nil {
	panic(err)
}
```

The same behavior applies to browser pools configured with both a profile and a start URL.

> [!NOTE]
>
> [Managed Auth](https://www.kernel.sh/docs/auth/managed-auth.md) controls the tab state of profiles attached to auth connections. Each login or automatic reauthentication starts with one tab at the configured login URL, or at the domain homepage when no login URL is configured. Successful authentication saves the resulting tab state. Failed and canceled sessions leave the profile unchanged.
>
> Set `start_url` when you create a browser if your automation requires a specific first page.

## Load a profile after browser creation

You can attach a profile to a running browser that was created without one. Loading the profile restarts Chromium, so reconnect your CDP or Playwright client afterward.

```typescript TypeScript theme={null}
const browser = await kernel.browsers.create();

await kernel.browsers.update(browser.session_id, {
  profile: { name: 'checkout-session' },
});
```

```python Python theme={null}
browser = await kernel.browsers.create()

await kernel.browsers.update(
    browser.session_id,
    profile={"name": "checkout-session"},
)
```

```go Go theme={null}
browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{})
if err != nil {
	panic(err)
}

_, err = client.Browsers.Update(ctx, browser.SessionID, kernel.BrowserUpdateParams{
	Profile: shared.BrowserProfileParam{
		Name: kernel.String("checkout-session"),
	},
})
if err != nil {
	panic(err)
}
```

> [!WARNING]
>
> You can't load a profile into a browser that already has one. The browser must have been created without a profile configuration.

Loading a profile this way restores all of its saved tabs, which can leave the browser with several tabs open at once. Pass [`start_url`](https://www.kernel.sh/docs/api-reference/browsers/update-browser-session#body-start-url) in the same `update()` call to collapse those restored tabs down to a single tab at the page you specify.

```typescript TypeScript theme={null}
await kernel.browsers.update(browser.session_id, {
  profile: { name: 'checkout-session' },
  start_url: 'https://example.com/dashboard',
});
```

```python Python theme={null}
await kernel.browsers.update(
    browser.session_id,
    profile={"name": "checkout-session"},
    start_url="https://example.com/dashboard",
)
```

```go Go theme={null}
_, err = client.Browsers.Update(ctx, browser.SessionID, kernel.BrowserUpdateParams{
	Profile: shared.BrowserProfileParam{
		Name: kernel.String("checkout-session"),
	},
	StartURL: kernel.String("https://example.com/dashboard"),
})
if err != nil {
	panic(err)
}
```

## Rename and manage profiles

Renaming a profile doesn't recreate or change its stored state. The new name must be unique within the project.

```typescript TypeScript theme={null}
await kernel.profiles.update('checkout-session', {
  name: 'checkout-session-v2',
});
```

```python Python theme={null}
await kernel.profiles.update(
    "checkout-session",
    name="checkout-session-v2",
)
```

```go Go theme={null}
_, err := client.Profiles.Update(ctx, "checkout-session", kernel.ProfileUpdateParams{
	Name: "checkout-session-v2",
})
```

Use the [Profiles API reference](https://www.kernel.sh/docs/api-reference/profiles/list-profiles.md) or [CLI reference](https://www.kernel.sh/docs/reference/cli/profiles.md) to list, inspect, download, rename, and delete profiles.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.