# Obsidian Surfing puts a browser inside your vault, with an agent bridge

> Obsidian Surfing is an Obsidian plugin that opens web pages inside the app instead of your system browser, by hijacking Obsidian's own file, http and https protocol handlers. Its 1.0 release added two large features, Chrome extensions running inside Obsidian tabs, and an MCP server so that coding agents can see and control the pages you have open.

**PKM-er/Obsidian-Surfing** — An Obsidian plugin that lets you browse the web within Obsidian.

- Repository: https://github.com/PKM-er/Obsidian-Surfing
- Stars: 579 · Forks: 45
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/pkm-er-obsidian-surfing

## The trick is hijacking Obsidian's own protocol handlers

Obsidian Surfing is an Obsidian plugin that allows you to browse the web within Obsidian using v1.0 tabs, and the core feature is described with unusual bluntness.

The plugin hijacks Obsidian's file, http and https protocols, enabling links to be opened directly in Obsidian rather than in external browsers. Yes, local HTML and other resources are also supported.

That is the whole mechanism, and it is a good one. Obsidian already owns the http and https protocol handlers on the desktop because it registers itself as the handler for obsidian:// links and for opening files, and a plugin that claims those handlers can take a URL that would otherwise have gone to Chrome and render it in a tab instead. The user does not have to think about it, and a link in a note just opens where you are.

The rendering is not the plugin's own work, and the documentation says so. The core functionality of the plugin, rendering a web view, is greatly influenced by Ellpeck's Obsidian Custom Frames plugin, and this plugin would not have been possible without it.

So this is a plugin built on top of another plugin, which is an important thing to know before you install it, because it means you are depending on both.

The feature list separates the core features from the auxiliary ones, and the auxiliary list starts with the honest one: simple dark mode, just simple. That is the plugin author telling you not to expect a polished browser.

The other auxiliary items are a default search engine setting, a default copy highlighted template setting that currently only supports very simple templates with a warning to avoid special characters, support for browsing history with the ability to jump back and forth, a command to clear that history, an option to open all links in the same panel on the right, a command to toggle that, and opening the current URL with the external browser from the right-click menu.

The project is TypeScript, MIT licensed, with the last push on 2026-10-14 and releases 1.0.0 from the same day, 0.9.14 from 2024-11-09 and 0.9.13 from 2024-11-09. The authors are listed as Boninall and Windily-cloud.

## The everyday features are about getting text back into your notes

Most of the core features are about taking something from the web and putting it into your vault, which is the point of having a browser inside a note-taking app.

The editor web search is the simplest. You can select keywords in the editor and then right-click to open them in the web browser and search using the default search engine.

The in-page web search does the same from the other side. Again, you can right-click within a web page to use the default search engine search.

Copying links pointing to highlights is the one that changes how you read. As with the browser, you can select text and copy the links pointing to it. So a highlighted passage becomes a link whose target is the exact position of that text in the page, which is a much better thing to paste into a note than a bare URL.

The bookmarklet route is the entry point for the whole workflow. The plugin registers an Obsidian uri protocol that allows you to open a web browser in Obsidian using the URL `obsidian://web-open?url=<url>`. A matching bookmarklet can then click a bookmark in the browser to open the current browser URL within Obsidian. The setup is: open the Open URL In Obsidian Web option in the plugin settings, then under that option there is a link of bookmarklets which you drag into your browser's bookmark bar, or you can click the link to copy the bookmarklets code and create the bookmarklet yourself.

There is also a Quickadd integration, documented as creating a macro based on a search-in-surfing script, then opening ChatGPT in Surfing, selecting some text and running the macro. That is the pattern for the plugin's intended use: you are reading in Obsidian, you select something, and you send it somewhere.

One feature is explicitly marked experimental and carries its own bug warning: copy video timestamp, where right-clicking on the text pops up a menu to copy the timestamp, currently only bilibili is supported, and there are some bugs where it is known that sometimes the menu does not pop up.

It is worth noting that the plugin also keeps a bookmark.json file at the repository root, which is presumably where the bookmarklet configuration lives.

