Back to action list

Bookmarks

BookmarksOpen in New Group

const tabIds = [];
for (const bookmark of bookmarks) {
  if (!bookmark.url) continue;
  const tab = await chrome.tabs.create({ url: bookmark.url, active: false });
  tabIds.push(tab.id);
}
if (tabIds.length === 0) return;
const groupId = await chrome.tabs.group({ tabIds });
const color = (inputs.color || '').toLowerCase();
const updateProps = {};
if (inputs.name) updateProps.title = inputs.name;
if (color && color !== 'unspecified') updateProps.color = color;
if (Object.keys(updateProps).length > 0) {
  await chrome.tabGroups.update(groupId, updateProps);
}

BookmarksSort by URL

const sorted = [...bookmarks].sort((a, b) => (a.url || "").localeCompare(b.url || ""));
for (const [i, bookmark] of sorted.entries()) {
  await chrome.bookmarks.move(bookmark.id, { index: i });
}

BookmarksSort by Title

const sorted = [...bookmarks].sort((a, b) => (a.title || "").localeCompare(b.title || ""));
for (const [i, bookmark] of sorted.entries()) {
  await chrome.bookmarks.move(bookmark.id, { index: i });
}

BookmarksDelete Duplicate Bookmarks (Keep Last)

const lastByUrl = new Map();
for (const bookmark of bookmarks) {
  if (!bookmark.url) continue;
  lastByUrl.set(bookmark.url, bookmark.id);
}
const keepIds = new Set(lastByUrl.values());
for (const bookmark of bookmarks) {
  if (bookmark.url && !keepIds.has(bookmark.id)) {
    await chrome.bookmarks.remove(bookmark.id);
  }
}

BookmarksFind & Replace in URL

const replace = inputs.replace || "";
const regex = new RegExp(inputs.find, "g");
if (!bookmark.url) return;
const newUrl = bookmark.url.replace(regex, replace);
if (newUrl !== bookmark.url) {
  await chrome.bookmarks.update(bookmark.id, { url: newUrl });
}

BookmarksFind & Replace in Title

const replace = inputs.replace || "";
const regex = new RegExp(inputs.find, "g");
const currentTitle = bookmark.title || "";
const newTitle = currentTitle.replace(regex, replace);
if (newTitle !== currentTitle) {
  await chrome.bookmarks.update(bookmark.id, { title: newTitle });
}

Downloads

History

HistoryOpen in New Group

const tabIds = [];
for (const visit of visits) {
  if (!visit.url) continue;
  const tab = await chrome.tabs.create({ url: visit.url, active: false });
  tabIds.push(tab.id);
}
if (tabIds.length === 0) return;
const groupId = await chrome.tabs.group({ tabIds });
const color = (inputs.color || '').toLowerCase();
const updateProps = {};
if (inputs.name) updateProps.title = inputs.name;
if (color && color !== 'unspecified') updateProps.color = color;
if (Object.keys(updateProps).length > 0) {
  await chrome.tabGroups.update(groupId, updateProps);
}

HistoryAdd to Bookmarks

let parentId;
if (inputs.destination && inputs.destination !== '(Default)') {
  parentId = await resolveBookmarkFolder(inputs);
}
await chrome.bookmarks.create({
  parentId,
  title: visit.title || visit.url,
  url: visit.url
});

Global

GlobalSearch

const dispositionMap = { 'Current Tab': 'CURRENT_TAB', 'New Window': 'NEW_WINDOW' };
const disposition = dispositionMap[inputs.disposition] || 'NEW_TAB';
return chrome.search.query({ text: inputs.query, disposition });

GlobalAdd to Bookmarks

const url = (inputs.url || "").trim();
let parentId;
if (inputs.destination && inputs.destination !== "(Default)") {
  parentId = await resolveBookmarkFolder(inputs);
}
await chrome.bookmarks.create({
  parentId,
  title: (inputs.title || "").trim() || url,
  url
});

GlobalGet Info (Copy to Clipboard)

const types = Array.isArray(inputs?.types) && inputs.types.length > 0 ? inputs.types : null;
const info = await getSystemInfo(types);
await copyToClipboard(JSON.stringify(info, null, 2));

GlobalGet Info (Download JSON)

