Chrome Extension PubMed Search Helper (2026)
PubMed remains one of the most critical resources for biomedical researchers, clinicians, and developers working in healthcare technology. With over 35 million citations in the database, finding relevant literature efficiently requires more than just manual browsing. A well-built Chrome extension for PubMed search can transform your research workflow, enabling rapid queries, automated filtering, and smooth integration with citation managers.
This guide walks you through building a custom PubMed search helper extension, covering the essential APIs, practical code examples, and implementation strategies that work for both personal projects and collaborative research tools.
Understanding the PubMed E-utilities API
The National Library of Medicine provides the E-utilities API as the official programmatic interface to PubMed. Before building your extension, you need to understand the core endpoints:
- esearch: Performs term-based searches and returns PMIDs (PubMed IDs)
- efetch: Retrieves detailed records in various formats
- esummary: Returns concise summary data for given PMIDs
- elink: Finds related articles and linked resources
For a Chrome extension, you’ll primarily work with esearch and efetch. The API requires proper formatting of queries using PubMed’s advanced search syntax.
API Rate Limits and Authentication
Without an API key, the E-utilities API allows 3 requests per second. With a registered API key, that ceiling rises to 10 requests per second. For a personal research extension, the unauthenticated limit is usually sufficient. For a team tool or any extension that might batch-fetch many records, register a free API key at the NCBI website and include it as a query parameter:
const NCBI_API_KEY = 'your_api_key_here'; // store in chrome.storage, not in source
async function buildParams(overrides = {}) {
const { apiKey } = await chrome.storage.local.get('apiKey');
return new URLSearchParams({
retmode: 'json',
...(apiKey ? { api_key: apiKey } : {}),
...overrides
});
}
Storing the API key in chrome.storage.local rather than hardcoding it in source files is important if you ever plan to publish the extension. Users supply their own key through an options page, and it stays off GitHub.
Setting Up Your Extension Project
A Chrome extension requires a manifest file, background scripts, and content scripts. Here’s the basic structure:
pubmed-search-helper/
manifest.json
background.js
popup.html
popup.js
content.js
options.html
options.js
styles.css
The manifest defines permissions and entry points:
{
"manifest_version": 3,
"name": "PubMed Search Helper",
"version": "1.0",
"permissions": ["activeTab", "storage", "contextMenus"],
"host_permissions": ["https://eutils.ncbi.nlm.nih.gov/*"],
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js"
},
"options_page": "options.html"
}
Notice the host permission for eutils.ncbi.nlm.nih.gov, this allows your extension to communicate with the PubMed API directly from background scripts. Adding contextMenus to the permissions list enables a right-click shortcut that is covered later in this guide.
Implementing the Search Functionality
The core of your extension is the search function that queries PubMed. Here’s a practical implementation:
async function searchPubMed(query, maxResults = 20, filters = []) {
const baseUrl = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi';
// Append any active filter presets to the query
const fullQuery = filters.length > 0
? `(${query}) AND (${filters.join(' OR ')})`
: query;
const params = new URLSearchParams({
db: 'pubmed',
term: fullQuery,
retmax: maxResults,
retmode: 'json',
sort: 'relevance',
usehistory: 'y' // enables server-side query cache for pagination
});
const response = await fetch(`${baseUrl}?${params}`);
const data = await response.json();
return {
ids: data.esearchresult.idlist,
total: parseInt(data.esearchresult.count, 10),
queryKey: data.esearchresult.querykey,
webEnv: data.esearchresult.webenv
};
}
This function takes a search query and returns an array of PMIDs. The sort: 'relevance' parameter ensures the most pertinent results appear first, which is essential when you’re scanning through many matches. The usehistory: 'y' flag stores the result set server-side, enabling efficient pagination, request the next page using the returned queryKey and webEnv values rather than re-executing the full search.
PubMed Query Syntax Essentials
PubMed’s query language is more expressive than a plain-text search engine. Understanding a few key qualifiers makes your extension significantly more useful:
| Qualifier | Example | Effect |
|---|---|---|
[ti] |
cancer[ti] |
Search title only |
[au] |
Smith J[au] |
Filter by author name |
[pt] |
review[pt] |
Filter by publication type |
[dp] |
2023:2025[dp] |
Date range filter |
[mh] |
Neoplasms[mh] |
MeSH heading filter |
[ta] |
Lancet[ta] |
Filter by journal |
Building a UI that exposes these qualifiers as guided fields, rather than forcing users to remember the syntax, is one of the highest-value improvements over PubMed’s own search interface.
Fetching Article Details
Once you have PMIDs, you need to fetch the actual article metadata. Use the efetch endpoint for full records or esummary for lighter-weight document summaries:
// esummary is faster and sufficient for listing views
async function fetchSummaries(pmids) {
const baseUrl = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi';
const params = new URLSearchParams({
db: 'pubmed',
id: pmids.join(','),
retmode: 'json'
});
const response = await fetch(`${baseUrl}?${params}`);
return await response.json();
}
// efetch returns full XML with abstract and MeSH terms
async function fetchFullRecord(pmid) {
const baseUrl = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi';
const params = new URLSearchParams({
db: 'pubmed',
id: pmid,
retmode: 'xml',
rettype: 'abstract'
});
const response = await fetch(`${baseUrl}?${params}`);
const xmlText = await response.text();
return parseAbstractXml(xmlText);
}
function parseAbstractXml(xmlText) {
const parser = new DOMParser();
const doc = parser.parseFromString(xmlText, 'text/xml');
const abstractTexts = doc.querySelectorAll('AbstractText');
return Array.from(abstractTexts).map(el => ({
label: el.getAttribute('Label') || '',
text: el.textContent
}));
}
Use esummary for your default list view, it returns title, authors, journal, and publication date in a single lightweight response. Reserve efetch for the “expand” action when a user clicks into a specific article to read the abstract, which keeps the initial search response fast.
This approach batches requests, which is far more efficient than fetching each article individually. For a typical search result of 20 articles, a single API call retrieves all the metadata.
Building the Popup Interface
The popup provides the user interface for executing searches without leaving your current tab. Here’s a minimal HTML structure:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="search-container">
<input type="text" id="search-input" placeholder="e.g. CRISPR off-target effects[ti]" autofocus>
<div id="filter-row">
<label><input type="checkbox" name="filter" value="review[pt]"> Reviews</label>
<label><input type="checkbox" name="filter" value="clinical trial[pt]"> Clinical trials</label>
<label><input type="checkbox" name="filter" value="free full text[sb]"> Free full text</label>
</div>
<button id="search-btn">Search PubMed</button>
</div>
<div id="results-meta"></div>
<div id="results"></div>
<script src="popup.js"></script>
</body>
</html>
Connect the popup to your search functionality:
document.getElementById('search-btn').addEventListener('click', async () => {
const query = document.getElementById('search-input').value.trim();
if (!query) return;
const activeFilters = Array.from(
document.querySelectorAll('input[name="filter"]:checked')
).map(el => el.value);
document.getElementById('results').innerHTML = '<p class="loading">Searching...</p>';
const { ids, total } = await searchPubMed(query, 20, activeFilters);
document.getElementById('results-meta').textContent =
`Showing ${ids.length} of ${total.toLocaleString()} results`;
const articles = await fetchSummaries(ids);
displayResults(articles);
await addToHistory(query, total);
});
The display function formats each result with title, authors, and journal information:
function displayResults(data) {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '';
if (!data.result) {
resultsDiv.innerHTML = '<p class="empty">No results found.</p>';
return;
}
const uids = data.result.uids || [];
uids.forEach(pmid => {
const article = data.result[pmid];
const pubYear = article.pubdate?.split(' ')[0] || '';
const authors = article.authors?.map(a => a.name) || [];
const authorText = authors.length > 3
? `${authors.slice(0, 3).join(', ')} et al.`
: authors.join(', ');
const item = document.createElement('div');
item.className = 'article-result';
item.innerHTML = `
<h3><a href="https://pubmed.gov/${pmid}" target="_blank">${article.title}</a></h3>
<p class="authors">${authorText}</p>
<p class="journal">${article.fulljournalname} <span class="year">${pubYear}</span></p>
<div class="actions">
<button class="btn-abstract" data-pmid="${pmid}">Abstract</button>
<button class="btn-cite" data-pmid="${pmid}">Copy citation</button>
<button class="btn-save" data-pmid="${pmid}">Save</button>
</div>
`;
resultsDiv.appendChild(item);
});
attachActionListeners();
}
Context Menu Integration
One of the most useful features for active researchers is the ability to select text on any web page and immediately search PubMed for it. Register a context menu entry in your background script:
// background.js
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'search-pubmed',
title: 'Search PubMed for "%s"',
contexts: ['selection']
});
});
chrome.contextMenus.onClicked.addListener(async (info) => {
if (info.menuItemId === 'search-pubmed') {
const query = info.selectionText.trim();
// Open popup is not possible from background; open a new tab instead
const url = `https://pubmed.gov/?term=${encodeURIComponent(query)}`;
chrome.tabs.create({ url });
}
});
A more sophisticated version stores the selected text in chrome.storage.session and has the popup read it on open, automatically pre-populating the search field. This keeps the user inside the extension’s interface rather than redirecting to PubMed’s standard search page.
Advanced Features for Power Users
Beyond basic search, consider implementing these features that significantly enhance research productivity:
Saved Searches
Store frequently used queries using Chrome’s storage API:
async function saveSearch(query, name) {
const { savedSearches = [] } = await chrome.storage.local.get('savedSearches');
savedSearches.push({ name, query, date: new Date().toISOString() });
await chrome.storage.local.set({ savedSearches });
}
async function getSavedSearches() {
const { savedSearches = [] } = await chrome.storage.local.get('savedSearches');
return savedSearches.sort((a, b) => new Date(b.date) - new Date(a.date));
}
Search History
Automatically track your search history to revisit previous queries:
async function addToHistory(query, resultCount) {
const { history = [] } = await chrome.storage.local.get('history');
history.unshift({ query, resultCount, timestamp: Date.now() });
// Keep only last 50 searches
await chrome.storage.local.set({ history: history.slice(0, 50) });
}
Filter Presets
Create reusable filter configurations for common search scenarios:
const filterPresets = {
'clinical-trials': '[Clinical Trial] OR [Randomized Controlled Trial]',
'reviews': 'review[pt]',
'systematic-reviews': 'systematic review[pt]',
'free-full-text': 'free full text[sb]',
'last-year': `${new Date().getFullYear() - 1}:${new Date().getFullYear()}[dp]`,
'humans-only': 'humans[mh]'
};
Append these filters to your base queries to quickly narrow results. Expose them as toggleable chips in the popup UI rather than checkboxes, chips take less vertical space and can be activated with a single click.
Citation Formatting
Researchers need citations in multiple formats. A citation formatter that reads from esummary data covers most use cases:
function formatCitation(article, style = 'apa') {
const authors = article.authors?.map(a => a.name) || [];
const year = article.pubdate?.split(' ')[0] || '';
const journal = article.fulljournalname || article.source;
const volume = article.volume || '';
const issue = article.issue ? `(${article.issue})` : '';
const pages = article.pages || '';
const authorStr = authors.length > 6
? `${authors.slice(0, 6).join(', ')}, ... ${authors[authors.length - 1]}`
: authors.join(', ');
if (style === 'apa') {
return `${authorStr} (${year}). ${article.title} ${journal}, ${volume}${issue}, ${pages}.`;
}
if (style === 'vancouver') {
return `${authorStr}. ${article.title}. ${journal}. ${year};${volume}${issue}:${pages}.`;
}
return `${authorStr} "${article.title}" ${journal} ${year}`;
}
Adding a one-click copy button for formatted citations removes one of the most tedious parts of literature review.
Integrating with Citation Managers
Most serious researchers use citation managers like Zotero, Mendeley, or EndNote. A PubMed Chrome extension can export results directly in formats these tools understand, eliminating copy-paste friction.
Zotero uses a COinS (Context Objects in Spans) metadata format that your extension can inject into popup HTML. Alternatively, export search results as RIS format, which every major citation manager imports:
function exportToRIS(articles) {
const lines = [];
Object.keys(articles.result).forEach(pmid => {
if (pmid === 'uids') return;
const article = articles.result[pmid];
lines.push('TY - JOUR');
lines.push(`TI - ${article.title}`);
lines.push(`JO - ${article.fulljournalname}`);
lines.push(`PY - ${article.pubdate?.split(' ')[0] || ''}`);
(article.authors || []).forEach(author => {
lines.push(`AU - ${author.name}`);
});
lines.push(`UR - https://pubmed.ncbi.nlm.nih.gov/${pmid}/`);
lines.push('ER -');
lines.push('');
});
return lines.join('\n');
}
function downloadRIS(risContent, filename = 'pubmed-results.ris') {
const blob = new Blob([risContent], { type: 'application/x-research-info-systems' });
const url = URL.createObjectURL(blob);
chrome.downloads.download({ url, filename });
}
For BibTeX export, map PubMed fields to BibTeX keys. Most downstream tools like LaTeX editors and Overleaf accept BibTeX directly:
function exportToBibTeX(articles) {
return Object.keys(articles.result).filter(k => k !== 'uids').map(pmid => {
const a = articles.result[pmid];
const year = a.pubdate?.match(/\d{4}/)?.[0] || '';
const key = `${(a.authors?.[0]?.name?.split(' ').pop() || 'Unknown')}${year}`;
return `@article{${key},
title = {${a.title}},
journal = {${a.fulljournalname}},
year = {${year}},
url = {https://pubmed.ncbi.nlm.nih.gov/${pmid}/}
}`;
}).join('\n\n');
}
Rate Limiting and API Etiquette
The NCBI E-utilities API has rate limits that matter for production extensions. Without an API key, you’re limited to 3 requests per second. With a registered API key, the limit increases to 10 requests per second.
Register for a free NCBI API key at the NCBI website and include it in all requests:
const NCBI_API_KEY = 'your_api_key_here'; // Store in chrome.storage, not hardcoded
async function searchPubMedWithKey(query, maxResults = 20) {
const baseUrl = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi';
const params = new URLSearchParams({
db: 'pubmed',
term: query,
retmax: maxResults,
retmode: 'json',
sort: 'relevance',
api_key: NCBI_API_KEY
});
const response = await fetch(`${baseUrl}?${params}`);
if (!response.ok) {
if (response.status === 429) throw new Error('Rate limit exceeded. Wait before retrying.');
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
Add exponential backoff for retries. Batch multiple PMID fetches into single requests rather than one request per article, the id parameter accepts comma-separated lists of up to 200 PMIDs.
For extensions serving multiple users from the same IP (unlikely but possible in shared environments), cache results aggressively. A 24-hour cache on search results is reasonable for most research workflows since PubMed indexing has its own delay.
Handling Advanced PubMed Query Syntax
The basic term parameter in the E-utilities search API accepts PubMed’s full advanced query syntax, which is substantially more powerful than plain keyword search. Building a query builder UI in your extension dramatically improves search precision for researchers.
PubMed field tags let researchers target specific parts of records. Common tags your extension should support:
[tiab]. Title and abstract only (avoids MeSH noise)[au]. Author name[dp]. Date of publication[mh]. MeSH heading[pt]. Publication type (e.g.,review[pt],clinical trial[pt])[ta]. Journal name abbreviation
Build a simple query constructor function that assembles these:
function buildAdvancedQuery(params) {
const parts = [];
if (params.keywords) {
parts.push(`(${params.keywords})[tiab]`);
}
if (params.author) {
parts.push(`${params.author}[au]`);
}
if (params.journal) {
parts.push(`${params.journal}[ta]`);
}
if (params.publicationType) {
parts.push(`${params.publicationType}[pt]`);
}
if (params.yearFrom && params.yearTo) {
parts.push(`${params.yearFrom}:${params.yearTo}[dp]`);
}
return parts.join(' AND ');
}
// Example usage
const query = buildAdvancedQuery({
keywords: 'machine learning cancer diagnosis',
publicationType: 'review',
yearFrom: 2022,
yearTo: 2026
});
// Produces: (machine learning cancer diagnosis)[tiab] AND review[pt] AND 2022:2026[dp]
Add a UI panel in your popup where researchers can fill in structured fields rather than writing raw query syntax. Display the assembled query string so users can copy it to PubMed directly if needed. This makes your extension useful both as a search tool and as a query learning aid for researchers new to PubMed’s syntax.
Deployment and Distribution
When your extension is ready, package it for distribution:
- Navigate to
chrome://extensions/ - Enable Developer mode
- Click “Pack extension”
- Select your extension directory
For Chrome Web Store distribution, you’ll need to create a developer account and prepare store listing assets. The review process typically takes 1-3 days.
If distributing within an institution, consider using enterprise policy deployment rather than the Chrome Web Store. This allows IT administrators to push the extension to all managed Chrome profiles without individual installs, and is how many hospital systems and research universities deploy tools for clinical staff.
Testing Before Submission
Before submitting to the Chrome Web Store, run through these verification steps:
- Confirm API calls work at the NCBI rate limit without triggering 429 responses
- Test the extension in an Incognito window to catch any assumptions about persistent session state
- Verify that error states (API down, no results, malformed query) display useful messages rather than blank or broken UI
- Check that the extension does not make any calls to third-party servers besides
eutils.ncbi.nlm.nih.gov, the Chrome Web Store review team flags unexpected network destinations
Conclusion
Building a Chrome extension for PubMed search puts powerful research capabilities directly in your browser. The key to a useful tool lies in understanding the E-utilities API, implementing efficient data fetching with proper batching, and creating an intuitive interface that fits smoothly into your workflow.
Start with the basic search functionality outlined here, then iterate based on your specific research needs. The context menu integration alone, selecting a drug name or disease term on any page and immediately surfacing relevant literature, is the kind of workflow shortcut that earns genuine daily use. The citation formatter and saved search features add depth for systematic reviewers and anyone managing a recurring literature monitoring task.
Whether you’re conducting systematic reviews, staying current with new publications, or building tools for a research team, a custom PubMed search helper extension pays dividends in time saved and improved literature discovery. The E-utilities API is stable, well-documented, and entirely free to use, making it one of the more accessible data sources for healthcare technology projects.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Chrome Extension APA Citation Formatter: Automate Your.
- Chrome Extension Development in 2026: A Practical Manifest V3 Guide
- Claude Code Actix Web Rust API Guide
Built by theluckystrike. More at zovo.one