Local REST API with MCP

by Adam Coddington
5
4
3
2
1
Score: 76/100

Description

Category: Coding & Technical Tools

The Local REST API plugin exposes a secure local API for working with vault files from scripts, browser extensions and AI clients. It supports full file CRUD, reading or updating the active note, tag queries, command listing and execution, simple text search, and structured search with JsonLogic. A standout part is note patching: you can target headings, block references, or frontmatter keys and append, replace, move, or delete just that section instead of rewriting the whole file. The plugin also ships with an MCP endpoint that mirrors the core API, so external clients can talk to the vault through the same authenticated surface. It includes extension hooks for other plugins to register extra routes and MCP tools.

Reviews

  • Kakson Lidiya
    Reviewed on May 24th, 2026
    No review text provided.
  • B Caesar
    Reviewed on Mar 22nd, 2026
    No review text provided.

Stats

2832
stars
682,464
downloads
335
forks
144
days
76
days
78
days
62
total PRs
4
open PRs
22
closed PRs
36
merged PRs
159
total issues
4
open issues
155
closed issues
212
commits

Latest Version

3 months ago

Changelog

  • Updates the plugin's settings tab to use Obsidian 1.13's declarative settings framework. Note that this raises the minimum supported Obsidian version to 1.13.1.
    • Moves the "How to access via REST" and "How to access via MCP" instructions onto dedicated sub-pages, keeping the main settings page focused on server status and your API key.
    • Adds copy buttons to the API key, authorization header, and connection URL displays.
  • Adds a typed, documented extension API for plugin authors, generated from a single source-of-truth module (publicApi.d.ts/publicApi.js); type-only dependencies are now declared as peers, and bundled dependencies are no longer shipped to extension consumers.
  • Fixes an issue in which a read immediately following a write could observe stale content, by routing vault writes through Obsidian's Vault API.
  • Updates @modelcontextprotocol/sdk to 1.30.0 and esbuild to 0.28.1, and patches several in-range dependency advisories.
  • Removes several unused dependencies (obsidian-dataview, uuid, minimatch, matcher).

README file from

Github

Local REST API with MCP

Give your scripts, browser extensions, and AI agents a direct line into your Obsidian vault via a secure, authenticated REST API.

What you can do

Access your vault through the REST API or the built-in MCP server — both interfaces expose the same core capabilities, so scripts, browser extensions, and AI agents all speak the same language.

  • Read, create, update, or delete notes — full CRUD on any file in your vault, including binary files
  • Surgically patch specific sections — target a heading, block reference, or frontmatter key and append, prepend, replace, delete, or move just that section without touching the rest of the file
  • Search your vault — simple full-text search or structured JsonLogic queries against note metadata (frontmatter, tags, path, content)
  • Follow vault events — subscribe to Obsidian events (a note created, its frontmatter changed, a file opened) as a filtered Server-Sent Events stream
  • Access the active file — read or write whatever note is currently open in Obsidian
  • List and execute commands — trigger any Obsidian command as if you'd used the command palette
  • Query tags — list all tags across your vault with usage counts
  • Open files in Obsidian — tell Obsidian to open a specific note in its UI
  • Extend the API — other plugins can register their own routes via the API extension interface

All requests are served over HTTPS with a locally generated certificate and gated behind API key authentication.

Quick start

After installing and enabling the plugin, open Settings → Local REST API to find your API key and certificate.

REST API

# Check the server is running (no auth required)
curl -k https://127.0.0.1:27124/

# List files at the root of your vault
curl -k -H "Authorization: Bearer <your-api-key>" \
  https://127.0.0.1:27124/vault/

# Read a note
curl -k -H "Authorization: Bearer <your-api-key>" \
  https://127.0.0.1:27124/vault/path/to/note.md

# Read a specific heading (URL-embedded target)
curl -k -H "Authorization: Bearer <your-api-key>" \
  https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section

# Append a line to a specific heading (PATCH with a JSON instruction)
curl -k -X PATCH \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  --data '{"targetType":"heading","target":["My Section"],"operation":"append","content":"New line of content"}' \
  https://127.0.0.1:27124/vault/path/to/note.md

To avoid certificate warnings, you can download the plugin's certificate authority from https://127.0.0.1:27124/obsidian-local-rest-api.crt and trust it in your OS or browser, or point your HTTP client at it directly (for example curl --cacert obsidian-local-rest-api.crt ...). The plugin generates its own certificate authority on first run and serves a server certificate signed by it, so the download is a CA certificate rather than the server certificate itself. That CA is name-constrained: it can only vouch for 127.0.0.1, localhost, your configured binding host, and the hostnames you list under Subject alternative names, so trusting it does not let it (or anyone who obtains its key) impersonate other sites.

MCP clients

The MCP server runs at https://127.0.0.1:27124/mcp/ and requires that you provide your bearer token for authentication via an Authorization header (i.e. Authorization: Bearer <your-api-key>). Because the plugin uses a locally generated certificate authority, you may need to either trust that certificate in your OS/client, or use the plain HTTP endpoint at http://127.0.0.1:27123/mcp/ (enable it under Settings → Local REST API → Enable HTTP server).

Claude Code

Claude Code has native HTTP MCP support. The quickest way to add the server is via the CLI:

claude mcp add --transport http obsidian https://127.0.0.1:27124/mcp/ \
  --header "Authorization: Bearer <your-api-key>"

Or add it manually to .mcp.json in your project root (project-scoped) or configure it user-wide via claude mcp add --scope user:

{
  "mcpServers": {
    "obsidian": {
      "type": "http",
      "url": "https://127.0.0.1:27124/mcp/",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}
Claude Desktop

Claude Desktop does not natively support remote HTTP MCP servers, but you can bridge it with mcp-remote (requires Node.js). Add the following to claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "obsidian": {
      "command": "npx",
      "args": [
        "mcp-remote@latest",
        "https://127.0.0.1:27124/mcp/",
        "--header",
        "Authorization: Bearer <your-api-key>"
      ]
    }
  }
}

Restart Claude Desktop after saving the file.

Cursor

Cursor supports the Streamable HTTP MCP transport. Add the following to ~/.cursor/mcp.json (global) or .cursor/mcp.json (project-specific):