const types = Array.isArray(inputs?.types) && inputs.types.length > 0 ? inputs.types : null;
const info = await getSystemInfo(types);
const filename = `system-info-${new Date().getTime()}.json`;
const json = JSON.stringify(info, null, 2);
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
await chrome.downloads.download({ url, filename });

Reading List

Reading ListOpen in New Group

const tabIds = [];
for (const entry of entries) {
  if (!entry.url) continue;
  const tab = await chrome.tabs.create({ url: entry.url, active: false });
  tabIds.push(tab.id);
}
if (tabIds.length === 0) return;
const groupId = await chrome.tabs.group({ tabIds });
const color = (inputs.color || '').toLowerCase();
const updateProps = {};
if (inputs.name) updateProps.title = inputs.name;
if (color && color !== 'unspecified') updateProps.color = color;
if (Object.keys(updateProps).length > 0) {
  await chrome.tabGroups.update(groupId, updateProps);
}

Tabs

TabsCreate New Tab

const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
const url = inputs.url || undefined;
const position = inputs.position || "Right of Tab";
let index;
if (position === "Right of Tab") {
  index = activeTab ? activeTab.index + 1 : undefined;
} else if (position === "Left of Tab") {
  index = activeTab ? activeTab.index : undefined;
} else if (position === "First") {
  index = 0;
} else if (position === "Last") {
  index = undefined; // Chrome places at end by default
} else if (position === "Random") {
  const tabs = await chrome.tabs.query({ currentWindow: true });
  index = Math.floor(Math.random() * (tabs.length + 1));
}
return chrome.tabs.create({ url, index });

TabsGo to Next Tab

if (!Array.isArray(tabs) || tabs.length === 0) return;
const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!activeTab) return;
const sortedTabs = [...tabs].sort((a, b) => a.index - b.index);
const currentIndex = sortedTabs.findIndex(t => t.id === activeTab.id);
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % sortedTabs.length;
const nextTab = sortedTabs[nextIndex];
if (!nextTab) return;
await chrome.tabs.update(nextTab.id, { active: true });
await chrome.windows.update(nextTab.windowId, { focused: true });

TabsGo to Previous Tab

if (!Array.isArray(tabs) || tabs.length === 0) return;
const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!activeTab) return;
const sortedTabs = [...tabs].sort((a, b) => a.index - b.index);
const currentIndex = sortedTabs.findIndex(t => t.id === activeTab.id);
const prevIndex = currentIndex === -1 ? sortedTabs.length - 1 : (currentIndex - 1 + sortedTabs.length) % sortedTabs.length;
const prevTab = sortedTabs[prevIndex];
if (!prevTab) return;
await chrome.tabs.update(prevTab.id, { active: true });
await chrome.windows.update(prevTab.windowId, { focused: true });

TabsHighlight

const tabsByWindow = new Map();
for (const tab of tabs || []) {
  if (!tab) continue;
  if (typeof tab.windowId !== "number" || typeof tab.index !== "number") continue;
  if (!tabsByWindow.has(tab.windowId)) {
    tabsByWindow.set(tab.windowId, []);
  }
  tabsByWindow.get(tab.windowId).push(tab.index);
}
for (const [windowId, indices] of tabsByWindow) {
  if (indices.length > 0) {
    await chrome.tabs.highlight({ windowId, tabs: indices });
  }
}

TabsAdd to New Group

const tabIds = (tabs || []).map((t) => t?.id).filter((id) => typeof id === "number");
const groupId = await chrome.tabs.group({ tabIds });
const color = inputs.color.toLowerCase();
const updateProps = {};
if (inputs.name) updateProps.title = inputs.name;
if (color && color !== "unspecified") updateProps.color = color;
if (Object.keys(updateProps).length > 0) {
  await chrome.tabGroups.update(groupId, updateProps);
}

TabsAdd to Existing Group

const name = (inputs.groupName || '').trim();
const groups = await chrome.tabGroups.query({});
const targetGroup = groups.find((group) => String(group?.title || '').trim().toLowerCase() === name.toLowerCase());
const groupId = targetGroup ? targetGroup.id : null;
if (groupId === null) throw new Error(`Group "${name}" not found`);
const tabIds = (tabs || []).map((t) => t?.id).filter((id) => typeof id === "number");
await chrome.tabs.group({ tabIds, groupId });

TabsAuto-Group by Domain

