Project Overview
Org-srs embeds a spaced-repetition system directly into Org mode, letting you review flashcards, clozes and other item types without leaving Emacs. It adapts to your workflow, supports multiple algorithms (including FSRS), and scales from single-file study notes to large, tagged collections.
Value Proposition
- Keep reviews and notes side by side in Org buffers
- Leverage Org’s outlining, tagging and metadata for fine-grained scheduling
- Customize review units (headlines, list items, blocks) and algorithms
- Automate batch operations: bulk item creation, export/import, review statistics
- Extend with Emacs Lisp hooks and Org properties
Feature Summary
- Review item types:
- Flashcards (question ↔ answer pairs)
- Cloze deletions (mask spans within text)
- Custom units via
org-srs-define-review-unit
- Algorithms:
- Built-in SM-2 and SuperMemo variants
- FSRS (Fast, Self-Adjusting) integration
- Hook into other schedulers
- Workflow commands:
org-srs-review-start
to run a sessionorg-srs-item-create
/ DWIM variants for on-the-fly item insertion- Bulk updates (
org-srs-cloze-update
,org-srs-update-all
)
- Reporting and export:
- Session logs and performance charts
- Export to Anki, CSV, JSON
Licensing
Org-srs is distributed under the GNU General Public License version 3.0 (GPLv3). You may freely use, modify and redistribute under the following conditions:
- Include the full text of GPLv3 in
LICENSE
at the repository root - Preserve license headers in all source files
- Disclose source when distributing binaries (or provide a written offer)
For full terms, see LICENSE
or https://www.gnu.org/licenses/gpl-3.0.html.
Supported Versions
- Emacs 26.1 or later
- Org mode 9.3 or later
Ensure your configuration loads Org-srs after Org-mode:
(use-package org-srs
:after org
:custom
(org-srs-directory "~/org/srs/")
(org-srs-review-method 'fsrs)
:bind (("C-c r s" . org-srs-review-start)))
## Getting Started
Get Org-srs up and running in three steps: install, configure, then run your first review.
### Installation
Install from MELPA or Git:
• MELPA
1. M-x package-refresh-contents
2. M-x package-install RET org-srs RET
• use-package
```elisp
(use-package org-srs
:ensure t
:after org)
• Manual
Clone the repo and add to your load-path
:
(add-to-list 'load-path "~/path/to/org-srs")
(require 'org-srs)
Basic Configuration
By default Org-srs scans org-agenda-files
. You can override:
;; Directory holding your SRS notes
(setq org-srs-directory "~/org/srs/")
;; Files to scan for SRS items
(setq org-srs-files
(directory-files-recursively org-srs-directory "\\.org$"))
;; Persist learning data
(setq org-srs-data-file
(expand-file-name "srs-data.el" org-srs-directory))
;; Enable Org-srs in Org buffers
(add-hook 'org-mode-hook #'org-srs-mode)
;; Keybindings for rating during review
(with-eval-after-load 'org-srs
(define-key org-srs-review-mode-map (kbd "e") #'org-srs-review-rate-easy)
(define-key org-srs-review-mode-map (kbd "g") #'org-srs-review-rate-good)
(define-key org-srs-review-mode-map (kbd "h") #'org-srs-review-rate-hard)
(define-key org-srs-review-mode-map (kbd "a") #'org-srs-review-rate-again))
Creating Your First SRS Item
You can author flashcards, clozes or custom units inline.
Flashcard Example
Add this to ~/org/srs/flashcards.org
:
* Vocabulary: Epitome
:PROPERTIES:
:SRS_TYPE: card
:END:
** Front
What word describes “a perfect example”?
** Back
Epitome
Or interactively:
- Place point on an Org heading.
- M-x org-srs-item-create RET card RET
- Fill any prompted properties (e.g. tags, initial schedule).
Cloze Example
Within any Org heading:
Here’s Newton’s second law: {{e5f3a1b}{F = m · a}{force}}
To create/remove cloze at point:
- M-x org-srs-item-cloze-dwim
- Regenerate all IDs after big edits: M-x org-srs-item-cloze-update
Running Your First Review
- Ensure your SRS files are saved and included in
org-srs-files
ororg-agenda-files
. - M-x org-srs-review-start
- Org-srs opens the first item, narrows to the question and hides the answer.
- Rate your recall:
• e → Easy
• g → Good
• h → Hard
• a → Again - Org-srs reveals the answer, updates the schedule, and proceeds to the next item.
- At session end, you see a summary of items reviewed and next review dates.
First Review Workflow
- Open Emacs and switch to an Org buffer in
~/org/srs/
. - M-x org-srs-review-start
- Answer each prompt using the keys above.
- After the session, check your
srs-data.el
for updated intervals and next-due dates.
You’re now set to build and review your knowledge base entirely within Org-mode. Happy learning!
User Guide
This guide covers common workflows in Org-srs: authoring items, embedding and prettifying content, running review sessions, logging progress, querying items, and visualizing statistics.
1. Authoring SRS Items
1.1 Creating Flashcard Items
Require the card module and insert a new card under an Org heading:
(require 'org-srs-item-card)
;; Interactive: prompt for front/back
M-x org-srs-item-card-create RET
;; Programmatic example:
(org-srs-item-card-create
:front '(plain . "What is the capital of France?")
:back '(plain . "Paris")
:properties '((Category . "Geography")))
This creates an Org subtree with SRS metadata and card content.
1.2 Creating Cloze Deletions
Use the DWIM command to turn text into a cloze:
(require 'org-srs-item-cloze)
;; Cloze an active region
(call-interactively #'org-srs-item-cloze-dwim)
;; Cloze the word at point
(call-interactively #'org-srs-item-cloze-dwim)
;; Batch-cloze in a table
(let ((org-srs-item-cloze-table-range "@1$1..@>$2"))
(call-interactively #'org-srs-item-cloze-dwim))
Customize hints and ID generation:
(setq org-srs-item-cloze-hint "synonym")
(setq org-srs-item-cloze-identifier
#'org-srs-item-cloze-identifier-number-sequence)
After edits, update cloze metadata:
M-x org-srs-item-cloze-update RET
;; Prefix argument forces full re-ID
2. Embedding and Prettifying
2.1 Overlay Rendering
Enable non-intrusive overlays to hide raw markers:
(require 'org-srs-embed)
(add-hook 'org-mode-hook #'org-srs-embed-overlay-mode)
;; Toggle manually
M-x org-srs-embed-overlay-mode RET
2.2 Embedding Cloze Markers
Insert raw markers around a region:
;; Interactive DWIM wrapper
M-x org-srs-embed-cloze-dwim RET
;; Programmatic:
(org-srs-embed-cloze (point-min) (point-max) "hint")
2.3 Removing Markers
Strip all embedded markers and restore plain text:
;; On the current buffer
(org-srs-embed-uncloze (point-min) (point-max))
3. Conducting Review Sessions
3.1 Starting a Session
Require the review module and launch:
(require 'org-srs-review)
;; Default: reviews due items in current buffer
M-x org-srs-review-start RET
;; Programmatic with custom source & strategy
(org-srs-review-start
:source (org-srs-review-source-dwim)
:strategy (org-srs-review-strategy-due-first))
3.2 Navigation & Ratings
During a session:
- n / SPC: next item
- p : previous
- q : quit session
- 1 (again), 2 (hard), 3 (good), 4 (easy): rate current item
For mouse/touch support:
(require 'org-srs-mouse)
(org-srs-mouse-mode 1)
4. Logging Progress
4.1 Inserting a New Log Entry
Record initial scheduling parameters:
;; Place point in an SRSITEMS drawer
M-x org-srs-log-insert RET
;; Or programmatically
(org-srs-log-insert)
4.2 Updating an Existing Entry
After reviewing, update algorithm fields:
;; Default quality = last rating
(org-srs-log-repeat)
;; Adjust quality
(org-srs-log-repeat :quality 4)
;; Force timestamp to now
(org-srs-log-repeat :timestampp t)
;; Supply explicit timestamp
(org-srs-log-repeat :timestamp "2025-08-06T09:00:00Z" :quality 5)
4.3 Key Bindings Example
Bind logging commands in review mode:
(define-key org-srs-review-mode-map (kbd "i") #'org-srs-log-insert)
(define-key org-srs-review-mode-map (kbd "r") #'org-srs-log-repeat)
5. Querying Review Items
5.1 Building Predicates
Use constructors for common filters:
(require 'org-srs-query)
;; Never reviewed
(setq p-new (org-srs-query-predicate-new))
;; Due now
(setq p-due (org-srs-query-predicate-due))
;; Learned today
(setq p-learn (org-srs-query-predicate-learned))
;; Combine: due and not new
(setq p-combo (org-srs-query-predicate-and p-due (org-srs-query-predicate-not p-new)))
5.2 Running Queries
Collect matching items:
;; In current buffer
(org-srs-query p-combo)
;; In a file
(org-srs-query p-combo "/path/to/notes.org")
;; Across a directory
(org-srs-query p-combo "~/org/")
6. Analyzing Progress
6.1 Review Counts Chart
Show daily review counts as a bar chart:
(require 'org-srs-stats-history)
M-x org-srs-stats-history-reviews RET
;; Lisp:
(org-srs-stats-history-reviews (org-srs-review-source-dwim))
6.2 Distinct Items Chart
Plot unique items reviewed per day:
M-x org-srs-stats-history-review-items RET
6.3 Retention Rates Chart
Visualize retention (no “again” responses):
M-x org-srs-stats-history-retentions RET
6.4 Custom Time Span
Fetch raw history and chart manually:
(cl-multiple-value-bind (dates records)
(org-srs-stats-history (org-srs-review-source-dwim) 30)
(chart-bar-quickie
'vertical "30-Day Reviews" dates "Date"
(mapcar #'length records) "Reviews"))
7. Calculating Review Intervals
Compute next-intervals for ratings:
(require 'org-srs-stats-interval)
;; Single rating
(org-srs-stats-interval :good)
;; Multiple ratings
(org-srs-stats-intervals '(:again :good :easy))
Use these functions to predict scheduling or integrate into custom commands.
Configuration
Org-srs offers multiple layers of tunable options—global variables, inheritable properties, scheduling tweaks, algorithm parameters, and UI adjustments—so you can tailor your spaced-repetition workflow.
Defining Org-srs Custom Properties
You can declare new Org-srs options that support per-directory, per-buffer, per-heading or per-item overrides via #+ATTR_SRS:
. Use org-srs-property-defcustom
to define both a customizable variable and a context-aware accessor.
Macro Signature
(org-srs-property-defcustom NAME
&rest DEFCUSTOM-ARGS)
- NAME: symbol, must start with
org-srs-
- DEFCUSTOM-ARGS: same as
defcustom
, plus::transform
– function to post-process the raw value (defaults toidentity
):inherit
– inheritance behavior for Org:PROPERTY:
overrides (defaults tot
)
Under the hood, this:
- Calls
defcustom
with NAME and your args. - Defines an accessor function
NAME
that reads:- Overrides from
#+ATTR_SRS:
at point - An
org-entry-get
of the uppercase property - The global default
- Overrides from
Example
Declare a “card interval” option (in days):
(org-srs-property-defcustom org-srs-card-interval
:type 'integer
:group 'org-srs
:safe 'integerp
:default 3
:transform #’identity
:inherit t
:documentation
"Default interval (in days) for newly created SRS cards.")
This generates:
- A customizable variable (
M-x customize-group RET org-srs RET
) - An accessor function
(org-srs-card-interval)
Using the Accessor
Global default
Set via Customize or(setq org-srs-card-interval 5)
.Heading/item override
* French vocabulary #+ATTR_SRS: :card-interval 7
(org-srs-card-interval)
returns7
within that heading.Temporary override in code
(org-srs-card-interval 10 (lambda () ;; Inside this lambda, the accessor returns 10 (message "Interval is %d" (org-srs-card-interval))))
For multiple bindings, use:
(org-srs-property-let ((org-srs-card-interval 14) (org-srs-another-option 42)) (schedule-new-cards))
Best Practices
- Name properties semantically:
org-srs-<thing>-<unit>
. - Document each option with
:documentation
. - Use heading-level overrides for fine-grained control.
- Set
:inherit nil
to prevent cascading.
Scheduling Tweaks
Burying Mechanism
Postpone sibling items to avoid reviewing related cards on the same day.
Configuration
;; Enable sibling burying
(setq org-srs-schedule-bury-sibling-items-p t)
;; Postpone interval (default 1 day)
(setq org-srs-schedule-bury-interval '(2 :day))
Hook setup:
(add-hook 'org-srs-review-after-rate-hook
#’org-srs-schedule-bury-update-due-timestamp 40)
Fuzzing Mechanism
Add randomness to due dates to spread review load.
Configuration
(setq org-srs-schedule-fuzz-ranges
'(((1 :day) . 0.2)
((7 :day) . 0.1)
((30 :day). 0.05)))
(setq org-srs-schedule-fuzz-unit '(6 :hour))
;; Custom interval function
(defun my-fuzz-interval (interval &optional unit)
(let ((s (org-srs-time-desc-seconds interval))
(u (org-srs-time-desc-seconds unit)))
(list (+ s (/ u 2)) :sec)))
(setq org-srs-schedule-fuzz-interval #’my-fuzz-interval)
Hook setup:
(add-hook 'org-srs-review-after-rate-hook
#’org-srs-schedule-fuzz-update-due-timestamp 60)
Offsetting Mechanism
Preserve context when learning ahead or reviewing overdue items.
Configuration
;; Always apply learn-ahead offset
(setq org-srs-schedule-offset-learn-ahead-time-p t)
;; Or only when due “today”
(setq org-srs-schedule-offset-learn-ahead-time-p #’org-srs-time-today-p)
Hooks:
(add-hook 'org-srs-review-before-rate-hook
#’org-srs-schedule-offset-save-due-timestamp)
(add-hook 'org-srs-review-after-rate-hook
#’org-srs-schedule-offset-update-due-timestamp 70)
Stepped (Re)learning Mechanism
Emulate Anki’s learning steps for new or failed cards.
Configuration
(setq org-srs-schedule-step-learning-steps '((1 :minute) (10 :minute) (1 :hour)))
(setq org-srs-schedule-step-relearning-steps '((5 :minute) (30 :minute)))
Hook setup:
(add-hook 'org-srs-review-after-rate-hook
#’org-srs-schedule-step-update-due-timestamp 50)
Practical usage:
- Use short units (seconds/minutes) for intensive initial repetition.
- Rebind these variables dynamically for decks or tags via
org-srs-property-let
.
FSRS Parameter Optimization
Automatically tune FSRS scheduler parameters using past review history.
Workflow
- Export review logs to CSV
- Run Python
fsrs-optimizer
- Apply returned parameters as file- or directory-local variables
Key Functions
• org-srs-algorithm-fsrs-optimizer-insert-review-log
• org-srs-algorithm-fsrs-optimizer-start-process
• org-srs-algorithm-fsrs-optimizer-optimize
• Interactive org-srs-algorithm-fsrs-optimize
Code Examples
Programmatic optimization:
(require 'org-srs-algorithm-fsrs-optimizer)
(org-srs-algorithm-fsrs-optimizer-optimize
markers
(lambda (&key weights request-retention maximum-interval &allow-other-keys)
(message "New weights: %S" weights)
(message "Retention target: %.2f" request-retention)
(message "Max interval: %S days" maximum-interval)))
Interactive optimization (M-x org-srs-algorithm-fsrs-optimize
):
- No prefix: current file or Dired directory
C-u
: prompt for file or directory
After completion, Emacs inserts:
#+PROPERTY: SRS_ALGORITHM (fsrs <weights> <request-retention> <max-interval>)
Tips
- Install Python package
fsrs-optimizer
(≥6.0). - Ensure entries have past review logs.
- Customize day-start hour or timezone; Emacs passes these automatically.
- After insertion, use
C-c C-c
to apply local variable changes.
Child Frame Utilities
Provide an API to create, reuse, and manage undecorated “child” frames for pop-ups or overlays.
Core Functions
• org-srs-child-frame
• org-srs-child-frame-p
• org-srs-child-frame-root
• org-srs-child-frames
/ (setf org-srs-child-frames)
Code Examples
Create and show a bottom panel at 30% parent height:
(let ((panel (org-srs-child-frame
'preview-panel
:size 0.3
:position :bottom
:buffer (get-buffer-create "*preview*"))))
(with-selected-frame panel
(erase-buffer)
(insert "Hello from child frame!")
(set-frame-visible panel t)))
Hide or delete a panel:
;; Hide if visible
(let ((panel (org-srs-child-frame 'preview-panel)))
(when (frame-visible-p panel)
(make-frame-invisible panel)))
;; Delete all ‘preview-panel’ frames
(setf (org-srs-child-frames 'preview-panel t) nil)
List all child frames:
(org-srs-child-frames) ; all frames
(org-srs-child-frames 'preview-panel t) ; only ‘preview-panel’
Practical Guidance
- Always use
org-srs-child-frame
for consistent styling and reuse. - Control visibility with
set-frame-visible
/make-frame-invisible
. - Position via
:position
—either:bottom
or(X . Y)
absolute pixels. - Clean up with
(setf (org-srs-child-frames NAME t) nil)
.
Developer Guide
Extend Org-srs by plugging in custom algorithms, query predicates, or review strategies. Each subsection below shows how to hook into the core interfaces and register your own implementations.
Implementing a Custom Spaced-Repetition Algorithm
Define two methods in org-srs-algorithm.el
:
org-srs-algorithm-ensure
normalizes your config into stateorg-srs-algorithm-repeat
advances that state on each review
1. Configure your algorithm
In your init.el:
(setq org-srs-algorithm
'(my-custom-algo :initial-interval 1 :factor 2.5))
2. Define ensure
(require 'org-srs-algorithm)
(cl-defmethod org-srs-algorithm-ensure
((algo (eql my-custom-algo)) &rest args)
"Initialize STATE plist for ‘my-custom-algo’."
(let ((plist (apply #'list :algo algo args)))
;; set defaults
(plist-put plist :interval (or (plist-get plist :initial-interval) 1))
(plist-put plist :factor (or (plist-get plist :factor) 2.0))
plist))
3. Define repeat
(cl-defmethod org-srs-algorithm-repeat
((state plist))
"Advance STATE by multiplying :interval by :factor."
(let* ((interval (plist-get state :interval))
(factor (plist-get state :factor))
(new-int (max 1 (round (* interval factor)))))
(plist-put state :interval new-int)))
4. Test interactively
;; Initialize
(setq my-state (org-srs-algorithm-ensure 'my-custom-algo :initial-interval 1 :factor 2))
;; Advance
(org-srs-algorithm-repeat my-state)
;; => (:algo my-custom-algo :initial-interval 1 :factor 2 :interval 2)
Org-srs will now call your ensure
and repeat
on each review cycle and save the returned state into the entry’s properties.
Creating Custom Query Predicates
Use org-srs-query-predicate
in org-srs-query.el
to build filters, then pass them to any query function.
Predefined forms
• Symbol: 'due
, 'new
, 'reviewed
• List: '(and due (not reviewed))
• Function: any zero-arg predicate returning non-nil on match
Combining predicates
;; due and not suspended
(let ((pred (org-srs-query-predicate '(and due (not suspended)))))
(org-srs-query-buffer pred (current-buffer)))
Custom predicate
(defun my-priority-p (min-pri)
"Return a predicate that matches items with priority > MIN-PRI."
(lambda ()
(> (org-srs-item-get-property org-srs-item-data :priority)
min-pri)))
;; Use in a query
(org-srs-query-directory
'(and due (my-priority-p 10))
"~/org/flashcards/")
All query entry points accept the same forms
(org-srs-query-region '(or new due)
(point-min) (point-max))
(org-srs-query-file '(reviewed) "/path/to/file.org")
(org-srs-query '(and due (not reviewed)))
Defining New Review Strategies
Review strategies live in org-srs-review-strategy.el
. You register a symbol and provide a method that returns a list of items.
1. Register a strategy symbol
Set your strategy name:
(setq org-srs-review-strategy 'due-priority-first)
2. Define the strategy method
(require 'org-srs-review-strategy)
(cl-defmethod org-srs-review-strategy
((strategy (eql due-priority-first)))
"Select due items, sorted by descending priority."
(let ((items (org-srs-query '(due))))
;; org-srs-item-priority returns a number
(cl-sort items #'> :key #'org-srs-item-priority)))
3. Run your strategy
;; Start review using your custom strategy
(org-srs-review-start)
The review engine calls your org-srs-review-strategy
method, presents each item, and updates its state via the configured algorithm.
Practical tips
• Name each symbol uniquely to avoid conflicts.
• Reuse org-srs-query
inside strategies for complex filters.
• Sort with cl-sort
and item properties (e.g. :due
, :priority
).
• Test with M-: (org-srs-review-strategy 'your-symbol)
before full review.
API Reference
Alphabetical list of interactive commands, public functions, variables, and customization groups for quick look-ups.
Interactive Commands
org-srs-add-item
(interactive)
Add the current Org heading as a new SRS item.org-srs-backlog-review
(interactive &optional days)
Review items overdue by more than DAYS (default: 0).org-srs-delete-item
(interactive ITEM-ID)
Remove the item identified by ITEM-ID from the SRS database.org-srs-review
(interactive &optional count)
Conduct a review session for up to COUNT due items (default: all).org-srs-show-stats
(interactive &optional period)
Display statistics over the last PERIOD (days, weeks, or months).
Public Functions
org-srs-calc-next-interval
(difficulty rating &optional algorithm)
→ integer
Compute the next review interval (in days) for a given DIFFICULTY rating (0–5) using ALGORITHM (default:org-srs-default-algo
).org-srs-deserialize-data
(&optional file)
→ plist
Load and return the SRS database from FILE (default:org-srs-db-file
).org-srs-get-due-items
(&optional date)
→ list
Return a list of items scheduled for review on DATE (default: today).org-srs-log-review
(item-id quality &optional timestamp)
Record a review result for ITEM-ID with QUALITY (0–5) at TIMESTAMP (default: now).org-srs-schedule-items
(items &optional start-date)
Assign review dates to ITEMS beginning from START-DATE (default: today).org-srs-serialize-data
(db-data &optional file)
Write DB-DATA (plist) to FILE (default:org-srs-db-file
).org-srs-update-item
(item-id &rest properties)
Modify PROPERTIES of the item identified by ITEM-ID.
Variables
org-srs-db-file (string)
Path to the file storing serialized SRS data.org-srs-default-algo (symbol)
Scheduling algorithm to use by default. Options:fsrs
,sm2
.org-srs-review-buffer (string)
Name of the temporary buffer for review sessions.org-srs-timezone (string)
Timezone identifier used for all date calculations.
Customization Groups
org-srs
Core settings for the Org SRS system.org-srs-algorithms
Parameters for scheduling algorithms (FSRS, SM2).org-srs-display
Appearance and layout options for review buffers.org-srs-logging
Controls logging format, file locations, and verbosity.