{
  "mcpServers": {
    "obsidian": {
      "url": "https://127.0.0.1:27124/mcp/",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}
Other clients

Any MCP client that supports the Streamable HTTP transport can connect to https://127.0.0.1:27124/mcp/ with an Authorization: Bearer <your-api-key> header. Consult your client's documentation for the exact configuration format.

API overview

Endpoint Methods Description
/vault/{path} GET PUT PATCH POST DELETE Read, write, or delete any file in your vault
/active/ GET PUT PATCH POST DELETE Operate on the currently open file
/search/simple/ POST Full-text search across all notes
/search/ POST Structured search via JsonLogic
/commands/ GET List available Obsidian commands
/commands/{commandId}/ POST Execute a command
/tags/ GET List all tags with usage counts
/open/{path} POST Open a file in the Obsidian UI
/ GET Server status and authentication check
/mcp/ GET POST MCP (Model Context Protocol) server — connect AI agents directly to your vault

For full request/response details, see the interactive docs.

The configuration directory is off-limits

The API refuses to read or write any file inside Obsidian's configuration directory (app.vault.configDir, normally .obsidian) — for both REST and MCP, reads as well as writes. A request for such a path is rejected with 403 (error code 40321) at the REST layer or a path error from an MCP tool.

That directory holds plugin code and each plugin's data.json — including this plugin's own, where your API key is stored. Writing into it is effectively remote code execution, since Obsidian runs an enabled plugin's main.js; reading from it leaks those secrets. Blocking it keeps a credential documented as "vault file access" from silently granting more (see GHSA-66m9-r757-qvq7).

The check is against where a path lands on disk, not just how it is spelled: a Windows 8.3 short name such as OBSIDI~1, a differently cased spelling on a case-insensitive filesystem, or a symlink that points into the configuration directory is refused the same way. Search and the tag listing skip any indexed note that lives there too, so a symlinked folder cannot hand the contents out by another route.

If you deliberately manage your Obsidian configuration through the API, turn on Settings → Local REST API → Advanced settings → Allow access to the configuration directory. It is off by default, and turning it on grants every holder of your API key that access.

Failed authentication is throttled

A source that presents a wrong credential -- an incorrect API key, or an invalid, expired or spent signed URL -- ten times within a minute has its further wrong credentials refused with 429 (error code 42901) and a Retry-After header until the minute is up. This applies to every route the plugin authenticates, including /mcp/ and GET / (routes an extension plugin publishes without authentication are not behind it): the root route answers 200 with authenticated: false to a wrong key, so it would otherwise be the cheapest place to guess from, and a wrong key sent there counts like a wrong key anywhere else.

Nothing else is slowed down. A request carrying the correct key or a valid signed URL is never counted or delayed, so MCP clients and scripts that make many requests in quick bursts are unaffected, and so is a client with the right key while another process on the same machine is being refused. A request that presents no credential at all is not counted either -- it has made no guess -- and is answered 401, or served on the routes that need no authentication, however many of them arrive. The counter is kept in memory per source address and is cleared by a plugin reload.

Conditional writes

Two clients that read the same note and then each write their edited copy back would otherwise lose the first write without a word. Every write to a file can carry a precondition instead, so it fails rather than overwriting a change it never saw:

# Read the note; the ETag response header is its current version
curl -i -H "Authorization: Bearer $KEY" https://127.0.0.1:27124/vault/notes/todo.md
# => ETag: "a1b2c3"

# Write it back only if nobody has changed it since
curl -X PUT -H "Authorization: Bearer $KEY" -H 'If-Match: "a1b2c3"' \
     -H "Content-Type: text/markdown" --data-binary @todo.md \
     https://127.0.0.1:27124/vault/notes/todo.md
# => 204, with the new version in ETag -- or 412 if the note changed, and nothing written

# Create a note only if it does not exist yet
curl -X PUT -H "Authorization: Bearer $KEY" -H "If-None-Match: *" \
     -H "Content-Type: text/markdown" --data-binary @new.md \
     https://127.0.0.1:27124/vault/notes/new.md

If-Match works on every write: PUT, POST, PATCH and DELETE on /vault/{path} and /active/, MOVE and COPY on /vault/{path}, and writes aimed at a heading, block or frontmatter field. It takes the ETag from a GET, the version from a document map or note JSON — all three are the same token — or the ETag a previous write answered with. A list matches if any entry does, and * matches any file that exists. A failed precondition is 412 (error code 41200), and the message names the file's current version. Writes made through the API are queued per file, so two clients holding the same version cannot both pass the check.

The ETag sent with a note's HTML rendering or its note JSON is not this token, because those also depend on other files; read note JSON's version field instead. MCP's write tools take the same precondition as an ifMatch argument (and vault_write/vault_append an ifNoneMatch: "*"), vault_read returns the version, and the write tools return the new one.

Browser clients and response headers

Several endpoints answer in a response header rather than in the body: ETag carries a file's version for conditional writes, Content-Location tells you which file a targeted or /active/ request actually resolved to, Markdown-Patch-Warnings reports what a PATCH had to work around, Deprecation warns that a format is sunsetting, and Mcp-Session-Id carries the session for a sessionful MCP connection.

Browsers hide response headers from JavaScript unless the server opts them in, so the API sends Access-Control-Expose-Headers: * and all of them are readable with response.headers.get(...). Safari honours the wildcard from 15.4 onward; older browsers see only the CORS-safelisted headers. Requests made with credentials: "include" are not supported — the API authenticates with a bearer token and sends Access-Control-Allow-Origin: *, which browsers reject for credentialed requests.

Patching notes

The PATCH method is one of the most useful features of this API. It lets you make targeted edits without rewriting entire files.

Send a JSON instruction: an operation (replace, prepend, append, or delete) applied to a scope (content, marker, markerAndContent, or parent) of a target — a heading (addressed as an array of heading texts from the top level down), a block reference, or a frontmatter key. The payload rides in content (a string), value (JSON, for frontmatter values), or destination (a heading move):

# Replace the value of a frontmatter field
curl -k -X PATCH \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  --data '{"targetType":"frontmatter","target":"status","operation":"replace","value":"done"}' \
  https://127.0.0.1:27124/vault/path/to/note.md

Heading levels inside a content string are relative to the target (a leading # becomes a direct child). Advisory warnings (e.g. a heading rebased past level 6) come back as percent-encoded JSON in the Markdown-Patch-Warnings response header — decode with decodeURIComponent before parsing. Pass ifMatch (the version from a document map) for optimistic concurrency, or send an If-Match header as with any other conditional write.

Note: Whitespace is library-owned — your content is reduced to trimmed, canonical form (leading and trailing blank lines are meaningless), and the API itself supplies the blank line wherever inserted content faces body text, so an append or prepend always lands as its own block and never merges into an existing paragraph. Heading lines, existing blank lines, and each document's spacing style are preserved as-is. See the interactive docs for worked examples.

To continue an existing block instead of starting a new one — say, extending a list — add within to a heading instruction: an index selecting one of the section's top-level body blocks (0-based in document order, negative counting from the end, so -1 is the last block). A within edit splices literally, so you own the joint:

# Add an item to the last list under "Log" (the leading \n continues the block)
curl -k -X PATCH \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  --data '{"targetType":"heading","target":["Log"],"within":-1,"operation":"append","content":"\n- new item"}' \
  https://127.0.0.1:27124/vault/path/to/note.md

With markerAndContent scope, prepend/append instead insert a new block beside the indexed one. Indices are positional, so read the document map first and pair the edit with ifMatch.

Raw-content mode

If your client templates markdown into the request body (Shortcuts, Tasker, curl from a template), JSON-escaping that content into an instruction is fragile. Raw-content mode moves the instruction's fields out of the body — target in the URL (or in Target-Type/Target headers with an explicit Markdown-Patch-Version: 2), operation and options in headers — and the body is the raw payload, no JSON escaping required:

# Append a templated line under a heading — no JSON escaping anywhere
curl -k -X PATCH \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Operation: append" \
  -H "Content-Type: text/markdown" \
  --data "- $TEMPLATED_CONTENT" \
  https://127.0.0.1:27124/vault/notes/daily.md/heading/Log

A text/* body is the content carrier, an application/json body the value carrier, and no body at all carries nothing (a delete, or a move via a Destination header). Target-Scope, Within (the instruction's within index as a plain integer, e.g. -1), Create-Target-If-Missing, Reject-If-Content-Preexists, and If-Match headers round out the instruction. See the interactive docs for the header encodings and the full details.

Already using the older header-driven PATCH format? It spread the instruction across request headers instead of a JSON body, and is deprecated and will be removed in 6.0. It still works — send Markdown-Patch-Version: 1 to opt back into it (the same header also selects the legacy ::-joined document map on GET), and responses served by it carry a Deprecation: true; sunset-version="6.0" header. To upgrade, drop that header and move each header into the JSON body; the interactive docs have the field-by-field mapping table. If your content contains headings, adjust their levels too: 1.x wrote them literally, and 2.x treats them as relative to the target (see the migration guide there).

See the interactive docs for the full instruction schema and options.

Targeting specific sections

You can read or write a specific part of a note — a heading, block reference, or frontmatter field — without fetching or replacing the whole file. This works on GET, PUT, POST, and PATCH requests (for PATCH this is raw-content mode — add an Operation header).

Append /<target-type>/<target> after the filename. Each nested heading level is its own path segment, so a heading whose text contains :: needs no escaping:

# Read the content under a specific heading
curl -k -H "Authorization: Bearer <your-api-key>" \
  https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section

# Read a nested heading (one path segment per level)
curl -k -H "Authorization: Bearer <your-api-key>" \
  https://127.0.0.1:27124/vault/path/to/note.md/heading/Work/Meetings

# Read a frontmatter field
curl -k -H "Authorization: Bearer <your-api-key>" \
  https://127.0.0.1:27124/vault/path/to/note.md/frontmatter/status

# Replace the content of a heading via PUT (heading levels are normalized for you)
curl -k -X PUT \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: text/markdown" \
  --data "Updated content" \
  https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section

# Append to a heading via POST
curl -k -X POST \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: text/markdown" \
  --data "Appended content" \
  https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section

Supported target types: heading, block, frontmatter.

A targeted URL is ambiguous on its face — /vault/notes/log.md/heading/Today could name the Today section of notes/log.md or a file literally called notes/log.md/heading/Today. The server walks backwards down the path until it finds a real file and reports which one it settled on in a Content-Location response header, with each path component percent-encoded on its own (non-ASCII characters, and reserved characters like #, ? and ,) so it can be pasted straight back into a request URL. A request whose URL names the file outright gets no such header.

On a GET, a Target-Scope header selects which part of the target comes back, mirroring the PATCH scopes: content (the default), marker (the label — a heading's raw text, a block's bare id, a frontmatter key), or markerAndContent (the whole node, in exactly the shape a PATCH replace at that scope consumes — a heading subtree reads back with its own line as # Title, levels relative to its parent):

# Read a whole section — heading line included — ready to edit and write back
curl -k -H "Authorization: Bearer <your-api-key>" \
  -H "Target-Scope: markerAndContent" \
  https://127.0.0.1:27124/vault/path/to/note.md/heading/My%20Section

Deprecated: header-based targeting. Earlier releases targeted a section with Target-Type, Target, and Target-Delimiter headers (plus Target-Scope/Trim-Target-Whitespace). That form is deprecated and will be removed in 6.0; it is only processed when you also send Markdown-Patch-Version: 1 (responses then carry a Deprecation header). Without it, supplying those targeting headers is rejected with 400. Supplying both URL-path targeting and the header form on one request returns 422 Unprocessable Entity.

Searching

POST /search/simple/?query=your+terms runs Obsidian's built-in fuzzy search and returns matching filenames with scored context snippets. Obsidian matches each word of the query separately, so a query containing a common word can match thousands of times; &maxResults= caps how many files come back (keeping the highest-scoring ones) and &maxMatchesPerFile= caps the matches within each. Each result's totalMatches and the Total-Results response header say how much was left out.

POST /search/ accepts a JsonLogic expression (content type application/vnd.olrapi.jsonlogic+json) and evaluates it against each note's metadata (frontmatter, tags, path, content).

Event streams

You can follow what happens in the vault as a Server-Sent Events stream. There are two steps. First, register a subscription to one Obsidian event, with an optional JsonLogic filter. Then open the URL that comes back:

# 1. Subscribe to notes under journal/ being modified
curl -X POST -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/vnd.olrapi.jsonlogic+json" \
  -d '{"glob": ["journal/*", {"var": "path"}]}' \
  https://127.0.0.1:27124/events/vault/modify/
# => {"id": "…", "url": "https://127.0.0.1:27124/events/vault/modify/…/?sig=…&exp=…&n=…", …}

# 2. Follow the stream; with signed URLs on, the URL needs no API key
curl -N "<url>"

It takes two steps because a browser's EventSource can only make GET requests, which have no body to carry a filter.

The events are Obsidian's own, and only these can be streamed:

Emitter Events
vault create, modify, delete, rename
metadataCache changed, deleted, resolve, resolved
workspace file-open, active-leaf-change, layout-change

Each event is serialized by code written for it. That code decides exactly what is sent: the path, the file's NoteJson (the same shape /search/ evaluates), and a few event-specific fields such as oldPath on a rename. Note content is sent only when the filter reads file.content. Events whose payloads are keystrokes, clipboard data, or UI objects (editor-change, quick-preview, editor-paste, the menu events, …) can't be streamed. To react to frontmatter changes, use metadataCache changed: vault modify fires before Obsidian has re-read the file's metadata.

Each message's id is <epoch>-<counter>. A new epoch, or a gap in the counter, means events were missed. Nothing is replayed. A stream URL expires after the signed-URL lifetime (or ?ttl=<seconds>), but a stream opened before then stays open. At most 16 streams can be open at once. Anyone holding a signed stream URL sees the paths and metadata of every event its filter matches, so treat it like the notes themselves. See the API docs for the full message format.

Server state

GET / answers without an API key, but an authenticated request gets more: certificateInfo, the list of installed API extensions, and a state section.

state holds observations, by namespace, that a client reads to decide whether to proceed. The server attaches no verdict. The plugin's own namespace is metadataCache:

{
  "status": "OK",
  "authenticated": true,
  "state": {
    "metadataCache": {
      "listeningSince": "2026-10-03T14:02:11.408Z",
      "lastResolvedAt": null,
      "lastActivityAt": "2026-10-03T14:02:13.951Z"
    }
  }
}

It exists because a note's links, backlinks, and unresolvedLinks come from Obsidian's vault-wide link graph, and while Obsidian is still indexing after startup they can be incomplete with no sign in the note itself. Obsidian gives no documented signal for the end of that indexing, so the plugin reports what it can observe: when it started listening (listeningSince, which is when it loaded), when it last heard the cache's resolved event (lastResolvedAt, fired each time Obsidian's resolver queue drains, or null if never since loading), and when it last heard any indexing activity at all (lastActivityAt: changed, resolve, or resolved). Indexing in progress looks like recent activity; done looks like silence. A plugin enabled into an already-indexed vault hears nothing, which is why listeningSince is there: "never resolved, but listening for a while now" is as good as done. A workable rule is to wait while lastResolvedAt is null and listeningSince is recent, or while lastActivityAt is recent, with "recent" set to whatever your vault's indexing takes. A client that would rather not poll can watch the metadataCache event stream instead, where resolve fires per file and resolved on each drain.

Every other key of state is an extension plugin's id, holding whatever that extension publishes (see Extension state), or null when it could not be read in time. The budget is Settings → Local REST API → Advanced settings → Extension state read budget, 100 ms by default; extensions are read concurrently, so it bounds the whole wait. MCP clients get the same document from the obsidian://local-rest-api/status resource.

MCP (Model Context Protocol)

[!NOTE] Several third-party MCP servers for Obsidian exist, but they are no longer necessary — this plugin ships a built-in MCP server that runs inside Obsidian and has direct access to your vault's live metadata, active file, and command palette. If you are currently using a third-party server, switching to this one is likely to give you better results.

The plugin includes a built-in MCP server at /mcp/ so AI agents and MCP-compatible clients can interact with your vault without hand-crafting HTTP requests.

Transport: Streamable HTTP — API key authentication required.

Protocol revisions

The endpoint serves the 2026-07-28 revision plus the sessionful revisions from 2024-10-07 through 2025-11-25, choosing per request, so clients on either can share it.

The 2026-07-28 revision is stateless: there is no initialize handshake and no session, so the plugin neither issues nor reads the Mcp-Session-Id header. Each request carries its own protocol version and client identity in params._meta, repeats them in the MCP-Protocol-Version, Mcp-Method, and Mcp-Name headers, and is answered on its own. Clients can call server/discover to learn the supported revisions and capabilities up front.

Clients that open with an initialize request are served the sessionful revision they negotiate: the handshake returns an Mcp-Session-Id, GET /mcp/ opens that session's notification stream, and DELETE /mcp/ ends it. Sessions exist only on this path, and they are what keeps the handshake's listChanged capabilities honest: when another plugin registers or removes an MCP tool, every live session is notified, while 2026-07-28 clients hear about it on a subscriptions/listen stream.

Connecting a client

Connect your MCP client to https://127.0.0.1:27124/mcp/. Authentication uses a bearer token — find your API key under Settings → Local REST API, then pass it as:

Authorization: Bearer <your-api-key>

The exact config syntax varies by client; see the Quick start examples above or consult your client's documentation for Streamable HTTP remote MCP servers.

[!WARNING] To connect to the MCP server securely, your client must trust the plugin's locally generated certificate authority. You can download and trust it from https://127.0.0.1:27124/obsidian-local-rest-api.crt, or configure your client to skip TLS verification for 127.0.0.1.

If trusting a locally generated certificate is not possible in your environment, you can connect insecurely using http://127.0.0.1:27123/mcp/ instead of https://127.0.0.1:27124/mcp/ if you have enabled the HTTP endpoint under Settings → Local REST API → Enable HTTP server.

Available tools

Tool Description
vault_list List files and subdirectories inside a vault directory
vault_read Read a text file's content, frontmatter, tags, and stat; refuses anything that is not valid UTF-8
vault_read_binary Read an attachment: images as an image block the model can see, anything else as a download link or embedded bytes
vault_get_download_url Mint a signed, expiring link to a file that works without the API key (only when signed URLs are enabled)
vault_get_upload_url Mint a signed, single-use link for uploading a file over PUT (only when signed URLs are enabled)
events_get_listener_url Subscribe to an Obsidian event and mint a signed link to its Server-Sent Events stream (only when signed URLs are enabled)
vault_write Create or overwrite a text file; refuses paths whose extension names a binary type
vault_append Append content to the end of a vault file
vault_patch Patch a specific heading, block reference, or frontmatter field
vault_delete Delete a vault file (moves to trash by default)
vault_move Move (rename) a vault file to a new path
vault_copy Copy a vault file to a new path
vault_get_document_map List the headings, block references, and frontmatter fields in a file
active_file_get_path Return the vault path of the file currently open in Obsidian
search_query Search using a JsonLogic query against note metadata
search_simple Full-text search using Obsidian's built-in search
tag_list List all tags across the vault with usage counts
command_list List all registered Obsidian commands
command_execute Execute an Obsidian command by ID
open_file Open a file in the Obsidian UI

search_simple returns at most the 20 highest-scoring files and the first 10 matches in each unless the call sets maxResults or maxMatchesPerFile. Obsidian's simple search matches each word of a query separately, so an unbounded search containing a common word can return megabytes, and an MCP result usually goes straight into the model's context. When anything was left out, a second text block beginning [Search results limited: says how much and how to see more. Its contextLength is at most 500 characters. POST /search/simple/ takes the same two parameters but has no limit by default, and does not bound contextLength.

Binary files and attachments

The REST API has always handled binary content: GET /vault/<path> returns raw bytes with a Content-Type derived from the file extension, and PUT /vault/<path> accepts a body of any content type and stores it byte-for-byte. Neither has a practical size limit.

MCP tools are a different story, because a tool's arguments and results pass through the model. vault_read and vault_write are text tools — they decode and encode UTF-8, which is lossy for anything that is not text — so vault_read refuses a file whose bytes are not valid UTF-8, and vault_write and vault_append refuse a path whose extension names an image (other than SVG, which is text), audio, video, font, PDF, or archive type. Reading an attachment as text and writing the result back is the mistake that destroys attachments, and both halves of it are now refused.

vault_read_binary is the tool for attachments, and what it returns depends on the file:

  • Raster images come back as an MCP image content block, downscaled to fit 1568px on the long side, plus a small text block with the file's path, MIME type, size, and dimensions. The model can actually look at the picture, and is billed for its pixels rather than its bytes: a multi-megabyte photo costs a couple of thousand tokens. An image that is still over 512 KiB once downscaled -- a large image that was already inside 1568px, so nothing was resized -- comes back as a download link instead, the same as any other oversized file, or is refused with a pointer at the REST endpoint when signed URLs are off and there is no link to give.
  • SVGs come back unchanged, as their source text in a resource block. A vector drawing is XML the model can read directly, so nothing is rasterized or resized.
  • Everything else comes back as a resource_link to a signed download URL when signed URLs are enabled (below), so the bytes never enter the conversation. When they are not enabled, a file under 512 KiB is embedded as a resource block with base64 bytes, and a larger one is refused with a pointer at the REST endpoint.

An as argument overrides the default: as: "bytes" embeds the raw bytes (under 512 KiB), as: "link" returns a signed link and never reads the file.

The 512 KiB ceiling is not only about token cost. A tool result carrying roughly a megabyte or more of base64 crashes Obsidian's Electron renderer outright, taking the plugin's HTTP server down with it, so the cap is set well below the point where that was observed.

There is no upload tool that carries bytes through the model — emitting base64 as output tokens is impractical beyond a few kilobytes. The agent's host has the file on disk; it uploads it with a PUT to the REST API, either with the API key or with a signed upload URL.

Signed URLs

Signed URLs let an agent hand a file to something that is not the MCP client — a browser tab, an <img> tag, a curl in a shell — without also handing over the API key. They are on by default; turn them off under Settings → Local REST API → Advanced settings → Enable signed URLs, and set their lifetime there (default 300 seconds).

While they are on, these MCP tools mint them:

  • vault_get_download_url returns a resource_link to GET /vault/<path>?sig=…&exp=…&n=…, plus a markdown link for clients that only render text. The link is valid until it expires and can be used repeatedly. Add &download=1 to have the browser save the file instead of showing it.
  • vault_get_upload_url returns a PUT /vault/<path>?sig=…&exp=…&n=… URL and a ready-to-run curl command, with the filename quoted for a POSIX shell so a name containing $, backticks or spaces cannot be expanded when the command is pasted. The link is consumed by the first request that succeeds — claimed at authorization rather than at completion, so concurrent redemptions cannot all pass, and released again if the request does not end in a 2xx. The Content-Type is informational on a signed upload: the bytes are stored exactly as sent, whatever type is declared, because a signed URL authorizes a whole-file write of that content. Without it, a .json destination was parsed and re-serialized, so a pretty-printed file lost its whitespace and trailing newline while still answering 204.
  • vault_read_binary uses download links for non-image files, and for anything when called with as: "link".
  • events_get_listener_url registers an event stream subscription and returns its GET /events/<emitter>/<event>/<id>/?sig=…&exp=…&n=… URL, plus a curl -N command. POST /events/<emitter>/<event>/ returns the same kind of URL.

A signed URL authorizes a whole-file write to the path it names, and only that: a request that also carries Target-Type/Target headers, or whose path continues into /heading, /block or /frontmatter, is refused rather than quietly becoming a targeted edit of a document the link never named. Use the API key for targeted writes.

How they work: the signature is an HMAC over the method, the normalized vault path, the expiry, and a random per-link nonce carried as n, under a secret generated fresh every time the plugin loads and kept only in memory. A link is therefore good for one file, one method, one window of time, and never survives an Obsidian restart. The nonce is what makes each mint its own link: exp has one-second granularity, so without it two links minted for the same file in the same second were byte-identical -- and since a spent upload link is remembered by its signature, re-minting straight after an upload handed back the link that had just been consumed. The host is not part of the signature, so the same link works whichever hostname on the certificate the client uses. Anyone holding a link can do what it names until it expires, so treat one as you would the file itself.

Two practical notes: whether a chat client renders a linked image inline is up to the client, and most do not today, but clicking through always works; and a link on the HTTPS port needs the plugin's certificate to be trusted by whatever opens it — the plain-HTTP port avoids that.

Available resources

URI Description
obsidian://local-rest-api/openapi.yaml Full OpenAPI specification for this REST API, including routes that extensions describe
obsidian://local-rest-api/status The authenticated GET / document: versions, installed extensions, and the state section; never cached, so it can be polled

API Extensions

Other plugins can register their own authenticated routes, public routes, MCP tools, streamable events, and state against this plugin's server. See Adding your own API Routes via an Extension for a walkthrough.

Public routes (addPublicRoute) are answered before the API key is checked, so they can't sit under a prefix the plugin serves its own routes from: /vault/, /active/, /search/, /commands/, /events/, /mcp/, /open/, and /tags/, in any letter case, along with /, the OpenAPI documents, and the certificate. A path whose first segment is a pattern (/:name/, /*) is refused for the same reason. addPublicRoute throws when you register one of these, so start public routes with a literal segment of your own, such as your plugin's id. Authenticated routes (addRoute) and vault sub-resources are unaffected.

Typed extension API

Install this package as a development dependency to get getAPI and the types for everything it returns:

npm install --save-dev obsidian-local-rest-api

This package declares obsidian, zod, and @types/express as peer dependencies, because its types refer to all three — addRoute returns express's IRoute, and addMcpTool takes zod schemas. npm installs peers for you; if you pin them yourself, keep them resolvable. Without them, TypeScript quietly widens those positions to any instead of reporting an error, so a project that suppresses the missing-types diagnostic gets no warning that it has lost type checking exactly where it matters most.

import { getAPI, type LocalRestApiPublicApi } from "obsidian-local-rest-api";

const api: LocalRestApiPublicApi | undefined = getAPI(this.app, this.manifest, 2);

The package entry point is a small standalone module — it resolves the running host plugin out of Obsidian's plugin registry rather than pulling the plugin bundle into your build. Passing an extension API version (2 above) makes getAPI throw ApiVersionUnsupportedError when the installed host is older than the surface you need; omit it to accept whatever is installed and feature-detect yourself. getAPI returns undefined when the plugin isn't installed or hasn't loaded yet.

publicApi.d.ts is generated from src/publicApi.ts, which the implementation is compile-time-checked against, so the published types cannot drift from what the plugin actually offers.

Registering again after the host reloads

Everything an extension registers belongs to the running instance of this plugin. When this plugin is disabled and re-enabled, updated, or reloaded during development, it starts with a new server that knows nothing about your routes, tools, or events, and the handle getAPI gave you earlier now points at the instance that was unloaded. Your routes answer 404 from then on, with no error to explain why.

Each time this plugin finishes loading, it triggers obsidian-local-rest-api:loaded on the workspace. Register in a method of your own, call it from onload, and call it again whenever that event fires:

async onload() {
  this.registerWithLocalRestApi();
  this.registerEvent(
    // Not in obsidian's typings; declare it on Workspace, or cast as here.
    (this.app.workspace as Events).on("obsidian-local-rest-api:loaded", () =>
      this.registerWithLocalRestApi()
    )
  );
}

registerWithLocalRestApi() {
  // A fresh handle every time: one kept from before a reload is stale.
  this.api = getAPI(this.app, this.manifest, 3);
  if (!this.api) return; // Not installed or not loaded yet; the event will call again.
  this.api.addRoute("/my-plugin/status").get((req, res) => { /* ... */ });
}