## Chrome extensions, and which surface they run in depends on a setting

The 1.0 release added the ability to run Chrome extensions such as Tampermonkey, uBlock Origin Lite, Dark Reader or Read Frog inside Obsidian, and the way this works has one subtlety worth understanding.

This used to be the separate Chrome Extensions Bridge plugin, so the capability moved into Surfing itself.

The rule is about where extensions run. While Obsidian's core Web Viewer plugin is enabled, extensions run in Web Viewer tabs, and Surfing leaves external links to Web Viewer, with Surfing's own view still available from its commands and menus. With Web Viewer disabled, extensions run in Surfing tabs. Switching the core plugin moves the extensions over automatically.

So the extension support works in both surfaces, but which surface you land in depends on whether Obsidian's own Web Viewer is on. That is a slightly odd design, and the practical effect is that if you want extensions in Surfing's own tabs you have to turn off the core Web Viewer plugin.

Each browser has its own session, so extension data, meaning settings and cookies, is kept separately for each.

Installation of an extension has three routes. From the Chrome Web Store, by opening the store with the command Open Chrome Web Store and then clicking Install to Obsidian in the tab header, from a store link or extension ID, or from a local .crx, .zip or unpacked folder. Everything is managed in Settings then Surfing then Extensions.

In use, every browser tab header has a puzzle button listing your extensions, and pinned extensions get their own button with an icon and a badge. Extension context menu items appear in the page's right-click menu, and manifest commands appear in the command palette, where you can bind hotkeys.

There is a hard requirement. Obsidian installed from a 1.12.4 or later installer with Electron 39 or later, and the explanation is that Electron is bound to the installer, so on older installs Surfing works as before, without extensions.

The migration note for people coming from the old plugin is useful. Disable Chrome Extensions Bridge, then use Import in Settings then Surfing then Extensions. Extensions installed from local folders get a new extension ID, so their data starts fresh.

## The MCP server lets an agent drive the pages you have open

The second 1.0 feature is the one that changes what the plugin is for, and it is an agent bridge rather than a browsing feature.

Surfing can run a local MCP server so external AI agents, naming Claude Code, Codex CLI and Cursor, can see and control the web pages open in Obsidian, in Surfing tabs and in the core Web Viewer, much like Claude in Chrome does for Chrome.

The setup is a settings toggle at Settings then Surfing then MCP then Enable MCP server. Surfing generates an access token and listens on http://127.0.0.1:27125/mcp, with the port configurable.

Connecting Claude Code is one command:

```
claude mcp add --transport http surfing http://127.0.0.1:27125/mcp --header "Authorization: Bearer <token>"
```

And connecting Codex CLI is a config file entry:

```toml
[mcp_servers.surfing]
url = "http://127.0.0.1:27125/mcp"
http_headers = { Authorization = "Bearer <token>" }
```

The settings page shows these snippets, and one for other clients, with copy buttons, and Surfing never writes other apps' config files.

The tool list is long and describes the scope. There are tabs_list, tabs_create, tabs_activate and tabs_close, a navigation call, read_page which returns elements with refs, find, get_page_text, click, type, key, scroll, form_input, screenshot, wait_for, console_messages, and javascript which is off by default. Clicks, typing and keys are real input events.

That last phrase is the important one. The agent is not making synthetic DOM calls, it is dispatching the same events a user would, so a page that requires a real gesture sees one.

The security model is spelled out in detail, which is right for something that exposes input control to an external process. The server listens on 127.0.0.1 only, every request needs the bearer token, and requests that carry an Origin header, meaning they come from web pages, or a foreign Host are rejected. The first time an agent reads or acts on a site, Obsidian asks you, with Allow this time, Always allow and Deny options, unless you chose Allow all sites. Blocked sites, given as example.com or *.example.com, are always refused. Running JavaScript in pages is off unless you turn it on. And a tab an agent is controlling shows an Agent badge, and its menu revokes that site for the session.

So there are three layers of control: a token, a per-site prompt, and a per-session revocation, plus the origin and host checks underneath.

## The build, the tests and what is still on the list

The package file describes a build with Vite and a test setup that is more than a single suite.