const domainMap = new Map();
for (const tab of tabs || []) {
  try {
    if (!tab || !tab.url) continue;
    const domain = new URL(tab.url).hostname;
    if (!domainMap.has(domain)) {
      domainMap.set(domain, []);
    }
    domainMap.get(domain).push(tab.id);
  } catch {
    // ignore invalid URLs
  }
}

const color = inputs.color.toLowerCase();
for (const [domain, ids] of domainMap) {
  if (ids.length > 1) {
    const groupId = await chrome.tabs.group({ tabIds: ids });
    const updateProps = { title: domain };
    if (color && color !== "unspecified") updateProps.color = color;
    await chrome.tabGroups.update(groupId, updateProps);
  }
}

TabsClose Duplicate Tabs (Keep First)

const seen = new Set();
const toClose = [];
for (const tab of tabs) {
  if (!tab.url) continue;
  if (seen.has(tab.url)) {
    toClose.push(tab.id);
  } else {
    seen.add(tab.url);
  }
}
if (toClose.length > 0) {
  await chrome.tabs.remove(toClose);
}

TabsClose Duplicate Tabs (Keep Last)

const lastByUrl = new Map();
for (const tab of tabs) {
  if (!tab.url) continue;
  lastByUrl.set(tab.url, tab.id);
}
const keepIds = new Set(lastByUrl.values());
const toClose = [];
for (const tab of tabs) {
  if (tab.url && !keepIds.has(tab.id)) {
    toClose.push(tab.id);
  }
}
if (toClose.length > 0) {
  await chrome.tabs.remove(toClose);
}

TabsSort by URL

const sorted = [...tabs].sort((a, b) => (a.url || "").localeCompare(b.url || ""));
for (const [i, tab] of sorted.entries()) {
  await chrome.tabs.move(tab.id, { index: i });
}

TabsSort by Title

const sorted = [...tabs].sort((a, b) => (a.title || "").localeCompare(b.title || ""));
for (const [i, tab] of sorted.entries()) {
  await chrome.tabs.move(tab.id, { index: i });
}

TabsShuffle

const shuffled = shuffleArray(tabs);
for (const [i, tab] of shuffled.entries()) {
  await chrome.tabs.move(tab.id, { index: i });
}

TabsZoom In

const zoom = await chrome.tabs.getZoom(tab.id);
const value = String(inputs.step ?? '').trim();
let delta;
if (!value) {
  delta = 0.1;
} else if (value.endsWith('%')) {
  const percent = parseFloat(value.slice(0, -1));
  delta = percent / 100;
} else {
  const numeric = parseFloat(value);
  delta = numeric <= 1 ? numeric : numeric / 100;
}
await chrome.tabs.setZoom(tab.id, Math.min(zoom + delta, ZOOM_LIMITS.max));

TabsZoom Out

const zoom = await chrome.tabs.getZoom(tab.id);
const value = String(inputs.step ?? '').trim();
let delta;
if (!value) {
  delta = 0.1;
} else if (value.endsWith('%')) {
  const percent = parseFloat(value.slice(0, -1));
  delta = percent / 100;
} else {
  const numeric = parseFloat(value);
  delta = numeric <= 1 ? numeric : numeric / 100;
}
await chrome.tabs.setZoom(tab.id, Math.max(zoom - delta, ZOOM_LIMITS.min));

TabsScreenshot to Downloads

try {
  await chrome.tabs.update(tab.id, { active: true });
  await new Promise(r => setTimeout(r, 100));
  const formatMap = { 'PNG': 'png', 'JPG': 'jpeg' };
  const extMap = { 'PNG': '.png', 'JPG': '.jpg' };
  const chosen = inputs.format || 'PNG';
  const format = formatMap[chosen] || 'png';
  const ext = extMap[chosen] || '.png';
  const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, { format });

  const dateStr = new Date().toISOString().split('T')[0];
  let filename = (inputs.filename || '{{url}}-{{date}}')
    .replace(/\{\{date\}\}/g, dateStr)
    .replace(/\{\{title\}\}/g, tab.title || String(tab.id))
    .replace(/\{\{url\}\}/g, safeHostname(tab.url));
  filename = filename.replace(/[<>:"/\\|?*]+/g, '_').replace(/_{2,}/g, '_').replace(/^_|_$/g, '') + ext;
  await chrome.downloads.download({ url: dataUrl, filename });
} catch (e) {
  logger.error("Screenshot failed:", e, { notify: true });
}

TabsScreenshot to Clipboard

try {
  await chrome.tabs.update(tab.id, { active: true });
  await new Promise(r => setTimeout(r, 100));
  const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" });
  await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: async (url) => {
      const res = await fetch(url);
      const blob = await res.blob();
      const item = new ClipboardItem({ [blob.type]: blob });
      await navigator.clipboard.write([item]);
    },
    args: [dataUrl]
  });
} catch (e) {
  logger.error("Screenshot to clipboard failed:", e, { notify: true });
}

