Skip to main content
The Teak browser extension lets you save web content, links, and text selections directly from your browser to your personal knowledge hub.

Overview

Built with Wxt (Web eXTension framework), the browser extension provides seamless integration with Chrome, Firefox, and other Chromium-based browsers.

Technology Stack

  • Wxt 0.20 framework
  • React 19 for UI
  • Better Auth integration
  • Convex for real-time sync

Browsers

  • Chrome/Chromium
  • Firefox
  • Edge
  • Brave

Key Features

Multiple Save Methods

Right-click to save content:
  • Save Page to Teak: Save the current page URL
  • Save Text to Teak: Save selected text
// Automatically creates context menu items
chrome.contextMenus.create({
  id: "save-page",
  title: "Save Page to Teak",
  contexts: ["page"],
});
Inline save buttons on supported platforms:
  • Instagram: Save posts directly from feed
  • X/Twitter: Save tweets with one click
  • Pinterest: Save pins to your vault
// Platforms supported for inline saving
const SUPPORTED_PLATFORMS = [
  "instagram.com",
  "x.com",
  "twitter.com",
  "pinterest.com"
];
Automatically detects and saves URLs from:
  • Address bar changes
  • Tab switches
  • Bookmarks (optional)

Social Platform Features

The extension integrates seamlessly with social media platforms:
  • Save posts from feed
  • Extract post permalinks
  • Save images and captions
  • Handle carousels

Authentication

Seamless authentication using Better Auth:
  • Cookie-based session sharing with web app
  • Automatic session detection
  • Secure credential storage
  • One-click sign in from popup
import { hasValidSession } from "./utils/getSessionFromCookies";

// Check if user is authenticated
const authenticated = await hasValidSession();

Installation

From Web Stores

Chrome Web Store

Install from the Chrome Web Store (coming soon)

Firefox Add-ons

Install from Firefox Add-ons (coming soon)

Manual Installation (Developer Mode)

1

Build Extension

Build the extension from source:
# Clone repository
cd apps/extension

# Install dependencies
bun install

# Build for Chrome
bun run build

# Or build for Firefox
bun run build:firefox
2

Load in Chrome

  1. Open chrome://extensions/
  2. Enable “Developer mode”
  3. Click “Load unpacked”
  4. Select .output/chrome-mv3 directory
3

Load in Firefox

  1. Open about:debugging#/runtime/this-firefox
  2. Click “Load Temporary Add-on”
  3. Select manifest.json from .output/firefox-mv3

Development

Setup

# Install dependencies
bun install

# Start dev server (Chrome)
bun run dev

# Start dev server (Firefox)
bun run dev:firefox

Configuration

The extension is configured via wxt.config.ts:
wxt.config.ts
import { defineConfig } from "wxt";

export default defineConfig({
  modules: ["@wxt-dev/module-react"],
  manifest: {
    name: "Teak",
    description: "Your personal knowledge hub",
    permissions: [
      "storage",
      "activeTab",
      "tabs",
      "contextMenus",
      "scripting",
      "cookies",
    ],
    host_permissions: ["<all_urls>"],
  },
});

Architecture

Extension Structure

extension/
├── entrypoints/
│   ├── background.ts        # Service worker
│   ├── content.ts           # Content script
│   └── popup/
│       ├── main.tsx         # Popup entry
│       └── App.tsx          # Popup UI
├── lib/
│   ├── convex-api.ts        # Convex integration
│   ├── auth-client.ts       # Auth handling
│   └── saveToTeak.ts        # Core save logic
├── hooks/
│   ├── useContextMenuSave.ts
│   ├── useAutoSaveUrl.ts
│   └── useWebAppSession.ts
├── types/
│   ├── messages.ts          # Message types
│   ├── contextMenu.ts       # Context menu types
│   └── social.ts            # Social platform types
├── utils/
│   └── getSessionFromCookies.ts
└── wxt.config.ts            # Extension config

Message Passing

The extension uses Chrome’s messaging API for communication:
// Message types
export const MESSAGE_TYPES = {
  GET_AUTH_STATE: "GET_AUTH_STATE",
  SAVE_CONTENT: "SAVE_CONTENT",
  SAVE_POST: "SAVE_POST",
} as const;

// Send message from content script
const response = await chrome.runtime.sendMessage({
  type: MESSAGE_TYPES.SAVE_CONTENT,
  payload: { content, source },
});

Content Scripts

Content scripts inject save buttons into social media pages:
entrypoints/content.ts
export default defineContentScript({
  matches: [
    "*://www.instagram.com/*",
    "*://www.x.com/*",
    "*://twitter.com/*",
    "*://www.pinterest.com/*",
  ],
  main() {
    // Inject save buttons
    injectSaveButtons();
  },
});

