Search
Quick open, the sidebar keyword pane, the three-mode search page, semantic search setup, and finding text inside imported PDFs and Word files.
Jotura has four ways to find something, and each one matches a different thing. Quick open matches file names and frontmatter aliases. The sidebar search pane and the search page both match note titles and note bodies, in keyword, semantic, or smart mode. The command palette matches command names rather than your notes.
Your indexes are built on your machine. The keyword index lives in your vault at .jotura/index/, and the semantic vector index lives in the same folder at .jotura/vectors.db. Neither file is ever uploaded, though in a vault that syncs the vectors inside vectors.db do travel between your devices as ciphertext, as described under Shared across devices. Keyword search never touches the network. Semantic search depends on which embedding provider you pick: with the local provider the only network request is the one-time download of the embedding model, and with OpenAI embeddings your note text and your search queries are sent to OpenAI under your own API key. Both are described under Turning on semantic search, and nothing is sent to OpenAI unless you choose it. Search also never waits on cloud sync: a vault whose sync key is not cached still reads, writes, and searches normally. See Sync and sharing for what pausing sync does and does not affect.
The four surfaces at a glance
| Surface | How to open | What it matches | Notes |
|---|---|---|---|
| Quick open | Cmd/Ctrl+P | File names and frontmatter aliases | Fastest way to jump to a file you can name |
| Sidebar search pane | The Search icon in the sidebar, or the Open Search Panel command |
Note titles and note bodies, in keyword, semantic, or smart mode | Starts in Keyword. Has the case, whole-word, and phrase toggles in Keyword mode |
| Search page | Cmd/Ctrl+Shift+F, or the Search Vault command |
Note titles and note bodies, in keyword, semantic, or smart mode | Opens as a pinned tab |
| Command palette | Cmd/Ctrl+Shift+P | Command names, not your notes | Fuzzy match over the 21 built-in commands |
All four search the vault. There is no find inside the note you are currently editing. Cmd/Ctrl+F is not bound to anything, and source mode installs no CodeMirror search keymap, so neither the WYSIWYG editor nor source mode has an in-pane find or a find-and-replace. If you need to locate a phrase within one long note, search the vault for it and open the result, or edit the file with another tool. See The editor for what the editing surface does provide.
Two quirks on the Dashboard, the screen shown when no tab is active, are worth knowing before you go further. Its four quick actions are New note, Quick open, Search vault, and Command palette, and the Search vault action opens the sidebar pane, while the command palette entry named Search Vault opens the search page. The Dashboard also prints macOS glyphs (⌘P, ⌘⇧F, ⌘⇧P) on every platform, so on Windows and Linux the chord shown there is not the chord you press. The real chords are in the shortcuts table at the end of this page.
Quick open
Cmd/Ctrl+P toggles quick open. It matches the file name and the note’s frontmatter aliases, not the folder path and not the content, using a fuzzy matcher that is always case-insensitive.
| Behavior | Detail |
|---|---|
| What is searched | The file name including its extension, plus every alias listed under aliases in the note’s frontmatter. A result matched through an alias shows the alias beside the file name |
| What is listed | Files whose extension Jotura recognizes: the text extensions listed further down, plus documents (pdf, docx and the other office formats, epub, rtf), images, and audio and video (mp3, m4a, wav, ogg, flac, mp4, mov, webm, mkv). A file with no extension, or with an unrecognized one such as zip, is never offered |
| Result limit | 20 |
| Debounce | 80 ms |
| Empty query | Every listed file scores zero and the list is truncated to 20, so opening quick open shows files straight away |
| Navigation | ArrowUp and ArrowDown move, Enter opens, and the list clamps at both ends rather than wrapping |
| Mouse | Hovering a row makes it the active row |
| Placeholder | Search files by name… |
| Empty state | No files match., shown only once the query is non-empty |
| Closing | Escape, clicking outside the dialog, or Cmd/Ctrl+P again |
The query is deliberately not cleared when you close quick open, so reopening restores what you last typed. The input is selected on open, so typing immediately replaces it.
Quick open has no > command prefix and no #tag filter. Commands live in the command palette, and tags have their own sidebar pane.
One consequence of listing documents as well as notes: an imported document and its converted text note both appear. Searching for report can show both report.pdf and report.pdf.md. Opening either one lands you in the same converted text.
The sidebar search pane
Open the pane from the sidebar’s Search icon, or run Open Search Panel from the command palette. This command has no keyboard shortcut of its own.
The pane has the same Keyword, Semantic, and Smart buttons as the search page and starts in Keyword. Semantic and Smart stay grayed out until semantic search is ready for the vault, and return up to 30 results. In Keyword mode the pane runs against the same index as the search page, but it exposes three query controls that the search page does not.
| Toggle | Label in the UI | Effect |
|---|---|---|
| Case sensitive | Aa |
Matches against a case-preserving copy of the body text instead of the lowercased one |
| Whole word | W |
Each term must match a whole token. With it off, each term is matched as a substring |
| Phrase | "" |
Rewrites the query: existing double quotes are stripped and the whole query is wrapped in quotes |
The phrase toggle only rewrites the query and uses the same backend as every other keyword search. Typing "quarterly review" with the quotes yourself has the same effect, and it works in the search page too.
| Behavior | Detail |
|---|---|
| Placeholder | Search vault…, one word shorter than the search page’s placeholder |
| Debounce | 150 ms |
| Result limit | 50 in Keyword mode, 30 in Semantic and Smart |
| Empty states | Type to search. while the query is empty, No results. when a query found nothing |
| Grouping | In Keyword mode, results are split into two labeled groups, Titles (N) and Content (N) |
| Navigation | ArrowUp and ArrowDown move through the combined list, Enter opens the active result, and Escape blurs the input and returns you to the Files pane |
Two separate indexing messages can appear here. A live counter reading indexing… {done}/{total} sits in the toggles row whenever an index build is running. Separately, when your last search ran against an incomplete index, a notice reads Still indexing… results may be incomplete (N%).
Clicking a hashtag inside the editor switches the sidebar to this pane, fills in the tag as the query, and focuses the input.
The search page
Cmd/Ctrl+Shift+F opens the search page. It opens rather than toggles: pressing it again while the page is open just refocuses the input. The same thing happens from the palette command Search Vault.
The page is a tab. It is pinned to the first position, it cannot be dragged or used as a drop target, and you close it with Escape while it is the active tab or with Cmd/Ctrl+W.
| Behavior | Detail |
|---|---|
| Placeholder | Search your vault… |
| Debounce | 250 ms |
| Keyword result limit | 50 |
| Semantic and smart result limit | 30 |
| Empty states | Type to search. before the first search and again every time you clear the input, No results. after a search that found nothing |
| Navigation | Results are plain buttons reached with Tab. There is no arrow-key navigation on this page |
| Result row | Title, score to two decimal places, folder breadcrumb, a heading path when the row has one, and a snippet |
The mode buttons sit under the input, with an info button labeled About search modes that opens a short explainer. Clicking outside the explainer closes it. Escape does more than that: the global handler sees Escape first and closes the whole search tab, because the active tab is the search tab.
The selected mode and the query live in memory only. Closing and reopening the tab keeps both, and both reset when you restart the app.
Keyword mode
Keyword search runs in two passes and merges them.
- A fuzzy pass over file names, matching against the name with the extension stripped. This pass contributes at most 20 results, and it is always case-insensitive, so the case and whole-word options do not apply to it.
- A body pass over the full-text index. Terms are combined with AND, so every term must appear.
Results from the body pass are deduplicated against the title pass, and the merged list is truncated to the limit.
How the body pass ranks depends on the whole-word setting. With whole word on, or for a phrase query, each term becomes a term query and tantivy ranks the matches with BM25, so a word that is frequent in a note and rare across the vault pushes that note up. With whole word off, each term becomes a substring pattern instead, which decides whether a note matches but does not rank by term frequency, so those scores carry much less information. The in-app explainer describes the BM25 behavior without drawing this distinction.
The search page always runs keyword mode with case sensitivity off and whole word off, so it takes the substring path. If you want exact-token or case-sensitive matching, use the sidebar pane’s toggles.
Wrapping your query in double quotes turns it into a phrase query in either surface. A phrase query skips the file-name pass entirely, on the assumption that you asked for an exact run of words in the body.
Semantic mode
Semantic search compares meaning rather than words. Your query and every chunk of every note are turned into embeddings, either by OpenAI’s embedding API under your own key or by a small model running on your machine, and the closest chunks win. It finds a note about cooking pasta when you search for boiling noodles.
| Detail | Value |
|---|---|
| Chunking | Notes are split along headings, targeting about 320 tokens per chunk with a hard cap of 480 |
| Retrieval | The 50 closest chunks are fetched, then grouped by note |
| Per note | One result per note, from its best-scoring chunk |
| Result label | The chunk’s heading path is shown under the title |
| Model | Your choice: OpenAI text-embedding-3-small at 1536 dimensions (the recommended default), OpenAI text-embedding-3-large, or the local bge-small-en-v1.5-int8 at 384 dimensions. See Choosing a provider |
An empty or whitespace-only query returns nothing rather than an error. If the query itself cannot be embedded at that moment, for example while OpenAI is rate limiting, the search returns keyword results instead and shows Search by meaning is busy. Showing keyword results.
Smart mode
Smart mode runs both searches and merges them by rank, not by raw score, using reciprocal rank fusion with a constant of 60. A keyword ranking of the top 50 notes is fused with the semantic note ranking, so a note that does reasonably well in both lists beats a note that tops only one.
Display text depends on where the note came from. A note that appears in the semantic ranking shows its best chunk’s snippet and heading path. A note that ranked only on the keyword side shows the keyword snippet and no heading path at all, so a smart-mode result list can mix rows with heading paths and rows without.
Smart is the default mode. When semantic search is not ready for the current vault, the page silently falls back to Keyword.
Turning on semantic search
Semantic and Smart stay disabled until semantic search is ready for the vault you have open. Ready means that vault’s vector database is open with semantic search enabled in it, not merely that the model files exist on the machine. Enabling semantic search in one vault leaves the modes grayed out in every other vault until you enable it there too. The disabled buttons carry the tooltip Enable semantic search to use this mode, and the search page shows a banner reading Semantic search is off with an Enable button. The same control lives in Settings under the Semantic search section.
Semantic search is off by default. In Settings, the Semantic search section shows a Provider choice above its Enable button, because the first decision is where your notes get embedded. The Enable button in the search page banner starts straight away with the provider Settings last recorded, which is OpenAI text-embedding-3-small until you choose otherwise.
Semantic search runs on the desktop app and the jotura CLI. The phone app does not offer it.
Choosing a provider
| Provider | Where embedding happens | Quality | What it needs |
|---|---|---|---|
| OpenAI (recommended, preselected) | OpenAI’s embedding API, over the network | Higher, and noticeably better on non-English notes and long passages | Your own OpenAI API key. Note text and search queries are sent to OpenAI, and OpenAI bills your account for the tokens |
| Local | A small model on your machine | Good on English notes; the model is English-only | A one-time 34 MB model download. No key, no account, and nothing is sent anywhere |
Settings preselects OpenAI at text-embedding-3-small, 1536 dimensions. If no key is saved yet, add one in the API key row first: with OpenAI selected and no key, the Enable button in Settings is grayed out with the helper Add your OpenAI API key to continue, and the search page’s Enable stops with add an OpenAI API key first. If you pick Local, Settings never asks for a key and nothing is sent to OpenAI.
Models and sizes
The OpenAI models accept a dimension count, and the index stores 4 bytes per dimension per chunk, so the size you pick sets both ranking quality and disk use.
| Provider | Model | Sizes | Default | Storage per chunk | Guidance |
|---|---|---|---|---|---|
| OpenAI | text-embedding-3-small |
512, 1024, 1536 | 1536, recommended | 2 KB, 4 KB, about 6 KB | The right choice for most vaults. The smaller sizes save disk at a small cost in ranking quality |
| OpenAI | text-embedding-3-large |
3072 | 3072 | about 12 KB | The highest quality at full size, at about double the storage of the small model and a higher per-token price. |
| Local | bge-small-en-v1.5-int8 |
384 | 384 | 1.5 KB | Fixed size. Runs on your CPU or GPU with no network |
The Index row in Settings shows the chunk count, so multiplying it by the per-chunk figure gives the size of the index. Quality differences between sizes of one model are modest; the bigger step is from local to OpenAI.
Enabling with OpenAI
- It checks that a key is available, from the API key row or from
OPENAI_API_KEY, and stops withadd an OpenAI API key firstif not. - It records your choice of provider, model, and size in the app’s settings.
- It opens the vector store at
<vault>/.jotura/vectors.db, registers the provider, model name, and dimension count, and marks semantic search enabled for that vault. - It starts a background worker that embeds every note in that vault through OpenAI’s API, in parallel requests of 64 to 512 chunks each (never more than 100,000 tokens in one request), starting at 4 requests at a time and rising to 8 while OpenAI keeps accepting them, and queues a full reconcile so nothing is missed.
There is no model download. The first pass sends every chunk of every note to OpenAI, which is the one moment the cost is proportional to the whole vault; after that only changed notes are sent.
Enabling with the local provider
- It downloads two files,
model.onnxandtokenizer.json, fromhttps://releases.jotura.io/models/bge-small-en-v1.5-int8/v1/. The settings helper quotes the size as about 34 MB. Each file’s SHA-256 is verified after download. - It records your choice in the app’s settings, opens the vector store at
<vault>/.jotura/vectors.db, registers the model name, version, and 384-dimension size, and marks semantic search enabled for that vault. - It starts a background worker that embeds every note in that vault, and queues a full reconcile so nothing is missed.
Only step 1 is shared across vaults. The model files land in your config directory rather than in the vault, so a second vault reuses the same download and skips straight to building its own index.
| Platform | Model directory |
|---|---|
| Windows | %APPDATA%\jotura\models\bge-small-en-v1.5-int8\v1\ |
| macOS | ~/Library/Application Support/jotura/models/bge-small-en-v1.5-int8/v1/ |
| Linux | ~/.config/jotura/models/bge-small-en-v1.5-int8/v1/ |
Download progress appears as a percentage both in Settings and as a progress bar on the search page.
The API key
The key is yours, created in your OpenAI account, and Jotura never sees it on its own servers. In the app it is masked from the first keystroke. On Save the app checks the format first, so a value that does not start with sk- or is shorter than 20 characters is rejected before anything leaves the machine, then sends one single-input embedding request to OpenAI to verify the key, and only then writes it to disk. Afterwards the row shows only the last four characters, and you can Replace or Remove it.
| Platform | Key file |
|---|---|
| Windows | %LOCALAPPDATA%\jotura\openai.json |
| macOS | ~/Library/Application Support/jotura/openai.json |
| Linux | ~/.config/jotura/openai.json |
On macOS and Linux the file is written with owner-only permissions (0600, inside a 0700 directory), so other accounts on the machine cannot read it. On Windows it is encrypted with your Windows account (DPAPI), so only you, signed in to this machine, can open it; setting the environment variable JOTURA_SECRET_STORE=plain keeps the older plaintext file in %APPDATA%\jotura instead, for a machine where an SSH session and the desktop need the same key. It is shared with the jotura CLI, which reads the same file. In a vault that syncs, an encrypted copy is also written into the vault so your other devices receive it, as described under Shared across devices. The key is never included in the diagnostics bundle and is redacted from every error message and log line. An OPENAI_API_KEY environment variable takes precedence over the file for both the app and the CLI. OPENAI_BASE_URL points both at a compatible endpoint other than https://api.openai.com/v1, for a proxy.
Switching provider, model, or size
While semantic search is enabled, changing the provider, model, or size in Settings re-embeds the whole vault: the existing index is deleted and rebuilt with the new choice. You confirm once. The confirmation names what you are switching to, reminds you that every note is re-embedded, and, when the target is OpenAI, that the rebuild sends every chunk to OpenAI under your key and costs money. Until the rebuild finishes, results come from the partial new index and the search page shows the usual indexing notice.
The choice lives in the vault, in Jotura/settings.json, so it applies to this vault on every device that syncs it; see Shared across devices. The provider you last picked is remembered on this machine only as the default Settings preselects the next time you enable semantic search in another vault. A vault whose index was built with a different provider, model, or size than its settings file says re-embeds the next time you open it. Indexes built before providers existed are stamped as local and left exactly as they are until you switch: nothing is ever migrated to OpenAI behind your back.
If the index and the settings disagree and the automatic switch could not run, for example because the key was removed in between, Settings shows Index was built with <old>; settings now say <new> with a Re-embed now button, and the index keeps serving results on the provider that built it in the meantime.
Shared across devices
Semantic search is configured per vault and applies on every device that syncs it. Whether it is on, and which provider, model, and size build the index, are written to Jotura/settings.json at the root of the vault, an ordinary text file that syncs like any note. Change the provider on one device and every device that syncs the vault switches with it; turn it off on one device and it turns off everywhere. One exception: when another device turns on the Local provider, a low-resource machine waits for you, and Settings there reads Off on this device with a Turn on here button. The Status row says as much: Applies to this vault on every device. The confirmation for a switch, a re-index, or a turn-off names every device too.
The index itself is shared as well, so a second device does not embed the vault again. Whichever device changes a note embeds it, then uploads the resulting vectors to an end-to-end encrypted embedding cache beside the sync server’s blob store. The cache is keyed by a hash of the exact text that was embedded and encrypted under your vault’s sync key, so the server holds opaque identifiers and ciphertext and cannot tell which note a vector belongs to. Every other device looks its chunks up in that cache first and only embeds what nobody has embedded yet. In practice a new laptop reaches a full index in minutes, with no OpenAI bill and no hours of local CPU, and a vault whose notes are already indexed on one device backfills the cache in the background so there is something to fetch.
The Index row reports what this is doing. While a change from another device is being applied it reads Re-indexing from your other devices' embeddings. While notes are queued behind vectors that have not arrived yet it reads Waiting for <n> notes to arrive from your other devices. A device waits about 30 seconds, then 5 minutes, then 30 minutes for a vector to appear before embedding the note itself, so a vault edited on a device that is offline for a while still catches up on its own.
The OpenAI key travels the same way. Saving a key in a vault that syncs also writes an encrypted copy to Jotura/provider-keys.json in the vault, sealed under the sync key, and each device that opens the vault with its sync key cached decrypts that copy into its own key file. The API key row says Saved to this vault and shared with your other devices when that is happening and Saved on this device only otherwise. Removing the key on one device removes it everywhere. A device whose sync key is not cached can still search and can still fetch vectors from the cache, but cannot embed; the Index row then reads Add your OpenAI key to search and to embed <n> notes. until a key arrives or is entered.
A vault without sync keeps everything on the one machine, exactly as before: the settings file is still written, but nothing reads it elsewhere, the cache is never contacted, and the key stays in the device file only. The jotura CLI keeps reading the device key file and needs nothing new.
What leaves your machine
| Local | OpenAI | |
|---|---|---|
| Note text | Never | Every chunk of every indexed note, at index time and again whenever a note changes |
| Search queries | Never | Each semantic or smart query, at the moment you run it |
| Your API key | Not used | Sent to OpenAI as the bearer token on each request, and nowhere else |
| Model download | Once, from releases.jotura.io |
None |
| Embeddings | To the Jotura sync server as ciphertext, only in a vault that syncs | The same |
Encrypted sync is unaffected by either choice: notes are still encrypted on your device before upload and the sync server never sees plaintext. The OpenAI path is a separate, opt-in channel from your device straight to OpenAI, subject to OpenAI’s terms for API data. The vectors themselves live in vectors.db on each machine whichever provider produced them; in a vault that syncs they also pass between your devices through the embedding cache, encrypted under your sync key, so the sync server never sees them either.
When embedding fails
| Failure | What happens |
|---|---|
| OpenAI rejects the key (HTTP 401 or 403) | The queue stops, Settings shows OpenAI rejected the API key, and nothing more is sent until you Replace the key. Saving a working key resumes the queue where it stopped |
| OpenAI reports the account is out of quota | The queue stops, Settings shows OpenAI quota exhausted. Check billing on your OpenAI account., and nothing more is sent until you save the key again or restart the app |
| Network error, rate limit (HTTP 429), or an OpenAI outage (5xx) | A rate-limited request is tried up to 6 times and any other failed request up to 4 times, with a random wait between 1 and 60 seconds. A 429 also pauses every request for as long as OpenAI’s Retry-After header asks, never more than 120 seconds, and halves the number of requests sent at once. If it still fails the worker pauses, the Index row reports it, and the queue retries by itself after 30 seconds, then 2 minutes, then every 10 minutes, or as soon as a note changes. No note is lost or skipped |
| One note cannot be embedded (unreadable bytes, an input OpenAI refuses) | That note is parked and counted as skipped, exactly as with the local provider. It is retried when its content changes or on Re-index vault |
Reading the status rows
Once semantic search is on, the Settings section shows its status in rows refreshed every two seconds while the modal is open.
| Row | What it shows |
|---|---|
| Status | The provider that built the index, as OpenAI text-embedding-3-small (1536) or Local bge-small (384), plus, for the local provider only, the compute backend: GPU (CUDA), GPU (DirectML), GPU (CoreML), CPU, or Unknown. Its helper reads Applies to this vault on every device. |
| Index | <n> chunks · <n> pending, plus an estimated time remaining while embedding is running, and · <n> skipped when notes were parked after a failure (unreadable bytes or an embedding error); a parked note is retried as soon as its content changes, or on Re-index vault. The helper switches to Re-indexing from your other devices' embeddings., Waiting for <n> notes to arrive from your other devices., or Add your OpenAI key to search and to embed <n> notes. in the states described under Shared across devices |
If something failed, a Last error row appears with the message and the word Failed.
While the embedding queue is draining, the search page shows one of two notices. With the worker running it reads Still indexing: {n} of {m} remaining · {eta} left; results may be incomplete. With the worker idle but a backlog outstanding it reads Still indexing: {n} notes pending; results may be incomplete. The status bar at the bottom of the sidebar reports the same background work.
Re-indexing and turning it off
| Control | What it does |
|---|---|
Re-index vault |
After the same confirmation as a switch, clears the chunks and rebuilds from scratch with the provider in Settings, on this device and on every device that syncs the vault: this device re-embeds and refills the cache, the others wipe and fetch. On an OpenAI index this sends every chunk to OpenAI again and costs money. Once queued, the row’s helper text is replaced with Re-index queued. Notes are re-embedded in the background. |
Re-embed now |
Appears only when the index was built with a different provider, model, or size than Settings now say. Deletes the index and rebuilds it with the current choice, after the same confirmation as a switch |
Turn off & delete index |
Asks Turn off semantic search for this vault on every device and delete the local index? Your notes are untouched; re-enabling rebuilds the index. and, if you confirm, deletes vectors.db along with its -wal and -shm sidecar files, here and on every device that syncs the vault |
Turning semantic search off never touches your notes, and keyword search keeps working exactly as before. Re-enabling shows the provider choice in Settings again with your last choice preselected, downloads nothing if you pick Local and the model files are still present, and rebuilds the index. Turning it off leaves a saved OpenAI key in place; remove it from the API key row if you no longer want it on disk.
Semantic search needs no Jotura account and no subscription. With the local provider the embedding and the querying both happen on your machine and no note text is uploaded. With OpenAI, note text and queries go to OpenAI under your own key and nowhere else, as laid out in What leaves your machine. See Security for the wider picture.
What gets indexed, and what does not
Both the keyword index and the semantic index walk the same file set, so the rules below apply to every mode.
| Rule | Detail |
|---|---|
| Text files only | Indexing covers a fixed list of text extensions: md, markdown, mdc, txt, rst, org, tex, json, yaml, yml, toml, csv, tsv, xml, html, htm, css, scss, sass, js, mjs, ts, tsx, jsx, py, rb, rs, go, java, kt, swift, c, cpp, h, hpp, sh, bash, zsh, fish, ini, conf, env, sql, log, ps1, bat, canvas, base |
| Size cap | Files larger than 25 MB are skipped |
| Hidden entries | Any file or folder whose name starts with a dot is skipped, so .jotura/, Obsidian’s .obsidian/ and .trash/, and their contents never appear in results |
| Frontmatter | The YAML block is split off before indexing. Only the body is searched |
| Titles | The title shown on a result is the file name with its extension removed |
| Shared notes | Notes inside a share mount under Shared with me/ are indexed like any other note. Sharing itself requires a paid Sync subscription, priced at /pricing |
Because frontmatter is not indexed, a tag written only in frontmatter will not be found by body search. Use the sidebar’s Tags pane for that, or search for the inline #tag form, which is ordinary body text. The Tags pane collects both forms: frontmatter tags: keys and inline #tags in note bodies outside code.
Quick open is the exception to the text-only rule. It lists documents, images, and audio and video files as well, because it matches names rather than content.
Finding text inside PDFs and Word files
When you import a document, Jotura converts it to markdown and saves the result as a sibling note next to the original: report.pdf gets report.pdf.md. That sibling is a normal note on disk, so it is indexed like any other note and it is searchable in keyword, semantic, and smart modes.
What this means in practice:
- Searching for a phrase that appears inside a PDF returns a result titled
report.pdf, because the title is the sibling’s file name with.mdremoved. - The file tree hides the sibling’s own row, so you see one entry per document. Clicking the document row opens the converted text.
- Quick open does show both rows, since it matches file names rather than content.
- If you edit a converted note, Jotura stops regenerating it. Conversion writes a body hash into the note’s frontmatter, and a body that no longer matches that hash is treated as protected and never overwritten.
- A failed conversion still produces a searchable sibling, but its body is a stub rather than the document’s text. When the converter is unavailable the stub reads
The markdown converter is not available yet, so this document could not be indexed., and other failures write a stub naming the reason. So a search can return the document with placeholder text instead of nothing. - Clicking a document row that has no sibling yet converts it on demand. The app announces
Converting {name}…, then opens the result. If the conversion is refused it announces{name} was not converted; its note is user-owned, if it errors it announcesConverting {name} failed, and in both cases nothing opens. - Conversion is skipped rather than run without OS sandboxing unless you turn on
Allow unsandboxed conversionin Settings, and password-protected or corrupt files fail. Settings hasRetry failed conversionsandReconvert stale documentsactions for this.
Full detail on the import and conversion pipeline is in Documents, and the conversion settings are listed in the Settings reference.
Searching from the command line
The jotura CLI gives scripts and agents their own search commands. Full reference: Reading and searching.
The two surfaces do not share a keyword index. jotura search keeps its own keyword index at .jotura/cli-search/, re-indexes only the files that changed since its last search, and never reads .jotura/index/. In a vault with no .jotura/ folder, or with --in or --no-cache, it builds a throwaway in-memory index for that one search instead. The vector database at .jotura/vectors.db is the one index the CLI shares with the app.
| Command | What it does |
|---|---|
jotura search <QUERY> |
Full-text search. --limit defaults to 20 and is clamped to 1 through 100 in semantic and smart modes, but passed through unclamped in keyword mode. --mode is keyword, semantic, or smart and defaults to keyword. --in <SCOPE> restricts results to a path prefix. --no-cache skips the CLI’s cached index for this search |
jotura quick-open <QUERY> |
Fuzzy file-name match, --limit defaults to 20. JSON is [{ "path", "score", "alias" }] |
jotura search-documents <QUERY> |
Searches converted document text and returns the original document paths. --limit defaults to 20. JSON is [{ "documentPath", "mdStorePath", "title", "snippet", "score" }], where mdStorePath is the converted sibling. Hits whose sibling cannot be mapped back to an original document are silently dropped, so a result count below --limit is expected |
jotura grep <PATTERN> |
Regular-expression search across note bodies, with -i, -w, -x, -c, -l, -L, --max-count, --include, and --exclude |
jotura search "recovery phrase" --json
jotura search "how do I pause uploads" --mode smart --limit 10
jotura search "deadline" --in projects/ --json
jotura quick-open standup --json
jotura search-documents "invoice total" --json
jotura grep -i "TODO" --include "projects/**"
The JSON shape of jotura search differs by mode. Keyword results are [{ "path", "title", "snippet", "score" }], while semantic and smart results are [{ "path", "headingPath", "snippet", "score" }]. A script that reads title from a --mode smart result gets nothing back, so branch on the mode you asked for.
Check these differences from the desktop app before you rely on the CLI results.
jotura searchruns keyword mode with whole-word matching on, while the desktop search page runs it with whole word off. The same query can return different results in the two places, and the CLI results are the BM25-ranked ones.--inand--no-cachework in keyword mode only. Combining either with--mode semanticor--mode smartexits with code 9 (InvalidArgs).- The CLI is query-only for semantic search. It embeds your query but never indexes notes, so a note written by the CLI is not semantically searchable until the desktop app indexes it. If semantic search has not been enabled in the app for that vault, or the model or vector database is missing, the command exits with code 13 (
SemanticUnavailable) and the message names the missing prerequisite. - The embedding provider is chosen in the desktop app, and the CLI embeds queries with whichever provider built the index. An OpenAI index needs an API key:
OPENAI_API_KEYin the environment wins, otherwise the CLI reads the key the desktop app saved. Without either, semantic and smart modes exit 13 withsemantic index uses OpenAI embeddings; set OPENAI_API_KEY or add the key in the desktop app (Settings -> Semantic search); if OpenAI rejects the key they exit 13 withOpenAI rejected the API key; update it in the desktop app or OPENAI_API_KEY. - A search with
--inor--no-cacheindexes the whole vault in memory first, so avoid those flags in a tight loop.
Three settings affect which vault, which model, and which key the CLI uses.
| Control | Effect |
|---|---|
--vault <PATH> |
Chooses the vault for this invocation. Resolution order is --vault, then $JOTURA_VAULT, then the desktop app’s last-opened vault. An inaccessible path or a non-directory exits with code 6 (NoVault) |
JOTURA_MODEL_DIR |
Overrides the local embedding model directory, which otherwise defaults to the platform paths in the table above. An empty value falls through to the default |
OPENAI_API_KEY |
The key used to embed queries against an OpenAI index. Takes precedence over the key saved by the desktop app |
Pass --json for machine-readable output on these commands. Errors are JSON on stderr regardless. See Exit codes and JSON output and Documents from the CLI.
Keyboard shortcuts
The global shortcut handler treats Cmd and Ctrl as interchangeable on every platform, and every chord below requires that Alt is not held.
| Action | Mac | Windows and Linux | Where it works |
|---|---|---|---|
| Toggle quick open | Cmd+P | Ctrl+P | Anywhere |
| Open the search page | Cmd+Shift+F | Ctrl+Shift+F | Anywhere |
| Toggle the command palette | Cmd+Shift+P | Ctrl+Shift+P | Anywhere |
| Close the active tab, including the search tab | Cmd+W | Ctrl+W | Anywhere, when a tab is active |
| Toggle the sidebar | Cmd+\ | Ctrl+\ | Anywhere |
| Dismiss | Escape | Escape | Anywhere, see the order below |
Escape follows a strict order and stops at the first match: it closes the command palette, then the Settings modal, then quick open, then the search tab if that tab is active. Nothing here searches inside the open note, and no chord is bound to Cmd/Ctrl+F.
Inside the result lists:
| Key | Quick open | Sidebar search pane | Search page |
|---|---|---|---|
| ArrowDown, ArrowUp | Move the selection, clamped at both ends | Move the selection, clamped at both ends | Not handled, use Tab |
| Enter | Open the selected file | Open the selected result | Activates the focused result button |
| Escape | Close quick open | Blur the input and return to the Files pane | Close the search tab |
The full list of app shortcuts is in Keyboard shortcuts.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| A note you just created is missing from results | The index is still catching up. The sidebar pane and the search page both say so while indexing is in progress |
| Semantic and Smart are grayed out | Semantic search is not enabled for this vault. Enable it in Settings, or from the banner on the search page |
| Semantic and Smart are grayed out in a second vault after you enabled them in the first | The enable flag is per vault. The model download is reused, so enabling the second vault only rebuilds its index |
| Semantic results look thin right after enabling | The embedding queue is still draining. The search page reports how many notes are pending |
Enable stops with add an OpenAI API key first |
OpenAI is the selected provider and no key is saved or set in the environment. Save a key in the API key row, or pick Local |
Settings shows OpenAI rejected the API key |
The key was revoked, expired, or belongs to a project without embeddings access. Replace it; the queue resumes on its own |
| The Index row says embedding is paused | A network error, rate limit, or OpenAI outage. Nothing to do: the worker retries with backoff and resumes when the next note changes |
Settings shows Index was built with <old>; settings now say <new> |
The provider, model, or size changed but this vault’s index has not been rebuilt yet. Click Re-embed now, or switch Settings back |
| A large file never matches | Files over 25 MB are not indexed |
| Quick open cannot find an archive by name | Only recognized extensions are listed, and archives such as zip are not among them. Audio and video formats such as mp3 and mp4 are recognized |
| Text inside a PDF is not found, or the result is a placeholder | The document was not converted, or conversion failed and wrote a stub. Check the conversion status and the retry actions in Settings |
| A frontmatter value is not found | Frontmatter is excluded from the body index by design |
| Cmd/Ctrl+F does nothing | There is no find inside a note. Search the vault instead |
More recovery steps are in Troubleshooting. If you are new to the app, Getting started covers opening a vault and the first index build.