Chrome Extension Vocabulary Builder — Developer Guide

12 min read

Build a Vocabulary Builder Extension

Build a vocabulary builder extension that saves words while browsing, provides definitions via dictionary API, and offers flashcard review with spaced repetition for effective learning.

What You’ll Build

Prerequisites

Project Structure

vocab-builder/
├── manifest.json
├── popup/
│   ├── popup.html
│   ├── popup.css
│   └── popup.js
├── sidepanel/
│   ├── sidepanel.html
│   ├── sidepanel.css
│   └── sidepanel.js
├── background/
│   └── service-worker.js
├── content/
│   └── content.js
└── icons/
    └── icon.png

Manifest Configuration

Create your manifest.json with the required permissions:

{
  "manifest_version": 3,
  "name": "VocabBuilder",
  "version": "1.0.0",
  "description": "Save and learn new vocabulary while browsing",
  "permissions": [
    "storage",
    "contextMenus",
    "activeTab",
    "sidePanel"
  ],
  "side_panel": { "default_path": "sidepanel/sidepanel.html" },
  "action": { "default_popup": "popup/popup.html" },
  "background": { "service_worker": "background/service-worker.js" },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content/content.js"]
  }],
  "icons": { "16": "icons/icon-16.png", "48": "icons/icon-48.png", "128": "icons/icon-128.png" }
}

Step 1: Word Capture

Context Menu Integration

Add “Save to VocabBuilder” option when users select text on any page.

// background/service-worker.js
chrome.contextMenus.create({
  id: 'saveWord',
  title: 'Save to VocabBuilder',
  contexts: ['selection']
});

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  if (info.menuItemId === 'saveWord' && info.selectionText) {
    const word = info.selectionText.trim();
    await lookupAndSaveWord(word, tab.url);
  }
});

Double-Click Capture

Listen for double-click events in content scripts to capture words.

// content/content.js
document.addEventListener('dblclick', async (e) => {
  const selection = window.getSelection();
  const word = selection.toString().trim();
  
  if (word && word.split(/\s+/).length === 1) {
    const tab = await chrome.runtime.sendMessage({
      type: 'LOOKUP_WORD',
      word: word,
      url: window.location.href
    });
  }
});

Step 2: Dictionary API Integration

Fetch Definitions

Use dictionaryapi.dev to get word definitions without authentication.

// background/service-worker.js
async function lookupWord(word) {
  const response = await fetch(
    `https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(word)}`
  );
  
  if (!response.ok) {
    throw new Error('Word not found');
  }
  
  const data = await response.json();
  return parseDictionaryResponse(data, word);
}

function parseDictionaryResponse(data, word) {
  const entry = data[0];
  const definitions = [];
  
  for (const meaning of entry.meanings) {
    for (const def of meaning.definitions) {
      definitions.push({
        partOfSpeech: meaning.partOfSpeech,
        definition: def.definition,
        example: def.example || null
      });
    }
  }
  
  return {
    word: entry.word,
    phonetic: entry.phonetic || '',
    definitions: definitions.slice(0, 3)
  };
}

Step 3: Storage Schema

Word Data Structure

Store vocabulary with spaced repetition metadata.

// background/service-worker.js
const VOCAB_KEY = 'vocabulary';

async function saveWord(wordData, sourceUrl) {
  const vocab = await getVocabulary();
  
  const wordEntry = {
    id: generateId(),
    word: wordData.word,
    phonetic: wordData.phonetic,
    definitions: wordData.definitions,
    sourceUrl: sourceUrl,
    contextSentence: null,
    addedAt: Date.now(),
    // SM-2 algorithm fields
    repetition: 0,
    interval: 1,
    easeFactor: 2.5,
    nextReview: Date.now()
  };
  
  vocab[wordData.word.toLowerCase()] = wordEntry;
  await chrome.storage.local.set({ [VOCAB_KEY]: vocab });
  
  return wordEntry;
}

async function getVocabulary() {
  const result = await chrome.storage.local.get(VOCAB_KEY);
  return result[VOCAB_KEY] || {};
}

Step 4: Popup Word List

Display Saved Words

Show all saved words with search and filter capabilities.

<!-- popup/popup.html -->
<div class="vocab-popup">
  <input type="text" id="searchInput" placeholder="Search words..." />
  <div id="wordList" class="word-list"></div>
  <div class="stats">
    <span id="totalWords">0</span> words |
    <span id="dueReview">0</span> due for review
  </div>
