Sanket Saurav43 downloadsShare individual notes publicly on your own domain via a self-hosted Outcrop server.
Outcrop shares individual Obsidian notes on your own domain. One container,
one SQLite database, and an Obsidian plugin. Run "Share note" on any note and
it is live at https://notes.example.com/<slug>, rendered the way it looks
in your vault. Edit the note and the page updates itself a few seconds later.
It has the essential primitives for sharing notes:
amber-falcon-83)<head> are managed from the plugin's settingsTwo pieces:
share_id, share_url) lives in
the note's frontmatter, so it survives sync and renames. The plugin works
on desktop and mobile.You need a domain and a machine with Docker.
mkdir -p outcrop/data && cd outcrop
sudo chown 65532:65532 data # the container runs as a non-root user
openssl rand -hex 32 # this is your API key
compose.yaml:
services:
outcrop:
image: ghcr.io/sanketsaurav/outcrop:latest
restart: unless-stopped
environment:
API_KEY: ${OUTCROP_API_KEY}
BASE_URL: https://notes.example.com
SITE_AUTHOR: Your Name # optional
TRUST_PROXY: "1"
volumes:
- ./data:/data
ports:
- "127.0.0.1:8080:8080"
Put TLS in front with a reverse proxy. With Caddy:
notes.example.com {
reverse_proxy 127.0.0.1:8080
}
Start it and check it responds:
OUTCROP_API_KEY=<your key> docker compose up -d
curl https://notes.example.com/healthz # ok
Install Outcrop from Obsidian's community plugins (Settings → Community plugins → Browse), enable it, then open its settings, enter the server URL and API key, and run Test connection. On a fresh server this also installs the default theme.
Manual install: download manifest.json, main.js, styles.css from the
latest release into
<vault>/.obsidian/plugins/outcrop/.
| Command | What it does |
|---|---|
| Share current note | Publishes (or updates) the note and copies the link |
| Copy share link / Copy passcode | Copies to the clipboard |
| Rotate share link | New URL; the old one stops working |
| Protect note with a passcode | Generates a passcode and gates the page |
| Remove passcode | Opens the note back up |
| Unshare current note | Deletes the share from the server |
| Update all shared notes | Re-publishes everything (useful after theme changes) |
| Open shared notes list | A table of every share with per-row actions |
| Push theme to server | Deploys your CSS/JS/head edits |
Shared notes re-publish automatically a few seconds after you stop editing
(configurable, or turn it off). The same actions are in a note's right-click
menu, and shared notes show a ⛰ shared status-bar item with a click menu.
When you share, unshare, or rotate a note, other shared notes that link to it are re-published so their links stay correct. Deleting a shared note from your vault does not delete the share; it shows up flagged in the shared-notes list, where you can remove it.
Frontmatter is never published. The plugin stores its state there:
share_id: 8fK2… # managed by the plugin; don't edit
share_url: https://notes.example.com/V5tGkq2Xw8
share_passcode: amber-falcon-83 # on protected notes; edit to change, delete to unprotect
And these are yours to set:
share_slug: how-i-take-notes # readable URL instead of the random token
share_title: Override title
share_description: Custom description for search and link previews
share_noindex: true # keep this note out of search engines and the sitemap
share_class: justify # extra CSS classes on this note's body; the theme
# defines what they do ("justify" ships by default)
Passcodes are hashed on the server (PBKDF2), unlock attempts are rate-limited per IP and note, and rotating the link or changing the passcode signs everyone out. A generated passcode has about 23 bits of entropy: right for a note you hand to a few people, wrong for secrets.
Three editable pieces in the plugin's settings, served on every public page:
<head>. Fonts go here; the default
theme loads Google Sans, Google Sans Code, and Crimson Pro from Google
Fonts as variable fonts, and a commented example shows how to swap them.Edit, then Push theme to server; every shared note picks it up. For full
control of the page structure, put a template.html in the server's data
folder, starting from the built-in one at
server/internal/web/templates/page.html.
Public notes carry canonical URLs, meta descriptions, OpenGraph and Twitter
tags, JSON-LD, and a preview image generated from the note's title, so a
pasted link unfurls properly in chat and social apps. sitemap.xml lists
indexable notes. A note marked share_noindex (or protected by a passcode)
is excluded from the sitemap and tagged noindex; there is also a setting to
make new shares noindex by default if you mostly share unlisted links.
Everything is environment variables on the server:
| Variable | Default | Meaning |
|---|---|---|
API_KEY |
required | shared secret between plugin and server (API_KEY_FILE works too) |
BASE_URL |
required | public origin, e.g. https://notes.example.com |
SITE_NAME |
host of BASE_URL |
shown in page titles and preview images |
SITE_AUTHOR |
unset | author name in structured data |
OG_ACCENT |
#6c5ce7 |
accent color in generated preview images |
MAX_NOTE_MB / MAX_ASSET_MB |
2 / 25 |
upload size limits |
TRUST_PROXY |
0 |
set 1 behind a reverse proxy so rate limits see client IPs |
LISTEN_ADDR / DATA_DIR |
:8080 / /data |
rarely need changing |
Everything lives in the data folder: outcrop.db and blobs/. Back up that
folder and you have backed up everything:
docker compose stop && tar czf backup.tgz data && docker compose start
Litestream works for continuous replication of the database file.
To update the server, pull and recreate; the database migrates itself:
docker compose pull && docker compose up -d
Releases also publish :X.Y.Z tags to pin instead of :latest. The plugin
updates through Obsidian's normal community-plugin updates.
API_KEY or BASE_URL is missing, or
the data folder is not writable by uid 65532. docker logs says which.TRUST_PROXY=1.CONTRIBUTING.md covers the development setup, tests, and the release process.
MIT