Bookmark Injection: Pre-Populate Browser Bookmarks
How to pre-populate browser bookmarks at launch with a JSON value, so test and privacy-research sessions start from a documented, repeatable bookmark state.
BotBrowser Team
Prefer the maintained product doc?
This article has a matching page in the docs center. Use the docs for the canonical setup flow, current flags, and long-term reference.
Why a fresh bookmark bar is an uneven starting point
A browser that people use every day accumulates bookmarks over weeks and months. They get sorted into folders and reflect the user's region, work and interests. A browser instance launched fresh for automation or privacy research starts with an empty bookmark bar, which is a different state from the one the rest of the profile may describe. If the profile says a German-speaking developer in Berlin, an empty bookmark bar is a small inconsistency inside an otherwise coherent setup.
BotBrowser provides the --bot-bookmarks flag to pre-populate the bookmark store when the browser launches. The flag takes a JSON array of url and folder entries, so each test session can start from a bookmark state that you wrote down and can reproduce.
Bookmarks as one layer of browser state
A browser session carries several layers of state: cookies, browsing history, locale, timezone, network configuration and bookmarks. Each layer is a separate piece of data, and each can be set independently. Bookmarks are one of the simplest layers to control because they are plain data with a small structure, and they do not change while a script runs unless something writes to them.
For privacy researchers, controlling bookmark state matters for reproducible experiments. If you compare how a site behaves for a brand-new browser and for a browser with some history, you need to know exactly which state differed between the two runs. Writing the bookmark set into your launch arguments turns an invisible difference into a recorded one that you can review, version and repeat.
For teams that run several identities, each identity benefits from a bookmark set that matches the rest of its profile. A German-language profile with a Berlin timezone and a German locale is easier to reason about when its bookmarks point to German sites. A developer persona is easier to reason about when its bookmarks are technical. The goal is a coherent, documented configuration. It is not a promise about how any website will react.
Empty bookmark bars are also a practical problem for some workflows. If you need to exercise bookmark-related interface elements, such as the bookmark bar, the bookmark manager or omnibox suggestions, a pre-populated store gives you something to work with from the first page load.
How Chromium stores bookmarks
The Bookmarks file
Chromium stores bookmarks in a JSON file called Bookmarks inside the profile folder of the user data directory, for example Default/Bookmarks. The file holds a tree whose root nodes include bookmark_bar, which is shown on the bookmark bar, and other, which is the "Other Bookmarks" folder (a synced root holds mobile bookmarks). The Chrome and BotBrowser pages cited at the end of this article do not document this on-disk layout, so treat the field names below as background and check them against a Bookmarks file in your own profile folder.
Each entry in the tree has these properties:
- name: The display text shown in the bookmark bar or manager
- url: The target URL for URL-type bookmarks
- type: Either
urlfor a bookmark orfolderfor a folder containing children - children: An array of nested bookmarks and folders (for folder-type entries)
- date_added: A timestamp representing when the bookmark was created
When the browser launches, it reads this file and populates the bookmark bar and the bookmark manager. Chromium also keeps internal metadata for each entry, such as an identifier and a timestamp in its own internal format. That metadata is the reason hand-written Bookmarks files are easy to get subtly wrong.
Where seeded bookmarks appear
Bookmarks show up in several places inside the browser:
- Bookmark bar: The most visible location, shown below the address bar when enabled
- Bookmark manager: Accessible through
chrome://bookmarks - Omnibox suggestions: The address bar may suggest bookmarked URLs as you type
The bookmark manager is the most reliable place to confirm a seeded entry, because it lists the whole tree including folders. The other locations depend on user interface settings.
Ways to seed bookmarks without the flag
Editing the Bookmarks file by hand
You can create a user data directory, write or edit the Bookmarks JSON file, and launch the browser with that directory. This works, but it has costs:
- The file format includes metadata fields, such as identifiers and timestamps in Chromium's internal format, that must be correct
- You have to manage user data directories explicitly and track which directory belongs to which identity
- There is no clean separation between bookmark data and the other state persisted in the same directory
Using the chrome.bookmarks API
The chrome.bookmarks API can create bookmarks programmatically, but the Chrome documentation lists it as an extension API. It is not available to regular page JavaScript or to automation frameworks. Using it means installing an extension and granting it the bookmarks permission, which adds a component you then have to maintain, review and keep in sync across identities.
Driving the bookmark interface from a framework
Neither Playwright nor Puppeteer exposes a bookmarks API. Through these frameworks the only built-in route is to open chrome://bookmarks and click through the interface, which is slow and fragile, and it has to be repeated for every new session.
How --bot-bookmarks works
BotBrowser handles bookmark pre-population through the --bot-bookmarks flag, so none of the three routes above is needed.
Population at launch
When --bot-bookmarks is passed, BotBrowser reads the JSON value and populates the browser's bookmark store at launch. The entries are then available in the bookmark manager and wherever else your browser configuration shows bookmarks.
The JSON format
The flag accepts a JSON string describing the bookmark structure:
--bot-bookmarks='[{"title":"Google","type":"url","url":"https://www.google.com"},{"title":"News","type":"folder","children":[{"title":"BBC","type":"url","url":"https://www.bbc.com"},{"title":"Reuters","type":"url","url":"https://www.reuters.com"}]}]'
Every entry has a title and a type. A url entry also needs a url, and a folder entry needs a children array containing further entries. The public Bookmark Seeding documentation describes the fields this way:
- title (required): Display name of the bookmark or folder
- type (required): Either
urlorfolder - url (required for
urlentries): The bookmark URL - children (required for
folderentries): Array of child bookmark entries
Folders can hold other folders, so a structure such as Work, then Projects, then individual links is expressible in one value.
No extension involved
Because the population happens inside the browser at launch, no extension has to be installed or maintained. The set of installed extensions stays what you chose it to be, and the bookmark state is described entirely by the launch arguments.
Alongside other identity settings
Bookmarks combine naturally with other launch settings. A full identity setup can include bookmarks, cookies, browsing history, timezone, locale and network configuration, each set explicitly so the session is internally consistent.
Launching, validating and checking seeded bookmarks
A short bookmark bar
chrome --bot-profile="path/to/profile.enc" \
--bot-bookmarks='[{"title":"Google","type":"url","url":"https://www.google.com"},{"title":"YouTube","type":"url","url":"https://www.youtube.com"}]'
Nested folders
chrome --bot-profile="path/to/profile.enc" \
--bot-bookmarks='[{"title":"Google","type":"url","url":"https://www.google.com"},{"title":"News","type":"folder","children":[{"title":"BBC","type":"url","url":"https://www.bbc.com"},{"title":"Reuters","type":"url","url":"https://www.reuters.com"}]},{"title":"Shopping","type":"folder","children":[{"title":"Amazon","type":"url","url":"https://www.amazon.com"}]}]'
From Puppeteer
const puppeteer = require('puppeteer-core');
const bookmarks = JSON.stringify([
{ title: 'Google', type: 'url', url: 'https://www.google.com' },
{ title: 'YouTube', type: 'url', url: 'https://www.youtube.com' },
{
title: 'News',
type: 'folder',
children: [
{ title: 'BBC', type: 'url', url: 'https://www.bbc.com' },
{ title: 'Reuters', type: 'url', url: 'https://www.reuters.com' },
],
},
]);
(async () => {
const browser = await puppeteer.launch({
executablePath: 'path/to/botbrowser/chrome',
args: ['--bot-profile=path/to/profile.enc', `--bot-bookmarks=${bookmarks}`],
headless: true,
defaultViewport: null,
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
Together with other identity settings
chrome --bot-profile="path/to/profile.enc" \
--bot-bookmarks='[{"title":"Google","type":"url","url":"https://www.google.de"},{"title":"Amazon","type":"url","url":"https://www.amazon.de"}]' \
--bot-inject-random-history \
--bot-cookies="@path/to/cookies.json" \
--bot-timezone=Europe/Berlin \
--bot-locale=de-DE \
--bot-languages=de-DE,de,en \
--proxy-server=socks5://user:pass@de-proxy:1080
In this combination every regional setting points to Germany: the bookmarks, the timezone, the locale, the language list and the network exit. The point of the example is that the layers agree with one another. It does not guarantee how any site will treat the session.
Among these flags, --bot-cookies and --bot-inject-random-history are PRO, and --bot-timezone, --bot-locale and --bot-languages are ENT Tier1, so the full combination needs a plan that includes them. --bot-bookmarks itself is Core.
Building the JSON without quoting mistakes
A common source of trouble with --bot-bookmarks is the JSON value itself. Three habits help avoid most of it.
First, never write the JSON by hand inside a shell command once it grows past a few entries. Build a normal data structure in your script and serialize it with JSON.stringify(), as the Puppeteer example does. The Bookmark Seeding documentation gives the same advice: use JSON.stringify() to build the value and do not wrap it in extra quotes.
Second, when you do write the value in a shell, wrap it in single quotes so that the double quotes inside the JSON survive. A title that contains an apostrophe breaks single-quote wrapping, which is another reason to serialize from code instead.
Third, validate the structure before launch. A small check catches the two mistakes that the documentation lists as typical causes of missing bookmarks: entries without title or type, and folders whose children array holds invalid entries.
function validateBookmarks(entries, path = 'root') {
if (!Array.isArray(entries)) throw new Error(`${path}: expected an array`);
entries.forEach((entry, index) => {
const where = `${path}[${index}]`;
if (!entry.title) throw new Error(`${where}: missing title`);
if (entry.type === 'url') {
if (!entry.url) throw new Error(`${where}: url entries need a url`);
} else if (entry.type === 'folder') {
if (!Array.isArray(entry.children)) throw new Error(`${where}: folder entries need a children array`);
validateBookmarks(entry.children, `${where}.children`);
} else {
throw new Error(`${where}: type must be "url" or "folder"`);
}
});
return entries;
}
const bookmarks = JSON.stringify(validateBookmarks(entries));
Running the check before every launch turns a silent empty folder into an error message with a path to the broken entry. In a test matrix that launches many sessions, that is the difference between a quick fix and a long search through results.
Choosing bookmark sets per identity
Seeded bookmarks work best when they follow the same decisions as the rest of the profile. A practical way to keep this manageable is to define one named bookmark set per identity in a single place and look it up at launch time:
const sets = {
de: [
{ title: 'Google', type: 'url', url: 'https://www.google.de' },
{ title: 'Spiegel', type: 'url', url: 'https://www.spiegel.de' },
{ title: 'Amazon', type: 'url', url: 'https://www.amazon.de' },
],
us: [
{ title: 'Google', type: 'url', url: 'https://www.google.com' },
{ title: 'Reuters', type: 'url', url: 'https://www.reuters.com' },
{ title: 'Amazon', type: 'url', url: 'https://www.amazon.com' },
],
};
const args = ['--bot-profile=path/to/profile.enc', `--bot-bookmarks=${JSON.stringify(sets[identity])}`];
Some guidelines for building the sets:
- Pick sites that a person with that region, language and role would plausibly save, such as a national news outlet, a local retailer or a documentation site.
- Use the regional domain where one exists. A German identity pointing to a German retailer is easier to explain than one pointing only to the global site.
- Keep the sets small enough to review. Ten to fifty entries is a manageable range to maintain by hand, and a short set is easier to compare between runs.
- Use folders where the persona would use them, for example a work folder for a developer or a shopping folder for a consumer persona.
- Store each set in version control next to the launch configuration, so a result can always be traced to the exact bookmark state that produced it.
A worked example shows how this fits a test plan. Suppose you want to compare two runs of the same page: one with an empty bookmark bar and one with the German set above. Launch both with the same profile, timezone and locale, change only the --bot-bookmarks value, and keep the two launch commands in your notes. Open chrome://bookmarks in each run to confirm that the state really differed, then compare whatever output you are studying. If the outputs match, you have learned that bookmark state did not matter for that page. If they differ, you have a documented, repeatable difference to investigate further. Either result is useful, and neither requires guessing.
Treat the sets as a description of the persona you decided to test. They are not evidence that a site reads or weighs bookmarks, and nothing in the flag changes that.
Checking the result after launch
After launching with --bot-bookmarks, open the bookmark manager and compare it with the value you passed:
const page = await browser.newPage();
await page.goto('chrome://bookmarks');
A short checklist helps to keep the comparison honest:
- Open
chrome://bookmarksand confirm that every top-level entry from your JSON is present, and check that the order matches what you expect. - Expand each folder and confirm that the child count matches the
childrenarray. - Check the bookmark bar itself. If it is hidden by the interface settings, the bookmarks can still exist in the manager.
- Type the domain of a seeded URL into the address bar and see whether the suggestion list shows the bookmark entry.
- Record the exact launch value next to the result, so the run can be repeated.
If something is missing, the documented troubleshooting steps are short. When bookmarks do not appear, verify that the JSON is valid and that each entry has title and type fields. When a folder shows empty, make sure its children array contains valid bookmark entries. When the browser reports a JSON parse error, build the value with JSON.stringify() and do not add extra quotes around it.
Limits and habits worth keeping
- Match bookmarks to the profile identity. A German profile should have German-language bookmarks such as google.de, spiegel.de and amazon.de.
- Vary bookmarks across identities. Each identity should have its own set, so that results are not accidentally shared between personas.
- Keep the count reviewable. Very large sets are hard to audit, and an empty set removes the point of seeding.
- Mix categories. People save search, news, shopping, social and work pages. A varied set describes a persona better than a single category.
- Use folders for organization. Folders such as Work, Shopping or News are common and they exercise the nested format.
- Set the other layers explicitly. Bookmarks work alongside
--bot-cookies,--bot-inject-random-historyand the locale and timezone settings. Each of those is its own flag.
BotBrowser supports the --bot-bookmarks flag, which populates the browser's bookmark store at launch from a JSON array of url and folder entries, so you can give each test session a documented, repeatable bookmark state without an extension. It cannot guarantee how any website or tracking system treats bookmark state, it does not replace cookies, history, or other identity layers, and persistence beyond the session depends on your user data directory.
Bookmark seeding questions
What JSON format does --bot-bookmarks accept?
The flag accepts a JSON array of bookmark objects. Each object has a title, a type (either "url" or "folder"), and either a url for URL entries or a children array for folder entries. The JSON is passed as a string directly in the command-line argument.
Can I load bookmarks from a file instead of inline JSON?
The CLI reference documents --bot-bookmarks as taking a JSON string. For larger sets, read your bookmark file inside your launch script, validate it, and pass the serialized value as the launch argument.
When are bookmarks available after launch?
BotBrowser populates the bookmark store at launch from the value you pass. Open chrome://bookmarks to confirm the contents.
Do bookmarks persist across sessions?
According to the Bookmark Seeding documentation, bookmarks persist for the duration of the session and are stored in the user data directory alongside other session data. Whether anything carries over to a later session therefore depends on the user data directory you use. Give each identity its own directory if you want to keep their bookmark state apart.
Can I use it with --bot-inject-random-history?
Yes. The two flags set different layers, bookmarks and browsing history, and you can pass them together in one launch.
Does --bot-bookmarks require a specific tier?
The CLI reference lists --bot-bookmarks as a Core flag. Check the CLI flags page for the current availability of each flag.
How many bookmarks should I include?
Enough to describe the persona and few enough to review by hand. Ten to fifty entries across a few categories is a practical range for a test identity. The right number depends on what you are testing.
Does seeding bookmarks change how websites treat the session?
Do not assume that it does. BotBrowser sets the browser's bookmark state. It makes no claim that any website or tracking system reads or weighs that state, and the chrome.bookmarks API is an extension API in the Chrome documentation, not something page JavaScript uses.
What to take away
Pre-populating bookmarks with --bot-bookmarks gives each BotBrowser session a bookmark state that is written down, reviewable and repeatable. Combined with cookies, browsing history and consistent locale settings, it keeps one more layer of a test identity under your control. Verify the result in chrome://bookmarks, keep the JSON in version control, and treat the feature as one state layer among several. Explore all identity management capabilities on the features page, or check available plans for the full list of tiers.
For related topics, see Cookie Management for session persistence, Synthetic Browsing History for history population, and Profile Management for organizing complete identity sets.
Sources
Related Articles
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.