# How Markdown Here Stores and Synchronizes User Options Across Browser Instances

> Discover how Markdown Here syncs user options across browsers using chrome.storage.sync. Learn about its custom OptionsStore and intelligent chunking for seamless customization.

- Repository: [Adam Pritchard/markdown-here](https://github.com/adam-p/markdown-here)
- Tags: internals
- Published: 2026-03-05

---

**Markdown Here uses a custom `OptionsStore` abstraction that leverages the browser's `chrome.storage.sync` API to automatically synchronize user preferences across all signed-in browser instances, with intelligent chunking to handle large values like custom CSS.**

The open-source Markdown Here extension (available at `adam-p/markdown-here`) ensures that your custom settings, styles, and preferences remain consistent whether you're composing emails on your work laptop or home desktop. Understanding how user options are stored and synchronized across different browser instances reveals the sophisticated storage architecture that keeps your markdown experience seamless across devices.

## The OptionsStore Architecture

At the heart of Markdown Here's persistence layer lies the **`OptionsStore`** class, defined in [`src/common/options-store.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/options-store.js). This module provides a unified asynchronous API for reading and writing user preferences while abstracting away the complexities of browser storage mechanisms.

### Storage Backend and Sync API

The primary storage mechanism utilizes the **WebExtension storage API**, specifically `chrome.storage.sync`. When available, this API automatically replicates stored JSON objects to the user's signed-in Chrome or Firefox profile, ensuring that options propagate to every installed copy of the extension without manual intervention.

The store detects sync capability through feature detection, preferring the cloud-backed storage whenever the browser supports it.

### Fallback Mechanisms for Offline Support

When `chrome.storage.sync` is unavailable—such as in older Chromium versions or restricted enterprise environments—**`OptionsStore`** gracefully degrades to a **`localStorage`** shim. This fallback ensures the extension remains functional offline, though preferences will not synchronize across devices until the sync API becomes available again.

## Handling Large Values with Intelligent Chunking

Chrome's sync storage imposes strict per-item size limits (approximately 8,192 bytes per key). To accommodate large user-generated content like custom CSS stylesheets or HTML templates, **`OptionsStore`** implements an automatic chunking system.

### The Chunking Algorithm

When writing data via `OptionsStore.set()`, the store checks string lengths against the internal `_maxlen()` threshold. Values exceeding this limit are split into multiple fragments using the delimiter defined by `_div` (set to `'##'`).

As implemented in [`src/common/options-store.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/options-store.js) (lines 93-100):

```javascript
if (typeof(val) !== 'string' || val.length < that._maxlen()) {
  finalobj[key] = val;
} else {
  const pieces = Math.ceil(val.length / that._maxlen());
  for (let i = 0; i < pieces; i++) {
    finalobj[key + that._div + i] = val.substr(i * that._maxlen(), that._maxlen());
  }
}

```

This creates storage keys like `main-css##0`, `main-css##1`, etc., allowing the full CSS content to sync across devices despite individual size constraints.

### Reassembling Split Values

During retrieval via `OptionsStore.get()`, the store detects chunked keys by searching for the `_div` delimiter. It then reconstructs the original value by joining the fragments in order.

From [`src/common/options-store.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/options-store.js) (lines 55-70):

```javascript
for (const key in sync) {
  const val = sync[key];
  const divIndex = key.indexOf(that._div);
  if (divIndex < 0) {
    finalobj[key] = val;
  } else {
    const base = key.slice(0, divIndex);
    const part = key.slice(divIndex + that._div.length);
    tempobj[base] = tempobj[base] || [];
    tempobj[base][part] = val;
  }
}
// Join the pieces
for (const key in tempobj) {
  finalobj[key] = tempobj[key].join('');
}

```

This transparent chunking ensures users can store extensive custom styles without worrying about synchronization limits.

## Defaults and Migration Strategy

To maintain consistency across browser instances, **`OptionsStore`** implements robust default value handling and legacy migration logic.

### Populating Missing Keys

When options are first accessed, the `_fillDefaults()` method merges the retrieved object with a comprehensive `defaults` table. This ensures that new installations or newly synced instances receive sensible defaults for all settings, including loading default CSS files via `Utils.getLocalFile` from [`src/common/utils.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/utils.js).

### Legacy Value Migration

The storage layer also handles schema evolution. For example, the system automatically migrates legacy Google Chart LaTeX URLs to current endpoints, ensuring that synchronized preferences remain functional even as the extension evolves. This migration runs consistently across all instances, preventing configuration drift.

## Cross-Instance Synchronization Flow

The actual synchronization of user options across different browser instances relies on the browser's native cloud sync infrastructure, orchestrated through the extension's background script.

### Background Script Integration

The background script ([`src/chrome/backgroundscript.js`](https://github.com/adam-p/markdown-here/blob/main/src/chrome/backgroundscript.js)) serves as the central authority for option retrieval. It calls `OptionsStore.get()` whenever current preferences are needed—for example, before rendering markdown or displaying upgrade notifications.

```javascript
// Retrieve the whole options object (used by many parts of the extension)
// backgroundscript.js → line 68-71
OptionsStore.get(function(prefs) {
  // `prefs` now contains the merged defaults and any user-saved values,
  // identical on every synced browser instance.
  // Example: use the main CSS and syntax CSS together.
  const combinedCss = prefs['main-css'] + prefs['syntax-css'];
});

```

Because `chrome.storage.sync` replicates data to the user's profile, every installed copy of Markdown Here receives identical preference objects on startup.

### Content Script Communication

Content scripts access these synchronized options via message passing. When a content script requires the latest settings, it sends a `"get-options"` action through `runtime.onMessage`. The background script responds with the object returned by `OptionsStore.get()`, guaranteeing that tabs opened in new windows or on different devices see the synchronized settings immediately.

```javascript
// Save an option – e.g., user toggles the GFM line-breaks feature
const newOpts = { 'gfm-line-breaks-enabled': true };
OptionsStore.set(newOpts, function() {
  // Callback runs after the data has been written (and split if needed)
  console.log('Option saved and synced!');
});

```

## Summary

- **Markdown Here** uses the **`OptionsStore`** abstraction in [`src/common/options-store.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/options-store.js) to manage user preferences across browser instances.
- The system prioritizes **`chrome.storage.sync`** for automatic cloud synchronization, falling back to **`localStorage`** when the sync API is unavailable.
- **Intelligent chunking** splits large values (like custom CSS) across multiple storage keys to bypass Chrome's per-item size limits, then reassembles them transparently on retrieval.
- **Default value filling** and **legacy migration** ensure consistent behavior across all synchronized instances, even as the extension updates.
- The **background script** serves as the central authority, responding to content script requests with the latest synchronized options via message passing.

## Frequently Asked Questions

### How does Markdown Here handle large custom CSS files that exceed browser storage limits?

Markdown Here automatically splits large strings into chunks using the `OptionsStore` chunking system. When a value exceeds the maximum length returned by `_maxlen()`, the `set()` method divides the string into pieces named with the pattern `key##0`, `key##1`, etc., using the `##` delimiter. Upon retrieval, `get()` detects these chunked keys and reassembles the full value by joining the fragments in order. This allows even extensive custom stylesheets to synchronize across devices without hitting Chrome's per-item quota.

### What happens to my Markdown Here settings when I switch between Chrome and Firefox?

Since `OptionsStore` relies on the standard WebExtension `storage.sync` API, your settings synchronize through the browser's respective cloud service (Chrome Sync or Firefox Sync) as long as you are signed into the same account. The extension stores preferences as JSON objects that are platform-agnostic. However, if you use browsers that do not share the same sync infrastructure (e.g., Chrome vs. Safari), the settings will not transfer automatically, though the extension will continue to function using local defaults or `localStorage` fallback.

### How does the extension ensure new browser instances receive the correct default settings?

When `OptionsStore.get()` is called, it triggers `_fillDefaults()`, which merges the retrieved storage object with a comprehensive `defaults` table. This table includes built-in values for all settings, and for CSS-related options, it loads default files via `Utils.getLocalFile` from [`src/common/utils.js`](https://github.com/adam-p/markdown-here/blob/main/src/common/utils.js). If a key is missing from storage (common on new installations), the default value is populated. This ensures that every synchronized instance, whether fresh or existing, maintains consistent baseline behavior even before the user customizes any settings.

### Can Markdown Here work offline without losing my preferences?

Yes. While the extension prefers `chrome.storage.sync` for cross-device synchronization, it includes a robust fallback mechanism for offline scenarios. If the sync API is unavailable or the device is offline, `OptionsStore` degrades to using a `localStorage` shim. This allows the extension to read and write preferences locally without interruption. When the browser reconnects and the sync API becomes available again, the storage layer will resume synchronizing changes to the cloud, ensuring your offline modifications propagate to other instances once connectivity is restored.