</div>
// popup/popup.js
async function renderWordList(filter = '') {
  const vocab = await getVocabulary();
  const words = Object.values(vocab);
  
  const filtered = filter 
    ? words.filter(w => w.word.toLowerCase().includes(filter.toLowerCase()))
    : words;
  
  const wordList = document.getElementById('wordList');
  wordList.innerHTML = filtered.map(word => `
    <div class="word-item" data-word="${word.word}">
      <span class="word">${word.word}</span>
      <span class="phonetic">${word.phonetic}</span>
      <span class="definition">${word.definitions[0]?.definition || ''}</span>
    </div>
  `).join('');
}

document.getElementById('searchInput').addEventListener('input', (e) => {
  renderWordList(e.target.value);
});

Step 5: Flashcard Review (SM-2 Algorithm)

Spaced Repetition Implementation

Implement the SM-2 algorithm for optimal review scheduling.

// sidepanel/sidepanel.js
function calculateNextReview(word, quality) {
  // quality: 0-5 (0-2 = fail, 3-5 = pass)
  let { repetition, interval, easeFactor } = word;
  
  if (quality < 3) {
    repetition = 0;
    interval = 1;
  } else {
    if (repetition === 0) {
      interval = 1;
    } else if (repetition === 1) {
      interval = 6;
    } else {
      interval = Math.round(interval * easeFactor);
    }
    repetition += 1;
  }
  
  easeFactor = easeFactor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));
  easeFactor = Math.max(1.3, easeFactor);
  
  return {
    repetition,
    interval,
    easeFactor,
    nextReview: Date.now() + interval * 24 * 60 * 60 * 1000
  };
}

async function getDueCards() {
  const vocab = await getVocabulary();
  const now = Date.now();
  
  return Object.values(vocab)
    .filter(word => word.nextReview <= now)
    .sort((a, b) => a.nextReview - b.nextReview);
}

Flashcard UI

Display cards with rating buttons for review.

// sidepanel/sidepanel.js
function showFlashcard(word) {
  const card = document.getElementById('flashcard');
  const front = card.querySelector('.front');
  const back = card.querySelector('.back');
  
  front.textContent = word.word;
  back.innerHTML = `
    <div class="phonetic">${word.phonetic}</div>
    <div class="definitions">
      ${word.definitions.map(d => `
        <p><em>${d.partOfSpeech}</em>: ${d.definition}</p>
      `).join('')}
    </div>
    ${word.sourceUrl ? `<a href="${word.sourceUrl}" target="_blank">Source</a>` : ''}
  `;
  
  card.classList.add('flipped');
}

async function rateCard(wordId, quality) {
  const vocab = await getVocabulary();
  const word = vocab[wordId];
  
  const updates = calculateNextReview(word, quality);
  Object.assign(word, updates);
  
  vocab[wordId] = word;
  await chrome.storage.local.set({ vocabulary: vocab });
  
  showNextCard();
}

Step 6: Progress and Export

Statistics Dashboard

Show learning progress and upcoming reviews.

async function getStats() {
  const vocab = await getVocabulary();
  const words = Object.values(vocab);
  const now = Date.now();
  
  return {
    total: words.length,
    mastered: words.filter(w => w.repetition >= 5).length,
    learning: words.filter(w => w.repetition > 0 && w.repetition < 5).length,
    new: words.filter(w => w.repetition === 0).length,
    dueToday: words.filter(w => w.nextReview <= now).length
  };
}

Export Functionality

Export vocabulary as JSON or CSV.

function exportVocabulary(format = 'json') {
  getVocabulary().then(vocab => {
    const words = Object.values(vocab);
    
    if (format === 'json') {
      downloadFile(JSON.stringify(words, null, 2), 'vocabulary.json', 'application/json');
    } else if (format === 'csv') {
      const csv = ['word,phonetic,definition,addedAt,repetition']
        .concat(words.map(w => 
          `"${w.word}","${w.phonetic}","${w.definitions[0]?.definition || ''}","${w.addedAt}",${w.repetition}`
        )).join('\n');
      downloadFile(csv, 'vocabulary.csv', 'text/csv');
    }
  });
}

function downloadFile(content, filename, type) {
  const blob = new Blob([content], { type });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

Cross-references

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article