Calling it from onload covers the case where this plugin loaded before yours, and the event covers every load after that, including this plugin loading after yours at startup. Avoid guarding the method with if (this.api) return: that keeps the stale handle and skips registering on exactly the occasions it's needed. The sample extension shows the complete pattern, including handling a host that is too old and cleaning up in onunload.

MCP tools, resources, and prompts

addMcpTool(name, description, schema, callback) sends whatever your callback returns back to the client as a single block of JSON text. From extension API version 3 you can instead pass a definition object, and the callback returns the complete MCP result, which reaches the client unchanged. Use it when you need images, structuredContent checked against an outputSchema, or an isError result that tells the model a call failed in a way it can recover from.

Everything in this section needs version 3, so ask for it when you call getAPI. The types describe the whole interface whichever version you pass, so an extension that asks for 2 still compiles against these methods, and then finds them missing at runtime on an older host:

const api = getAPI(this.app, this.manifest, 3);

api?.addMcpTool({
  name: "comments_count",
  description: "Count the comments on a note",
  inputSchema: { path: z.string() },
  outputSchema: { count: z.number() },
  callback: async ({ path }) => {
    const count = await countComments(path as string);
    return {
      content: [{ type: "text", text: `${count} comments` }],
      structuredContent: { count },
    };
  },
});

