sejder1 downloadsSpaced-repetition flashcards from simple Question::Answer lines, scheduled with FSRS. All review data stays out of your notes.
Spaced-repetition flashcards for Obsidian that stay out of your notes.
Write cards as plain Question::Answer lines. Retain schedules them with FSRS, the algorithm modern Anki uses. All review data lives in the plugin's own data file, so your notes never get scheduling comments or IDs appended to them.

![]() |
![]() |
| Review with LaTeX, links and interval previews | Stats: history, retention, upcoming reviews |
#flashcards/languages/spanish and every card under it goes into that deck. Nested tags become nested decks.

Put the flashcard tag on a heading, then write one card per line with :: between question and answer:
## Vocabulary #flashcards/languages/spanish
la biblioteca::the library
madrugar::to get up early
## Mechanics #flashcards/science/physics
Newton's second law?::$F = ma$
Period of a simple pendulum?::$T = 2\pi\sqrt{L/g}$
#flashcards on its own puts cards in a deck called default.$$ math blocks are ignored, and so are :: inside inline code.key:: value fields are safe.- question::answer.The tag, the separator and the scheduling options are under Settings → Retain, and they also show up in Obsidian's settings search.
Review state is stored in .obsidian/plugins/retain/data.json, keyed by a hash of each card's question:
| You do this | Retain does this |
|---|---|
| Fix a typo in a question | Matches the edited card to the old one and keeps its history |
| Rewrite a question completely | Treats it as a new card. The old card's history is kept for 30 days in case it comes back |
| Edit via the ✏️ button in a review | Moves the history over directly, however much the question changed |
| Rename or move a note, or move a card to another note | Keeps the history |
| Delete a card | Keeps its history for 30 days (in case you undo), then removes it |
The data file syncs with the rest of your .obsidian folder (Obsidian Sync, iCloud, Nextcloud, git…).
Retain makes no network requests. To build the card index, it lists the markdown files in your vault and reads only the notes that carry your flashcard tag, using Obsidian's metadata cache to tell them apart. Nothing leaves your device.
| Command | |
|---|---|
| Review flashcards | Opens the deck list (also on the ribbon icon and the status bar) |
| Review flashcards in current note | Starts a review session for the open note |
| Open stats | |
| Rescan vault | Rebuilds the card index |
In a review: Space/Enter shows the answer, then grades Good · 1–4 grade · U or Ctrl/Cmd+Z undo · E edit · O open in note · Esc close.
| Setting | Default |
|---|---|
| Flashcard tag | #flashcards |
| Separator | :: |
| New cards per day (per deck) | 20 |
| Target retention | 0.90 |
| Maximum interval | 36500 days |
| Fuzz intervals | on |
| Show interval previews | on |
From Obsidian: Settings → Community plugins → Browse → search for "Retain" (once it's accepted into the community list).
With BRAT: add Sejder/retain.
Manually: download main.js, manifest.json and styles.css from the latest release into <vault>/.obsidian/plugins/retain/, then enable the plugin under Community plugins.
Requires Obsidian 1.13 or later.
GitHub Actions builds release files from the tagged source and signs them with build provenance attestations. To check a download:
gh attestation verify main.js --repo Sejder/retain
The repo includes a devenv environment. Entering the directory activates it and installs the dependencies. Without devenv, any Node 22 works.
npm run dev # rebuild on change and copy into $VAULT/.obsidian/plugins/retain
npm test # unit tests (parser, card store, queue)
npm run build # type-check + production bundle
To release, bump the version in manifest.json, package.json and versions.json, then push a matching tag (e.g. git tag 0.2.0 && git push --tags). The release workflow tests, builds, attests and publishes it.
Set VAULT to your test vault, for example in a devenv.local.nix containing { env.VAULT = "/path/to/vault"; }. That file is gitignored. With VAULT set, npm test also parses your vault as a sanity check.
The screenshots come from the demo vault in demo/vault. To regenerate them, run Obsidian on a virtual display:
npm run build && npm run demo:seed && npm run demo:deploy
Xvfb :97 -screen 0 1600x1000x24 & DISPLAY=:97 node demo/screenshots.mjs docs-raw
The script also checks the UI: exactly one close button, nothing overflowing, undo and edit working, and no console errors.