TabsFind & Replace in URL

const replace = inputs.replace || "";
const regex = new RegExp(inputs.find, "g");
const newUrl = tab.url.replace(regex, replace);
if (newUrl !== tab.url) {
  await chrome.tabs.update(tab.id, { url: newUrl });
}

TabsScroll to Top

const behavior = inputs?.smooth === true ? "smooth" : "auto";
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (scrollBehavior) => {
    window.scrollTo({ top: 0, behavior: scrollBehavior });
  },
  args: [behavior]
});

TabsScroll to Bottom

const behavior = inputs?.smooth === true ? "smooth" : "auto";
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (scrollBehavior) => {
    const maxScrollTop = Math.max(
      document.body?.scrollHeight || 0,
      document.documentElement?.scrollHeight || 0
    );
    window.scrollTo({ top: maxScrollTop, behavior: scrollBehavior });
  },
  args: [behavior]
});

TabsScroll Up

const parsedAmount = parseInt(inputs?.amount, 10);
const amount = !isNaN(parsedAmount) && parsedAmount > 0 ? parsedAmount : 300;
const behavior = inputs?.smooth === true ? "smooth" : "auto";
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (pixels, scrollBehavior) => {
    window.scrollBy({ top: -pixels, left: 0, behavior: scrollBehavior });
  },
  args: [amount, behavior]
});

TabsScroll Down

const parsedAmount = parseInt(inputs?.amount, 10);
const amount = !isNaN(parsedAmount) && parsedAmount > 0 ? parsedAmount : 300;
const behavior = inputs?.smooth === true ? "smooth" : "auto";
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (pixels, scrollBehavior) => {
    window.scrollBy({ top: pixels, left: 0, behavior: scrollBehavior });
  },
  args: [amount, behavior]
});

TabsPage Up

const behavior = inputs?.smooth === true ? "smooth" : "auto";
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (scrollBehavior) => {
    window.scrollBy({ top: -window.innerHeight, left: 0, behavior: scrollBehavior });
  },
  args: [behavior]
});

TabsPage Down

const behavior = inputs?.smooth === true ? "smooth" : "auto";
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (scrollBehavior) => {
    window.scrollBy({ top: window.innerHeight, left: 0, behavior: scrollBehavior });
  },
  args: [behavior]
});

TabsScroll Left

const parsedAmount = parseInt(inputs?.amount, 10);
const amount = !isNaN(parsedAmount) && parsedAmount > 0 ? parsedAmount : 300;
const behavior = inputs?.smooth === true ? "smooth" : "auto";
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (pixels, scrollBehavior) => {
    window.scrollBy({ top: 0, left: -pixels, behavior: scrollBehavior });
  },
  args: [amount, behavior]
});

TabsScroll Right

const parsedAmount = parseInt(inputs?.amount, 10);
const amount = !isNaN(parsedAmount) && parsedAmount > 0 ? parsedAmount : 300;
const behavior = inputs?.smooth === true ? "smooth" : "auto";
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (pixels, scrollBehavior) => {
    window.scrollBy({ top: 0, left: pixels, behavior: scrollBehavior });
  },
  args: [amount, behavior]
});

TabsShare

await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: async () => {
    if (typeof navigator.share !== "function") {
      throw new Error("Web Share API is not available on this page");
    }
    await navigator.share({ title: document.title, url: location.href });
  }
});

TabsAdd to Bookmarks

let parentId;
if (inputs.destination && inputs.destination !== '(Default)') {
  parentId = await resolveBookmarkFolder(inputs);
}
await chrome.bookmarks.create({
  parentId,
  title: tab.title || tab.url,
  url: tab.url
});