Version 3 also lets an extension expose things that aren't tools:

  • addMcpResource({ name, uri, read }) adds a resource at a fixed URI.
  • addMcpResourceTemplate({ name, uriTemplate, read, list? }) adds a family of resources addressed by an RFC 6570 template such as tandem://comments/{path}. read gets the matched variables. list is optional, and when you provide it, its resources appear in resources/list.
  • addMcpPrompt({ name, argsSchema?, callback }) adds a prompt. MCP passes prompt arguments as strings.

Clients that are already connected are notified when these lists change, and unregister() removes everything the handle registered.

Documenting your routes

The plugin can't see what an extension's routes accept or return, so they don't appear in the OpenAPI spec until the extension describes them. addOpenApiDescription (extension API version 3) takes the paths, components, and tags your routes need, in the same shape as the matching parts of an OpenAPI document, and merges them into the spec served at /openapi.yaml, /openapi.json, and the MCP openapi-spec resource:

Request version 3 from getAPI so an older host fails loudly instead of lacking the method:

const api = getAPI(this.app, this.manifest, 3);
api.addRoute("/widgets/:id/").get(handler);
api.addOpenApiDescription({
  paths: {
    "/widgets/{id}/": {
      get: {
        tags: ["Widgets"],
        summary: "Return one widget.",
        parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
        responses: { "200": { description: "The widget." } },
      },
    },
  },
  tags: [{ name: "Widgets", description: "Routes added by the Widgets plugin." }],
});

