Guides
Bookmarks
Import a browser bookmark export once and give many profiles the same bookmarks — merged or replaced, in one folder or everywhere — applied by each profile's own browser.
A bookmark import is a list of links read from a browser's bookmark export. You import the file once into the workspace library, then queue it on any number of profiles. Each profile's browser applies it through its own bookmarks manager at the next start, before the session is reported ready, and reports exactly what it did.
Import a file
Export bookmarks from any browser (Chrome, Edge, Brave, Firefox …: Bookmark manager → Export bookmarks). The result is a bookmarks.html file in the Netscape bookmark format. The Browlix server reads it with a real HTML parser and keeps only what is safe:
- only
http://andhttps://links — bookmarklets (javascript:),data:,file:,chrome:,place:and every other scheme are rejected with a reason; - addresses carrying a user name or password (
https://user:pass@…) are refused and never stored; - titles are plain text; icons and descriptions in the file are ignored;
- the bookmarks bar stays the bookmarks bar, everything else becomes other bookmarks, folders are kept (up to 10 levels);
- at most 10 MB and 5,000 links per import; the same link twice in the same folder is kept once.
import { readFile } from "node:fs/promises";
const imported = await bx.call("bookmarks.import", {
body: { name: "Team links October", html: await readFile("./bookmarks.html", "utf8"), file_name: "bookmarks.html" },
});
const set = imported.data.set;
console.log(set.links, set.rejected_count); // what was kept, how many were refusedImporting the exact same list again returns the existing import (created: false). bookmarks.get returns every link (root bar/other, folder path, title, url) and up to 50 examples of rejected links with their reason: unsafe_scheme, invalid_url, credentials, too_long, too_deep or over_limit.
Apply it to profiles
Applying is compare-and-set per profile: first read each profile's bookmark revision, then send it back as expected_rev. A profile whose bookmarks changed in between (another import was queued or applied) is reported as conflict and nothing is queued for it — read again and decide.
const set = (await bx.call("bookmarks.list", {})).data.find((s) => s.name === "Team links October");
const profileIds = ["…", "…"];
const preview = await bx.call("bookmarks.preview", { body: { profile_ids: profileIds } });
if (set) {
const queued = await bx.call("bookmarks.apply", {
path: { id: set.id },
body: {
mode: "merge",
scope: "folder",
folder: "Team links",
targets: preview.data.map((p) => ({ profile_id: p.profile_id, expected_rev: p.rev })),
},
});
console.log(queued.data.queued, queued.data.conflicts, queued.data.not_found);
}| Choice | Effect in each profile |
|---|---|
mode: "merge" | adds links that are not already in the target folder; existing bookmarks stay |
mode: "replace" | first removes the bookmarks in the target, then imports the list |
scope: "folder" | everything goes into one folder on the bookmarks bar (folder, created if missing), keeping the file's subfolders |
scope: "all" | the bookmarks bar and other bookmarks exactly as in the file |
replace with scope: "all" removes every existing bookmark of those profiles first — the web app asks for an explicit confirmation before sending it.
Up to 500 profiles per call; profiles outside your groups are not_found. A queued application can be withdrawn with bookmarks.cancel until a browser takes it.
What the machine does
A machine needs worker 0.16.0 or newer. An older worker never receives imports: they stay queued (the session shows them as waiting) until the profile opens on an updated machine.
When a profile starts, the session start names each queued import and how many links it carries; only the machine holding that start can fetch the links. Its browser opens its own bookmarks manager in the background, applies the imports in the order they were queued (up to 8 per start), closes it, and only then reports the session ready. profiles.bookmarks and sessions.get show the browser's own counts:
| State | Meaning |
|---|---|
pending | waiting for the profile's next start |
applying | a browser took it and is applying it |
applied | done: added, duplicates (already there) and removed (by replace) |
failed | the browser could not apply it; the reason says why (changes already made are counted) |
cancelled | withdrawn before a browser took it |
An import that a machine took but did not confirm — the start failed, or the machine stopped before reporting — goes back to pending and runs at the next start. Merging is idempotent: running it again adds nothing that is already there.
Errors you will see
| Code | When |
|---|---|
FORBIDDEN | importing, applying, withdrawing or deleting without bookmark.import |
VALIDATION_FAILED | not a bookmark export, over 10 MB, no http(s) link left, a missing folder name with scope: "folder", or more than 500 profiles |
CONFLICT | withdrawing an import a browser is applying (or that already ended), or deleting an import profiles still wait for |
NOT_FOUND | the import does not exist in this workspace |
Every decision is in the audit log: bookmark.import, bookmark.apply (per profile, with mode, scope and revision), bookmark.cancel, bookmark.delete.