TabsClear Browsing Data (This Domain)

const dataTypes = normalizeDataTypes(inputs.types);
const dataToRemove = buildDataToRemove(dataTypes);
const since = getSinceFromRange(inputs.range);
if (!tab?.url) return;
const url = new URL(tab.url);
if (url.protocol !== "http:" && url.protocol !== "https:") return;
await chrome.browsingData.remove({ since, origins: [url.origin] }, dataToRemove);

Windows

WindowsCreate New Tab

const windowId = (typeof win === "number" ? win : win?.id);
const url = inputs.url || undefined;
const position = inputs.position || "First";
let index;
if (position === "First") {
  index = 0;
} else if (position === "Last") {
  index = undefined; // Chrome places at end by default
} else if (position === "Random") {
  const currentWindow = await chrome.windows.get(windowId, { populate: true });
  const tabCount = currentWindow.tabs ? currentWindow.tabs.length : 0;
  index = Math.floor(Math.random() * (tabCount + 1));
}
await chrome.tabs.create({ windowId, url, index });

WindowsGo to Next Window

if (!Array.isArray(windows) || windows.length === 0) return;
const focusedWindow = await chrome.windows.getLastFocused();
if (!focusedWindow) return;
const sortedWindows = [...windows].sort((a, b) => a.id - b.id);
const currentIndex = sortedWindows.findIndex(w => w.id === focusedWindow.id);
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % sortedWindows.length;
const nextWindow = sortedWindows[nextIndex];
if (!nextWindow) return;
await chrome.windows.update(nextWindow.id, { focused: true });

WindowsGo to Previous Window

if (!Array.isArray(windows) || windows.length === 0) return;
const focusedWindow = await chrome.windows.getLastFocused();
if (!focusedWindow) return;
const sortedWindows = [...windows].sort((a, b) => a.id - b.id);
const currentIndex = sortedWindows.findIndex(w => w.id === focusedWindow.id);
const prevIndex = currentIndex === -1 ? sortedWindows.length - 1 : (currentIndex - 1 + sortedWindows.length) % sortedWindows.length;
const prevWindow = sortedWindows[prevIndex];
if (!prevWindow) return;
await chrome.windows.update(prevWindow.id, { focused: true });

WindowsResize

const windowId = (typeof win === "number" ? win : win?.id);
const width = parseInt(inputs.width);
const height = parseInt(inputs.height);
const update = {};
if (!isNaN(width)) update.width = width;
if (!isNaN(height)) update.height = height;
if (Object.keys(update).length > 0) {
  return chrome.windows.update(windowId, update);
}

WindowsMove Position

const windowId = (typeof win === "number" ? win : win?.id);
const left = parseInt(inputs.left);
const top = parseInt(inputs.top);
const update = {};
if (!isNaN(left)) update.left = left;
if (!isNaN(top)) update.top = top;
if (Object.keys(update).length > 0) {
  return chrome.windows.update(windowId, update);
}

WindowsDuplicate

const windowId = (typeof win === "number" ? win : win?.id);
const currentWindow = await chrome.windows.get(windowId, { populate: true });
const newWin = await chrome.windows.create({});
const defaultTab = newWin.tabs && newWin.tabs[0];
for (const tab of currentWindow.tabs || []) {
  if (!tab || !tab.url) continue;
  await chrome.tabs.create({ windowId: newWin.id, url: tab.url });
}
if (defaultTab) {
  await chrome.tabs.remove(defaultTab.id);
}

Tab Groups

Tab GroupsMove to New Window

const groupId = (typeof group === "number" ? group : group?.id);
// Get group properties before moving (group will be destroyed when tabs leave)
const currentGroup = await chrome.tabGroups.get(groupId);
const { title, color } = currentGroup;

const tabs = await chrome.tabs.query({ groupId });
if (tabs.length === 0) return;
const tabIds = tabs.map(t => t.id);
const win = await chrome.windows.create({ tabId: tabIds[0] });
if (tabIds.length > 1) {
  await chrome.tabs.move(tabIds.slice(1), { windowId: win.id, index: -1 });
}
// Create a new group in the new window with the same properties
const newGroupId = await chrome.tabs.group({ tabIds, createProperties: { windowId: win.id } });
await chrome.tabGroups.update(newGroupId, { title, color });