Write path parameters the OpenAPI way ({id}), not express's (:id). Each path you contribute is published with an x-obsidian-extension field set to your plugin ID. A path, component, or tag that the plugin or another extension already declares makes the call throw without publishing anything, and unregister() removes your description along with your routes.

Sub-resources under a note

Routes added with addRoute can't live under /vault/, because the plugin's own /vault/* handler claims those paths first. From extension API version 3, addVaultSubresource(name) lets an extension serve routes under any note instead:

import type { VaultSubresourceRequest } from "obsidian-local-rest-api";

const comments = api.addVaultSubresource("comments");
comments.get("/", (req, res) => {
  const { vaultFile } = req as VaultSubresourceRequest;
  res.json(listComments(vaultFile));
});
comments.get("/:id", (req, res) => { /* ... */ });

GET /vault/Notes/draft.md/comments/a1f3 then reaches that router as GET /a1f3, with the note attached as req.vaultFile; /active/comments/a1f3 does the same for the active file. The plugin resolves the note before your router runs, so it only ever sees notes that exist, and a request your router doesn't answer continues to the plugin's own handlers. Requests need the API key; signed URLs never reach a sub-resource.

A %2F in the URL is a literal slash inside one segment, which Express's own route matching can't tell apart from a separator. req.vaultSubresourceSegments holds the segments after the name, each decoded on its own, for when that matters.

A name is one path segment. heading, block and frontmatter are reserved, and each name can only be registered by one extension at a time.

Extension events

From extension API version 3, an extension can add its own events to the event streams. Each one is streamed under the extension's plugin id as the emitter:

const api = getAPI(this.app, this.manifest, 3);

// Your plugin's own Events instance; call this.events.trigger("task-completed", ...)
// wherever the event happens.
this.events = new Events();

api.addStreamableEvent("task-completed", {
  source: this.events,
  serialize: (file, line) => ({ path: (file as TFile).path, line }),
});
// Now available at POST /events/<your plugin id>/task-completed/

Your serializer decides everything a stream sends. The host adds emitter and event and sends nothing else, so return only what someone holding a stream URL should see. Return null to skip an occurrence. unregister() closes any open streams for your events.

Extension state

From extension API version 4, an extension can publish its own namespace in the state section of GET /, under its plugin id, for anything a client would read to decide whether to proceed: whether an index is built, when a sync last ran, how much work is queued.

const api = getAPI(this.app, this.manifest, 4);

api.addState({
  description: "Whether the semantic index is ready to query, and how far along a rebuild is.",
  schema: {
    type: "object",
    required: ["ready", "pending"],
    properties: {
      ready: { type: "boolean" },
      pending: { type: "integer", description: "Notes not yet indexed." },
      lastRunAt: { type: ["string", "null"], format: "date-time" },
    },
  },
  read: async () => ({ ready: this.index.ready, pending: this.queue.length, lastRunAt: this.lastRunAt }),
});
// Now served as state["<your plugin id>"] on every authenticated GET /