The scripts show the shape. The build is `vite build`, the dev script is `vite build --watch --mode=development`, and there are separate scripts for the extension and MCP test suites. `test:extensions` builds the extensions with a script and runs them, and `test:mcp` builds the MCP server and runs its own runner. The `test` script runs both in sequence.

The lint and review setup is unusual and worth noting. The lint script calls review, which runs eslint from a tools/review/node_modules directory with its own config, and there is a review:install script that installs into that prefix. So the linting toolchain is deliberately isolated in its own directory with its own dependencies rather than sharing the project's node_modules, which avoids version conflicts between the plugin's own toolchain and the linter's.

The typecheck script runs tsc twice, once for the main tsconfig and once for a separate tsconfig in src/extensions, which implies the extension code is a separately typed package.

The dependency list tells you about the runtime. @electron/remote is there for reaching into Obsidian's Electron process, which is how a plugin gets at things the API does not expose. monkey-around is a mocking library, which is a hint that the tests mock the Obsidian environment. The obsidian package is the API typings. TypeScript at 7.0.2, Electron at 43.7.11 and Vite at 5.0.12 are the rest of the toolchain. There is also a CodeMirror set of packages, which suggests the plugin has editor integration of its own.

The single runtime dependency is defuddle at 0.6.6, which is a content extraction library, so the plugin can pull the readable article out of a page rather than leaving you with the rendered page.

There is a version-bump script, a versions.json, a manifest.json and a main.css at the root alongside the styles.css in the styles directory. The husky directory and the prepare script set up git hooks.

The TODO list at the top of the documentation is short and honest. Support for extensions is checked off in the release, and support for custom CSS and custom JS are the remaining items.

## Conclusion

Adopt Obsidian Surfing if you want to read and clip web pages inside Obsidian rather than switching to a browser, since the protocol hijacking means links in your notes open in place. Skip it if you need to see a page in your real browser profile, since the extension support runs in its own session with separate cookies and settings, and if you are on an older Obsidian installer, since extensions require a recent Electron build. Verify first which of the two browsing surfaces you use, because extensions run in Obsidian's Web Viewer tabs when that core plugin is enabled and in Surfing tabs when it is not, that you understand the agent bridge is a local server with real input events, and that the video timestamp feature is experimental and currently limited to one video site.

## FAQ

### How does Obsidian Surfing open web pages inside the app?

The plugin hijacks Obsidian's file, http and https protocols, so links open directly in Obsidian rather than in an external browser, and local HTML and other resources are supported too. The rendering itself is built on top of Ellpeck's Obsidian Custom Frames plugin, which the documentation credits as the foundation that made the plugin possible.

### How do I run Chrome extensions inside Obsidian Surfing?

Install from the Chrome Web Store using the Open Chrome Web Store command and the Install to Obsidian button, from a store link or extension ID, or from a local .crx, .zip or unpacked folder, then manage them in Settings then Surfing then Extensions. Extensions run in Obsidian's Web Viewer tabs when that core plugin is enabled and in Surfing tabs when it is disabled, and each browser keeps its own session with separate settings and cookies.

### Can an AI agent control Obsidian Surfing?

Yes. Enable it in Settings then Surfing then MCP then Enable MCP server, and Surfing generates an access token and listens on http://127.0.0.1:27125/mcp. The tools include tabs_list, tabs_create, read_page, find, click, type, key, scroll, screenshot and others, with clicks, typing and keys dispatched as real input events. The server is bound to 127.0.0.1, every request needs the bearer token, and each site must be allowed before an agent can act on it.

## Sources

- [Issues](https://github.com/PKM-er/Obsidian-Surfing/issues)
- [License: MIT](https://github.com/PKM-er/Obsidian-Surfing/blob/main/LICENSE)
- [PKM-er/Obsidian-Surfing on GitHub](https://github.com/PKM-er/Obsidian-Surfing)
- [README](https://github.com/PKM-er/Obsidian-Surfing/blob/main/README.md)
- [Releases](https://github.com/PKM-er/Obsidian-Surfing/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pkm-er-obsidian-surfing
