A flashcard should always be stored together with the context from which it was derived. (Context Preservation Principle) Flashcards are generated automatically, with Markdown notebooks serving as their sole source of truth. Users only need to focus on building and refining a knowledge base composed of Markdown files.
This application is programmable and automates flashcard generation from Q&A lists in arbitrary formats through JSON intermediate files. It stores performance data in SQLite3 and supports efficient spaced repetition through scheduling based on the FSRS algorithm.
- Content Addressable: cards are identified by the hash of their text. This means a card's progress is reset when the card is edited.
- Efficient: uses FSRS for scheduling reviews, maximizing learning while minimizing time spent reviewing.
- Visibility: Provides a detailed view of the cards' current status and offers clear visualization of the learning schedule.
- Git mirroring: Because hooks are fully programmable, flashcards can, in principle, be generated automatically from any repository. Repositories containing structured Q&A data can therefore be transformed into hashcrds decks.
- In a Markdown editor such as Obsidian, take notes on what you have understood about topics that interest you.
- In question sections, write the information you want to memorize as a list of Q&A pairs. (e.g.
## Question::DeckName {{Q&A}} ---) - Push the Markdown notebooks to a remote repository.
- If the repository mirroring settings are configured correctly, flashcards will be generated automatically.
Warning
This app is still under development.
The following JSON file is a valid hatchards deck:
[
{ "deckName": "Neurophysiology",
"kind": "basic",
"question": "How many neurons are there in the human brain?",
"answer": "~80 billion."
},
{
"deckName": "Neurophysiology",
"kind": "cloze",
"text": "An [agonist] is a ligand that binds to a receptor and [activates it]."
},
{
"deckName": "Neurophysiology",
"kind": "basic",
"question": "How many synapses are there in a human brain?",
"answer": "~100 trillion"
},
{
"deckName": "Neurophysiology",
"kind": "cloze",
"text": "In the nervous system, [chemical] communication happens [between] neurons."
}
]Please use the script of your choice to convert your flashcards into this format. A sample Python script is provided in the scripts directory.
Caution
While the databases for the Rust and Go port versions of hashcards are compatible, you cannot migrate by simply copying the file. The Go port version includes additional management ID columns used by PocketBase, as well as view tables for statistics. To migrate from the Rust version, you need to use a script to insert the data via the API.
# On a running server
$ uv run --with requests import_rust_hashcards.py ~/data/hashcards.db http://127.0.0.1:3000 admin@mail.internal passwordCreate a directory for your flashcards, and add a JOSN file with some cards:
$ mkdir -p cards
$ cd cards
$ cat > example-notebook-1970-01.json << 'EOF'
[
{
"deckName": "Geography",
"kind": "basic",
"question": "What is Coulomb's constant?",
"answer": "The proportionality constant of the electric force."
},
{
"deckName": "Geography",
"kind": "basic",
"question": "What is an object with zero net charge called?",
"answer": "Neutral."
}
]
EOFThe name of the JSON file can be anything you like, but it is generally easier to manage if you create a separate file for each resource in the Markdown notebook from which the Q&A flashcards are generated. Note that the value of the deckName key in the JSON object is used as the playlist name displayed when reviewing the flashcards.
Start drilling:
$ hatchards serveThis opens a web interface at http://localhost:3000 where you can review your
cards. The interface is simple: you read the question, mentally recall the
answer, and click reveal (or press space). Then you grade yourself on how you
did, with one of four choices:
- Forgot (shortcut:
1) - Hard (shortcut:
2) - Good (shortcut:
3) - Easy (shortcut:
4)
Be honest. If you got the answer almost right, press "Forgot". If you mis-grade
something, you can undo (shortcut: u). The session ends when every card has
been graded "Good" or higher. You can end the session prematurely by clicking
"End", this will save your changes.
This section documents the hatchards command line interface.
Start a drilling session.
$ hatchards serve --dir [DIRECTORY]Note
- Note: your progress is not saved until the session ends, either when you run out of cards, or when you click "End".
- Settings such as the card location can be changed using environment variables.
Print collection statistics to standard output.
$ hatchards stats [DIRECTORY]Check the integrity of a collection.
$ hatchards check [DIRECTORY]Manage orphan cards (cards that exist in the database, but not in the collection, i.e., cards that were deleted from the collection).
$ hatchards orphans list [DIRECTORY]
$ hatchards orphans delete [DIRECTORY]Example:
$ hatchards orphans list Cards
04effc035b71692b66a90a622559479516526e7720c41afa22b29562915d58af
059e4e0fd5c3d0ab7ef0cc902cdc402a555ec4152b842fe584109de6c8082ce3
061b8c27e0f437d0c6ae735e829b39cc3bf0ad8218cb16387dcb4271c20b244d
$ hatchards orphans delete Cards
04effc035b71692b66a90a622559479516526e7720c41afa22b29562915d58af
059e4e0fd5c3d0ab7ef0cc902cdc402a555ec4152b842fe584109de6c8082ce3
061b8c27e0f437d0c6ae735e829b39cc3bf0ad8218cb16387dcb4271c20b244d
$ hatchards orphans list Cards
# no output
hatchards serve reads its configuration from environment variables. All are optional; defaults are shown below.
| Variable | Default | Description |
|---|---|---|
SERVER_HOST |
0.0.0.0 |
Host address the HTTP server binds to. |
SERVER_PORT |
3000 |
Port the HTTP server listens on. |
DATA_ROOT |
. |
Path to the collection directory (where your deck .json files live). |
FSRS_TARGET_RECALL |
0.9 |
Target recall probability (0β1) used to compute review intervals. |
FSRS_MIN_INTERVAL |
1.0 |
Minimum interval (in days) between reviews. |
FSRS_MAX_INTERVAL |
256.0 |
Maximum interval (in days) between reviews. |
Example:
SERVER_HOST=0.0.0.0
SERVER_PORT=3000
DATA_ROOT=./cards
FSRS_TARGET_RECALL=0.9
FSRS_MIN_INTERVAL=1.0
FSRS_MAX_INTERVAL=256.0These can be set directly, via .envrc/direnv, or (as in hashacrds.env and compose.yaml) loaded from a file / passed to the Docker container.
hatchards stores card performance data and review history in the SQLite3 database managed by PocketBase.
The cards table has the following schema:
| Column | Type | Description |
|---|---|---|
id |
text primary key |
Randomly generated unique ID (e.g., 'r...'). |
card_hash |
text primary keytext not null default '' |
The hash of the card. Has a UNIQUE index constraint. |
added_at |
text not null |
The timestamp when the card was first added to the database, in timestamp format. |
last_reviewed_at |
texttext not null default '' |
The timestamp when the card was most recently reviewed. nullEmpty string if the card is new. |
stability |
realnumeric not null default 0 |
The card's stability. null0 if the card is new. |
difficulty |
realnumeric not null default 0 |
The card's difficulty. null0 if the card is new. |
interval_raw |
realnumeric not null default 0 |
The FSRS-calculated interval, before rounding and clamping. A real number of days until the next review. null0 if the card is new. |
interval_days |
realnumeric not null default 0 |
The interval as an integer number of days, after rounding and clamping. null0 if the card is new. |
due_date |
texttext not null default '' |
The date when the card is next due, in YYYY-MM-DD format. nullEmpty string if the card is new. |
review_count |
integer not null |
The number of times the card has been reviewed. |
The sessions table has the following schema:
| Column | Type | Description |
|---|---|---|
session_idid |
integer primary keytext primary key default ('r'||lower(hex(randomblob(7)))) |
The ID of the session. |
started_at |
text not nulltext not null default '' |
The timestamp when the session started, in timestamp format. Has an index constraint. |
ended_at |
text not nulltext not null default '' |
The timestamp when the session ended, in timestamp format. |
The reviews table has the following schema:
| Column | Type | Description |
|---|---|---|
id |
text primary key default ('r'||lower(hex(randomblob(7)))) |
The review ID. Randomly generated unique ID. |
review_id |
integer primary key |
(This column was replaced by id) |
session_id |
integer not nulltext not null default '' |
The ID of the session this review was performed in, a foreign key. Has an index constraint. |
card_hash |
text not null default '' |
The hash of the card that was reviewed, a foreign key. Has an index constraint. |
reviewed_at |
text not nulltext not null default '' |
The timestamp when the review was performed (i.e., when the user submitted a grade). |
grade |
text not nulltext not null default '' |
One of forgot, hard, good, or easy. |
stability |
real not nullnumeric not null default 0 |
The card's stability after this review. |
difficulty |
real not nullnumeric not null default 0 |
The card's difficulty after this review. |
interval_raw |
realnumeric not null default 0 |
The FSRS-calculated interval, before rounding and clamping. A real number of days until the next review. null0 if the card is new. |
interval_days |
realnumeric not null default 0 |
The interval as an integer number of days, after rounding and clamping. null0 if the card is new. |
due_date |
text not nulltext not null default '' |
The date, in the user's local time, when the card is next due, in YYYY-MM-DD format. |
Note
- "timestamp format" is
YYYY-MM-DDTHH:MM:SS.MMM, e.g.2025-10-04T17:09:51.517.
- eudoxia0/hashcards
- org-fc
- org-drill
- hascard
- carddown
- My implementation of a personal mnemonic medium
Β© 2026- by asano69. Licensed under the Apache 2.0 license.
Β© 2025β2026 by Fernando Borretti. Licensed under the Apache 2.0 license.
To learn how to write good flashcards, read Effective Spaced Repetition.
=> https://gutenberg.org/cache/epub/47748/pg47748-images.html
=> https://archive.org/details/reasonwhynathist00philrich/page/n5/mode/2up