read is called on every authenticated GET /, concurrently with every other extension's, and raced against the read budget from the plugin's advanced settings. A read that overruns, rejects, or resolves to anything but a JSON object is served as null for your extension and logged to the console, once, until a read succeeds again; nothing else in the response is affected. Keep it cheap: compute in the background and hand back the latest result. The schema, if you give one, is merged into the GET / response schema at /openapi.yaml, so clients can rely on the shape; without it the namespace is documented as a free-form object. Either way the published entry also admits null, since that is what a failed read is served as. unregister() removes both.

Known extensions

Contributing

See CONTRIBUTING.md. If you want to add functionality without modifying core, consider building an API extension instead — extensions can be developed and released independently.

Credits

Inspired by Vinzent03's advanced-uri plugin, with the goal of expanding automation options beyond the constraints of custom URL schemes.

Similar Plugins

info
• Similar plugins are suggested based on the common tags between the plugins.
Core Search Assistant
5 years ago by qawatake
An Obsidian plugin to enhance built-in search: keyboard interface, card preview, bigger preview
Power Search
5 years ago by Aviral Batra
Settings Search
5 years ago by Jeremy Valentine
Adds a search bar to Obsidian.md's settings
Search Everywhere
5 years ago by Mom0
Obsidian Search Everywhere Plugin
Card View Switcher
5 years ago by qawatake
An Obsidian plugin to provide a quick switcher with card view
Version History Diff (Sync, File Recovery & Git)
5 years ago by kometenstaub
Get a diff view of your Obsidian Sync, File Recovery and Git version history
Quiet Outline
5 years ago by the_tree
Improving experience of outline in Obsidian
Book Search
4 years ago by anpigon
Obsidian plugin that automatically creates notes by searching for books
Execute Code
4 years ago by twibiral
Obsidian Plugin to execute code in a note.
Enveloppe
4 years ago by Mara-Li
Enveloppe helps you to publish your notes on a GitHub repository from your Obsidian Vault, for free!
Media DB Plugin
4 years ago by Moritz Jung
A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.
Packrat
4 years ago by Thomas Herden
Process completed instances of recurring items created by the Obsidian Tasks plugin
User Plugins
4 years ago by mnowotnik
Allows user scripts to use plugin API
Linkify
4 years ago by Matthew Chan
Text Expander JS
4 years ago by Jonathan Heard
Obsidian plugin: Type text shortcuts that expand into javascript generated text.
Open File by Magic Date
4 years ago by simplgy
Note Linker
4 years ago by Alexander Weichart
🔗 Automatically link your Obsidian notes.
Day and Night
4 years ago by Kevin Patel
An Obsidian plugin to automatically switch between day and night themes based on a set schedule
Copy Search URL
4 years ago by Carlo Zottmann
A plugin for Obsidian (https://obsidian.md) that adds a menu entry to its search view for copying the Obsidian search URL.
Obsidian to Flomo
4 years ago by Xiaoyu Li
Quickly share content to Flomo.
Chorded Hotkeys
4 years ago by Trey Connor Meyers
Type multiple letters at the same time to trigger text insertion, template insertion, or command execution.
External Link Helper
4 years ago by Jhonghee Park
Obsidian plugin for link suggestion
Contacts
4 years ago by vbeskrovnov
With this plugin, you can easily organize and manage your contacts within Obsidian. Simply create a note with contact information and use the plugin's features to quickly search, and sort through your contacts. Contacts plugin also helps you to remember birthdays of your contacts and keeps track of the last time you met them.
Weekly Review
4 years ago by Brandon Boswell
Khoj
4 years ago by Debanjum Singh Solanky
Your AI second brain. Self-hostable. Get answers from the web or your docs. Build custom agents, schedule automations, do deep research. Turn any online or local LLM into your personal, autonomous AI (gpt, claude, gemini, llama, qwen, mistral). Get started - free.
Babashka
4 years ago by Filipe Silva
Run Obsidian Clojure(Script) codeblocks in Babashka.
Unicode Search
4 years ago by BambusControl
Simple Unicode character search for Obsidian.md
Emoji Magic
4 years ago by simplgy
Makes it easier to add emojis using an improved keyword search
Commando
4 years ago by qaptoR
Obsidian Plugin for automatically repeating commands with loop iterations
File Publisher
4 years ago by Devin Sackett
Global Search and Replace
4 years ago by Mahmoud Fawzy Khalil
A plugin to do a global search and replace in all your Obsidian vault files.
Floating Search
4 years ago by Boninall
A plugin for searching text by using Obsidian default search view.
Tab Rotator
3 years ago by Steven Jin
Obsidian Rotate opened tabs with a specified time interval
Semantic Search
3 years ago by bbawj
Semantic search for Obsidian.md
Cron
3 years ago by Callum Loh
Obsidian cron / schedular plugin to schedule automatic execution of commands
Fuzzy Chinese Pinyin
3 years ago by lazyloong
Auto Template Trigger
3 years ago by Numeroflip
An obsidian.md plugin, to automatically trigger a template on new file creation
Open Plugin Settings
3 years ago by Mara-Li
Create a command that open the settings tabs of a registered plugin (because I was bored to open the parameters).
Pieces for Developers
3 years ago by Pieces For Developers
Pieces' powerful extension for Obsidian-MD that allows users to access their code snippets directly within the Obsidian workspace
Arcana
3 years ago by A-F-V
Supercharge your Obsidian note-taking through AI-powered insights and suggestions
APIRequest
3 years ago by rooyca
Obsidian plugin that allows you to integrate API data into your notes with request caching, variable support, and precise JSON extraction.
Auto Front Matter
3 years ago by conorzhong
Auto Hyperlink
3 years ago by take6
Simple CanvaSearch
3 years ago by ddalexb
Syncthing Integration
3 years ago by LBF38
Obsidian plugin for Syncthing integration
RunJS
3 years ago by eoureo
Let's run JavaScript easily and simply in Obsidian.
Attachment Manager
3 years ago by chenfeicqq
Attachment folder name binding note name, automatically rename, automatically delete, show/hide.
YouVersion Linker
3 years ago by Jaanonim
Obsidian plugin that automatically link bible verses to YouVersion bible.
Gnome Terminal Loader
3 years ago by David Carmichael
Codeblock Template
3 years ago by Super10
A template plugin that allows for the reuse of content within Code Blocks!一个可以把Code Block的内容重复利用模板插件!
Search Templates Library
3 years ago by Pentchaff
Obsidian plugin that allows to store searches templates for later use, and displays search results both in the search view and graph view.
Modal forms
3 years ago by Danielo Rodriguez
Define forms for filling data that you will be able to open from anywhere you can run JS
Tag Breakdown Generator
3 years ago by Hananoshika Yomaru
Break down nested tags into multiple parent tags
Auto Filename
3 years ago by rcsaquino
Auto Filename is an Obsidian.md plugin that automatically renames files in Obsidian based on the first x characters of the file, saving you time and effort.
Frontmatter generator
3 years ago by Hananoshika Yomaru
A plugin for Obsidian that generates frontmatter for notes
Run
3 years ago by Hananoshika Yomaru
Generate markdown from dataview query and javascript.
RSS Copyist
3 years ago by aoout
Get the RSS articles as notes.
Daily note creator
3 years ago by Mario Holubar
Automatically creates missing daily notes.
AI Tagger
3 years ago by Luca Grippa
Simplify tagging in Obsidian. Instantly analyze and tag your document with one click for efficient note organization.
Prompt ChatGPT
3 years ago by Coduhuey
Differential ZIP Backup
3 years ago by vorotamoroz
Command Block List
3 years ago by Ryota Ushio
Hide unwanted commands from the command palette in Obsidian.
Line Commands
3 years ago by charliecm
Adds commands to quickly select, copy, cut, and paste lines under the selection or cursor.
Moulinette Search for TTRPG
3 years ago by Moulinette
Plugin for Obsidian
Automation
3 years ago by Benature
Personal OS
3 years ago by A.Buot
LinkMagic
2 years ago by AndyReifman
Notes 2 Tweets
2 years ago by Tejas Sharma
Generate and schedule tweets automatically from your notes on Obsidian
Rapid AI
2 years ago by Rapid AI
AI Assistant for selected text and generating content with Markdown. Shortcuts and quick action buttons provide instant AI assistance. It provides a high availability API for unlimited Chat GPT request rates, so you can ensure smooth work for any workload.
Substitutions
2 years ago by BambusControl
Automatic text replacer for Obsidian.md
Search In Canvas
2 years ago by Boninall
Watched-Metadata
2 years ago by Nail Ahmed
Watches for changes in metadata and updates the note content accordingly.
Daily Note Structure
2 years ago by db-developer
This obsidian plugin creates a structure for your daily notes
Latex Render
2 years ago by jvsteiner
An Obsidian plugin that renders `label` code blocks to `<svg>` for viewing in notes. Make sure to bring your own command!
Unofficial Fabric Integration
2 years ago by Chasebank87
Integrate fabric by danielmiessler/fabric into Obsidian
Giphy
2 years ago by LuCrypto
OpenAPI Renderer
2 years ago by Sentiago
Integrate OpenAPI specification management into Obsidian with features for version control, visualization, editing, and easy navigation of API specs.
Note Linker with Previewer
2 years ago by Nick Allison
Obsidian Plugin to find and Link notes
Paste as Embed
2 years ago by Matt Laporte
Obsidian plugin to paste contents of clipboard into a new note, and embed it in the active note.
Note 2 Tag Generator
2 years ago by Augustin
Fast Image Auto Uploader
2 years ago by Longtao Wu
upload images from your clipboard by gopic
Current File
2 years ago by Mark Fowler
An Obsidian plugin to allows external applications to know what file Obsidian is currently viewing
Blockreffer
2 years ago by tyler.earth
An Obsidian plugin to search and embed blocks with ^block-references (aka ^block-ids)
Snippets Manager
2 years ago by Venkatraman Dhamodaran
Snippets Manager (Text Expander) For Obsidian
Auto Periodic Notes
2 years ago by Jamie Hurst
Obsidian plugin to create new periodic notes automatically in the background and allow these to be pinned in your open tabs. Requires the "Periodic Notes" plugin.
Text expand
6 years ago by MrJackphil
A simple text expand plugin for Obsidian.md
Templater
6 years ago by SilentVoid
A template plugin for obsidian
Snippets
6 years ago by Pelao
Vantage - Advanced search builder
6 years ago by ryanjamurphy
Vantage helps you build complex queries using Obsidian's native search tools.
Tag Wrangler
6 years ago by PJ Eby
Rename, merge, toggle, and search tags from the Obsidian tag pane
Buttons
6 years ago by Sam Morrison
Buttons in Obsidian
Advanced URI
6 years ago by Vinzent
Advanced modes for Obsidian URI
Regex Pipeline
6 years ago by No3371
An Obsidian plugin that allows users to setup custom regex rules to automatically format notes.
Excalidraw
5 years ago by Zsolt Viczian
A plugin to edit and view Excalidraw drawings in Obsidian
Zoottelkeeper
5 years ago by Akos Balasko
Obsidian plugin of Zoottelkeeper: An automated folder-level index file generator and maintainer.
Apply Patterns
5 years ago by Jacob Levernier
An Obsidian plugin for applying patterns of find and replace in succession.
Relative Find
5 years ago by phibr0
Shell commands
5 years ago by Jarkko Linnanvirta
Execute system commands via hotkeys or command palette in Obsidian (https://obsidian.md). Some automated events are also supported, and execution via URI links.
CustomJS
5 years ago by Sam Lewis
An Obsidian plugin to allow users to reuse code blocks across all devices and OSes
URI Commands
5 years ago by kzhovn
Execute URIs from the command palette
JavaScript Init
5 years ago by ryanpcmcquen
Run custom JavaScript in Obsidian.
Another Quick Switcher
5 years ago by tadashi-aikawa
This is an Obsidian plugin which is another choice of Quick switcher.
Webhooks
5 years ago by Stephen Solka
Connect obsidian to the internet of things via webhooks
Omnisearch
4 years ago by Simon Cambier
A search engine that "just works" for Obsidian. Supports OCR and PDF indexing.
Command Tracker
2 years ago by namikaze-40p
An Obsidian plugin that tracks the number of times the command is used.
Lemons Search
2 years ago by Moritz Jung
An Obsidian plugin that offers a fast fuzzy finder based quick switcher with preview.
Jura Links
2 years ago by Lukas Collier & Emi Le
Verlinke deine Normangaben, Aktenzeichen oder Fundstellen in deiner Obsidian Notiz mit Gesetzesanbietern.
Metadata Auto Classifier
2 years ago by Beomsu Koh
AI-powered Obsidian plugin that automatically classifies and generates metadata (tags, frontmatter) for your notes.
Smart Composer
2 years ago by Heesu Suh
AI chat assistant for Obsidian with contextual awareness, smart writing assistance, and one-click edits. Features vault-aware conversations, semantic search, and local model support.
Tree Search
2 years ago by catacgc
Text Finder
2 years ago by hafuhafu
Provides a find/replace window in edit mode similar to VSCode (supports regular expressions and case sensitivity).
Todos sort
2 years ago by Jiri Sifalda
A plugin for Obsidian that sorts todos within a note
Magiedit
2 years ago by Matteo Gassend
pycalc
2 years ago by pycalc
Clipper Catalog
2 years ago by Greg K.
A catalog view that provides a powerful interface for all your clipped web articles and content. Easily organize, search, and manage your web clippings within your vault.
Hanko
2 years ago by Telehakke
Obsidian plugin.
Sentinel
2 years ago by Giorgos Sarigiannidis
A plugin for Obsidian that allows you to update properties or run commands based on document visibility changes.
Missing Link File Creator
2 years ago by Lemon695
The plugin creates both missing links and the corresponding files.
NetClip
2 years ago by Elhary
this plugin is for Obsidian that allows you to browse the web and clip webpages directly into your vault.
Plugin REPL
2 years ago by readwithai
An in-note Read Evaluate Print Loop to execute JavaScript within Obsidian
InlineAI
2 years ago by FBarrca
Media Companion
2 years ago by Nick de Bruin
AI Providers
2 years ago by Pavel Frankov
This plugin is a hub for setting AI providers (OpenAI-like, Ollama and more) in one place.
Enhanced Canvas
2 years ago by RobertttBS
When editing on Canvas, properties and Markdown links to notes are automatically updated, enabling backlinks in Canvas.
Varinote
2 years ago by Giorgos Sarigiannidis
A plugin for Obsidian that allows you to add variables in Templates and set their values during the Note creation.
Mastodon Threading
2 years ago by El Pamplina de Cai
Obsidian plugin to compose and post threads to Mastodon
AI integration Hub
2 years ago by Hishmat Salehi
A modular AI integration hub for Obsidian
Organized daily notes
2 years ago by duchangkim
Automatically organizes your daily notes into customizable folder structures for better organization and easier navigation.
Hotstrings
2 years ago by wakywayne
Runsh
2 years ago by Ddone
A simple plugin that allows to run shell commands from obsidian.
EUpload
2 years ago by Appleex
Obsidian 插件,专用于上传文件到存储仓库。目前支持 Lskypro(兰空图床),后续有需求会引入其它存储方式,如:Github/Gitee等等。
Inkporter
2 years ago by Ayush Kumar Saroj
Inkporter is an Obsidian plugin that digitizes handwritten notes with smart ink isolation, adaptive theming, and seamless import workflows.
Automatic Linker
2 years ago by Kodai Nakamura
Last Position
2 years ago by saktawdi
Automatically scroll to the last viewed position when opening the markdown document.
Vault File Renamer
2 years ago by Louan Fontenele
Vault File Renamer: Automatically standardizes file names to GitHub style (lowercase, no accents, only -, ., _) while preserving folder structure and file contents.
Rsync
2 years ago by Ganapathy Raman
An Obsidian plugin to perform sync files between machines using Rsync
Task Mover
2 years ago by Mariia Nebesnaia
A plugin for obsidian to move unfinished tasks to the daily note automatically
Title As Link Text
2 years ago by Lex Toumbourou
An Obsidian plugin to set the Link Text using the document title
KOI Sync
2 years ago by Luke Miller
AI Tagger Universe
2 years ago by Hu Nie
An intelligent Obsidian plugin that leverages AI to automatically analyze note content and suggest relevant tags, supporting both local and cloud-based LLM services.
Memos AI Sync
2 years ago by leoleelxh
obsidian-memos-sync-plugin,将 Memos 内容同步到 Obsidian 的插件,提供无缝集成体验。
Blog AI Generator
2 years ago by Gareth Ng
Obsidian Plugin: generate blog via AI based on the current note.
tidit
2 years ago by codingthings.com
tidit is an Obsidian - https://obsidian.md - plugin that adds timestamps to your document as you type — when you want it, how you want it, where you want it.
Copy Local Graph Paths
2 years ago by Amy Z
copy-local-graph-paths is a simple Obsidian plugin that copies the paths of notes linked to your current page.
IMSwitch in Math Block
2 years ago by XXM
Image Picker
2 years ago by ari.the.elk
One Step Wiki Link
2 years ago by Busyo
用于 Obsidian 一步插入当前界面匹配到的所有外链(维基链接)
TG Emoji Search
2 years ago by MarsBatya
CmdSearch
2 years ago by SpaceshipCaptain
Data Fetcher
2 years ago by qf3l3k
Fetch data from multiple sources (REST APIs, RPC, gRPC, GraphQL) and insert results into notes.
Auto Daily Note
a year ago by John Dolittle
Daily Notes Automater
a year ago by David Pedrero
Tasks Cleaner
a year ago by lowit
🧹 Tasks Cleaner is a plugin for Obsidian that helps you automatically remove old completed tasks from your Markdown notes
Template Filename
a year ago by Callum Alpass
Obsidian plugin for creating notes with templatable filenames
Note UID Generator
a year ago by Valentin Pelletier
Allow you to automatically generate UID for the notes in your vault.
Discord Message Sender
a year ago by okawak
Obsidian Plugin: Send messages from a Discord channel to your Vault
Auto Replacer
a year ago by Alecell
A live text replacement plugin that applies automatic formatting, corrections, or custom replacements in real-time. Define your own regex-based rules and transformation logic to modify text dynamically as you type.
Current View
a year ago by Lucas Ostmann
Automatically set the view mode (Reading, Live Preview, Source) for notes in Obsidian using folder rules, file patterns, or frontmatter.
Timeline Canvas Creator
a year ago by chris-codes1
Quickly create timeline structured canvases in Obsidian.
Random Wikipedia Article
a year ago by SpencerF718
An Obsidian plugin to generate a note of a random Wikipedia article.
EasyLink
a year ago by isitwho
Select text in your obsidian editor to find the most similar content from other notes and easily create links.
Clipboard Manager
a year ago by Ayush Raj
The clipboard obsidian plugin
Content OS
a year ago by eharris128
Post to LinkedIn from within Obsidian
Code Blocks commands
a year ago by dragonish
Provide commands to insert code blocks with markup, and support triggering commands with backticks.
NotePix
a year ago by Ayush Parkara
Automatically upload Obsidian images to GitHub (public/private) and replace them with fast hosted links. Encrypted, cross-platform.
Note Codes
a year ago by Ezhik
Reference your Obsidian notes from anywhere with simple 4-character codes.
URL Formatter
a year ago by Thomas Snoeck
Automatically formats specific URLs pasted into Obsidian into clean Markdown links.
Move Cursor On Startup
a year ago by Jared Kelnhofer
Obsidian plugin to move the cursor to the right and back to the left when starting up. Why? To keep DataView expressions from not running on the first load of, say, your Home file.
Google Calendar Importer
a year ago by Fan Li
A simple and light-weighted google calendar importer, allow injecting the events / tasks of a day automatically to your daily notes, or import it to anywhere with a command.
Open or Create File
a year ago by Ilya Paripsa
Set up Obsidian commands that create or open files based on predefined patterns.
Steward
a year ago by Dang Nguyen
A vault-specific agent equipped with agentic capacity, fast search, flexible commands, vault management, and terminals to "jump" into other CLI agents, such as Claude, Gemini, etc.
API Designer
10 months ago by Ruveyda Yilmaz
A plugin for Obsidian that lets you design and document API endpoints visually without leaving your notes.
Handlebars Dynamic Templating
8 months ago by Hide_D
Handlebars dynamic templating. Define template files and use them dynamically via hb blocks. Template recursion is also possible.
Segerlab
6 months ago by Semyon Kononchuk
Renders calculator views within Obsidian notes from JSON data copied from the Segerlab app.
Agent Client
5 months ago by rait-09
Bring AI agents into Obsidian via Agent Client Protocol (ACP), such as Claude Code, Codex and Gemini CLI.
Excalidraw Extras
4 months ago by zsviczian
Companion Obsidian.md plugin hosting extra add-on optional features for the main Excalidraw-Obsidian plugin
Commander
4 months ago by jsmorabito
Commander - Obsidian Plugin | Add Commands to every part of Obsidian's user interface
Hot Reload
3 months ago by pjeby
Automatically reload Obsidian plugins in development when their files are changed
Large Language Models
3 months ago by eharris128
The LLM plugin gives Obsidian users access to local and web-based, large language models via several chat interfaces: modal, widget, FAB window, and commands.
Note Toolbar
3 months ago by chrisgurney
Flexible, context-aware toolbars for your notes in Obsidian.
Claudian
3 months ago by Yishen Tu
An Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault
Advanced Note Mover
3 months ago by Lars Bücker
Quickly and easily move notes to predefined folders. Perfect for organizing your notes.
Markdown Tabs
3 months ago by xhuajin
Create and render a Tabs component in your notes.
Neural Composer
3 months ago by Oscar Campo
AI-Powered Graph Memory for Obsidian
Mention Autocomplete
3 months ago by Darren Zheng
Type `@` to instantly search and link any note in your vault — with full-text search, rendered preview, and smart sentence extraction.
Gay Toolbar
3 months ago by chaskane
Colorful, customizable toolbar for Obsidian, designed for mobile.
Notebook Navigator
3 months ago by Johan Sanneblad
A better file browser and calendar inspired by Apple Notes, Bear, Evernote and Day One.
Local LLM Hub
3 months ago by TAKESHI MORITA
All-in-one local AI hub for Obsidian — LLM chat with vault tools, MCP servers, RAG, workflow automation, encryption, and edit history. Fully private, no cloud required.
Gemini Helper
3 months ago by TAKESHI MORITA
AI chat, workflow automation, semantic search (RAG), LLM Wiki (OKF) powered by Google Gemini. Works on both desktop and mobile.
Browser Note
3 months ago by fengshuzi
Obsidian plugin: localhost HTTP API for vault notes with a built-in browser UI.
Ribbon Folder
3 months ago by limniemdung
obsidian plugin ribbon folder
MCP Connector
2 months ago by istefox
Your Obsidian vault, exposed to Claude and any MCP client. Runs inside Obsidian, on-device semantic search, no cloud round-trip, no binary to download.
Journal View
2 months ago by RUverse
A Journal View for Obsidian
Daily News Briefing
2 months ago by Adam Chen
Get AI-powered daily news summaries directly in your Obsidian vault. Stay informed about your topics of interest with smart, automated news collection and summarization.
Community Install Manager
2 months ago by Konstantin Volobuev
Allows you to use `community-plugins.js` to search for and automatically install plugins when you launch `obsidian`.
Workbuddian
2 months ago by jiang198012
Obsidian 插件:把本地 CodeBuddy CLI 或 Hermes agent 变成 vault 内 AI 聊天——流式回复、@引用、双后端切换。Obsidian plugin turning local CodeBuddy CLI or Hermes agent into an in-vault AI chat with streaming, @-references, and dual backends.
Granola Meetings Simple Sync
2 months ago by philfreo
"Granola Meetings Simple Sync" Plugin for Obsidian