Background Service Worker

Handles context menu, authentication, and save operations:
entrypoints/background.ts
import { saveToTeak } from "../lib/saveToTeak";

export default defineBackground(() => {
  // Create context menus
  chrome.contextMenus.create({
    id: "save-page",
    title: "Save Page to Teak",
    contexts: ["page"],
  });
  
  // Handle clicks
  chrome.contextMenus.onClicked.addListener(async (info, tab) => {
    const result = await saveToTeak({
      content: tab.url,
      source: "context-menu",
    });
  });
});

Features in Detail

Context Menu Integration

Two context menu items are available:

Save Page

Right-click anywhere on a page to save the current URL with metadata extraction

Save Text

Select text and right-click to save it as a text card
The popup provides quick access to:
  • Current page save status
  • Recent saves
  • Authentication status
  • Extension settings
popup/App.tsx
import { useWebAppSession } from "@/hooks/useWebAppSession";
import { useContextMenuSave } from "@/hooks/useContextMenuSave";

function PopupApp() {
  const { authenticated } = useWebAppSession();
  const { state, error } = useContextMenuSave();
  
  return (
    <div>
      {authenticated ? <SaveStatus /> : <LoginPrompt />}
    </div>
  );
}

Social Media Extractors

Platform-specific content extractors:
entrypoints/content/platforms/
├── instagram.ts     # Instagram post extractor
├── x.ts             # X/Twitter tweet extractor
└── pinterest.ts     # Pinterest pin extractor
Each extractor handles:
  • Post/tweet/pin detection
  • Permalink extraction
  • Metadata parsing
  • Media URL extraction

Auto-Save Functionality

Optional automatic saving of URLs:
hooks/useAutoSaveUrl.ts
export function useAutoSaveUrl() {
  useEffect(() => {
    chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
      if (changeInfo.url && isValidUrl(changeInfo.url)) {
        void saveToTeak({ content: changeInfo.url, source: "auto-save" });
      }
    });
  }, []);
}

Permissions

The extension requires the following permissions:
Store extension settings and cached data
Access the currently active tab for saving content
Manage tabs and detect URL changes
Create right-click context menu items
Inject content scripts for social media integration
Access authentication cookies from the web app
Access all websites for content extraction (required for social media integration)

Privacy & Security

Privacy First

  • No analytics or tracking
  • No data collection beyond what you save
  • Session data stored locally only
  • Secure communication with Convex backend
  • Open source for transparency
The extension reads cookies from teakvault.com to maintain authentication:
utils/getSessionFromCookies.ts
export async function hasValidSession(): Promise<boolean> {
  const cookies = await chrome.cookies.getAll({
    domain: "teakvault.com",
  });
  
  // Check for valid session token
  return cookies.some(c => c.name === "better-auth.session_token");
}

Testing

The extension includes comprehensive tests:
# Run all tests
bun run test

# Test coverage includes:
# - Background script
# - Content script
# - Social extractors
# - Auth utilities
# - Save functionality
// __tests__/background.test.ts
import { describe, it, expect } from "bun:test";

describe("Context Menu", () => {
  it("creates save page menu item", () => {
    // Test context menu creation
  });
});

Publishing

Chrome Web Store

1

Build

Create production build and zip:
bun run build
bun run zip
2

Prepare Assets

  • Screenshots (1280x800 or 640x400)
  • Promotional images
  • Icon (128x128)
  • Description and privacy policy
3

Submit

  1. Go to Chrome Web Store Developer Dashboard
  2. Upload .output/chrome-mv3.zip
  3. Fill in store listing
  4. Submit for review

Firefox Add-ons

1

Build

Create Firefox build and zip:
bun run build:firefox
bun run zip:firefox
2

Submit

  1. Go to Firefox Add-on Developer Hub
  2. Upload .output/firefox-mv3.zip
  3. Submit for review

Troubleshooting

  • Verify manifest.json is valid
  • Check browser console for errors
  • Ensure all dependencies are built
  • Try reloading the extension
  • Ensure you’re logged in to the web app
  • Check cookie permissions
  • Clear extension storage and re-authenticate
  • Verify domain matches (teakvault.com)
  • Check network connection
  • Verify Convex backend is accessible
  • Check browser console for errors
  • Ensure content is not from restricted URL (chrome://, file://, etc.)
  • Refresh the page
  • Check if platform is supported
  • Verify content script is injecting
  • Check browser console for errors

Browser Compatibility

  • Chrome 88+
  • Manifest V3
  • Full feature support

Learn More

Backend

Learn about the Convex backend integration

API Reference

Explore the Teak API

Wxt

Official Wxt framework documentation

Chrome Extensions

Chrome extension development guide

Build docs developers (and LLMs) love