Script Runner is a calibre plugin that runs saved Python user scripts when books are added or removed. It also provides named manual actions, a configurable editable workspace, recipes, and a separate toolbar/menu plugin ZIP.
A typical setup keeps the event hook as a small wrapper:
# on_book_added.py
from actions import _on_book_added
def on_book_added(book):
_on_book_added(book)The user-owned actions.py contains the workflow. The following is an example:
# actions.py
from script_lib.goodreads import get_goodreads_metadata
def _on_book_added(book):
book.add_tag("TODO")
# Normalize tags according to Tag Mapper
book.apply_toolbar_tag_mapper()
if not book.has_identifier("isbn"):
book.log.info("ISBN missing; starting extraction for book", book.book_id)
isbn = book.extract_isbn(keep_existing=False)
if isbn:
book.set_identifier("isbn", isbn, overwrite=False)
book.log.info("ISBN added for book", book.book_id, isbn)
else:
book.log.info("No ISBN found for book", book.book_id)
else:
book.log.info("ISBN already present; extraction skipped for book", book.book_id)
if not book.has_identifier("isbn"):
book.log.info("Goodreads lookup skipped: ISBN is missing")
return
match = get_goodreads_metadata(
book,
require_absolute_match=True,
minimum_title_similarity=None,
)
if match is not None:
book.set_identifier("goodreads", match.goodreads_id, overwrite=False)
book.set_title(match.title)The fresh-install hook is intentionally a no-op: installing Script Runner does
not change book metadata. Create actions.py and replace the hook with the
wrapper above when you are ready to enable an automatic workflow.
- Download matching versions of the Script Runner and Script Runner Toolbar ZIPs from the desired GitHub release.
- Optional: install and enable the Extract ISBN plugin from
kiwidude's calibre plugins
if your scripts will call
book.extract_isbn(). - Open calibre > Preferences > Plugins.
- Choose Load plugin from file and select the Script Runner ZIP.
- Choose Load plugin from file again and select the Script Runner Toolbar ZIP.
- Restart calibre.
- Open Preferences > Plugins > Script Runner > Customize plugin to edit the saved Python script.
Add the Script Runner action to a calibre toolbar or menu from Preferences >
Toolbars & menus if calibre does not show it automatically.
The editable script workspace defaults to calibre's configuration directory
under plugins/script_runner/, outside the plugin ZIP. Its location can be
changed at the top of the Customize window. New file, Import file, Rename, and
Delete manage custom .py files; the event-hook and helper bootstrap files are
protected and can be restored from the plugin defaults.
Default workspace:
plugins/script_runner/
|-- on_book_added.py
|-- on_book_removed.py
`-- script_lib/
`-- __init__.py
The ZIP contains the stable engine and only seeds this minimal workspace. User
actions and helpers remain ordinary files outside the ZIP. In particular,
installing or updating the plugin never creates, restores, protects, or changes
actions.py. The editor only writes it when the user explicitly saves an edit.
Users own that file completely, so adding or editing actions does not require a
Script Runner release. Plugin updates are reserved for the editor, calibre
integration, job handling, and the stable script API.
The toolbar plugin listens for books removed through the running calibre GUI
from the current library and calls on_book_removed(removed) once for each
removed database ID. The fresh-install hook is a no-op. You can edit
on_book_removed.py to perform work that only needs the removal identifiers:
def on_book_removed(removed):
removed.log.info(
"Removed book",
removed.book_id,
"from library",
removed.library_id,
)The removed object provides only:
script_api_versionbook_idlibrary_idlog
Calibre sends this event after it has removed the book record from the active
library database, and the event supplies only the removed IDs. Consequently,
this hook cannot access the title, authors, identifiers, comments, formats, or
any other book metadata, and it does not receive book.db or book.api. If a
removal workflow needs metadata, it must save that information before the book
is removed.
The hook runs for both moving a book to calibre's library trash and deleting it permanently. Calibre does not tell the listener which kind of removal occurred.
Optional examples live under recipes/ in the repository and are not included
in either plugin ZIP. The Goodreads recipe provides a manual action that looks
up a book by ISBN, uses exact normalized base-title matching by default, and adds
the goodreads:<bookId> identifier. Its optional fuzzy threshold and metadata
handling are configured in the user's actions.py. The Count Pages recipe uses
that plugin's public API to calculate selected statistics. Each recipe README
lists the helper's workspace-relative import path and the small function to
merge into the user-owned actions.py.
The toolbar action opens the script editor. Its menu provides:
- Configure scripts
- Open workspace folder
- Named
bookactions discovered from workspace scripts - View script log
The Script Runner log viewer updates once per second and follows new entries by default. Scrolling upward turns off Follow latest and preserves the current reading position while more lines arrive. Enable Follow latest again to jump to the newest entry. This viewer is separate from calibre's job-details window.
Named book actions create calibre background jobs visible in calibre's Jobs
UI. Each action runs against the current selected books. Completion remains in
calibre's Jobs UI and the status bar; successful runs do not open a popup.
Different named actions use separate job types, so a short action can run while
another named action is still working. Repeated runs of the same action remain
serialized. Avoid running actions concurrently when they update the same field
on the same books, because the last write may replace the earlier one.
The toolbar menu also discovers top-level functions in root-level workspace
.py files, excluding script_lib/, that can be called with a single book
argument. Create and maintain actions.py yourself as the intended home for
actions you want to run manually on existing selected books. Set a literal
menu_name string inside the function to control the menu label. If no
menu_name is present, the formatted function name becomes the menu label.
Actions appear in the same order as their function definitions in each file.
The menu refreshes from the workspace when those scripts change:
def add_verified_tag(book):
menu_name = "Add VERIFIED tag"
book.add_tag("VERIFIED")book.extract_isbn() is a non-mutating lookup. Scripts explicitly decide
whether to store its return value with book.set_identifier(). Scanning is
delegated to the separately installed Extract ISBN plugin through its
calibre_plugins.extract_isbn.jobs.scan_for_isbn() callable. If that plugin is
missing, disabled, or incompatible, extraction logs a clear error and returns
None.
Script Runner contains only the dependency adapter. It does not package Extract ISBN's scanner implementation.
Both the on_book_added(book) hook and named toolbar actions receive a
BookContext object named book. It provides:
script_api_version(currently(2, 2))book_iddbandapititle,authors,tags,languages,comments,formats,identifiersformat_pathsget_field(name)set_field(name, value)set_title(title)request_plugin_call(plugin_name, method_name, *, args=None, kwargs=None, merge_list_argument=None)(manual toolbar actions only)clear_field(name)add_tag(name)remove_tag(name)apply_toolbar_tag_mapper()set_comments(html)clear_comments()set_languages(*codes)add_language(code)remove_language(code)clear_languages()has_language(code)has_identifier(name)get_identifier(name, default=None)set_identifier(name, value, overwrite=True)remove_identifier(name)has_format(name)is_format(name)has_any_format(*names)is_any_format(*names)get_format_path(name)clear_rating()extract_isbn(keep_existing=True)(requires the optional Extract ISBN plugin from installation step 2; returns an ISBN without changing metadata)log
get_field(name) reads any calibre metadata field by its lookup name.
set_field(name, value) replaces that field's complete value for the current
book. For example:
book.set_field("publisher", "Example Press")
book.set_field("languages", ["sv", "en"])For fields containing multiple values, set_field() does not append or merge:
the supplied list becomes the complete new value. The dedicated language
helpers make incremental changes clearer:
book.set_languages("sv", "en")
book.add_language("fr")
book.remove_language("en")
book.clear_languages()book.apply_toolbar_tag_mapper() applies the rules most recently accepted in
calibre's toolbar Tag Mapper to the current book only. It returns True when
the tags changed and False when there are no toolbar rules or the tags were
already normalized. Call it after any script steps that add or edit tags:
def _on_book_added(book):
book.add_tag("Imported")
book.apply_toolbar_tag_mapper()The toolbar configuration is a snapshot of the last rules accepted in its dialog. Editing a named saved ruleset does not update that snapshot until the ruleset is loaded in the toolbar Tag Mapper and the dialog is accepted.
Language helpers accept two- or three-letter ISO 639 codes such as sv, en,
and fr. Calibre stores and returns their canonical three-letter forms.
Calibre uses the lookup name comments for the description or back-cover text
and allows it to contain HTML. book.comments returns the stored value,
set_comments(html) replaces it exactly, and clear_comments() removes it.
Script Runner does not sanitize, escape, or convert the content:
description = book.comments
book.set_comments("<p>A revised description.</p>")The same API tuple is available as SCRIPT_API_VERSION in every user-script
namespace. Scripts should prefer these BookContext methods over direct access
through book.api; the direct database handles remain available for advanced
workflows.
Warning
Scripts run as local Python code. They can import modules and access the local filesystem, so only save scripts you trust.
Script Runner is distributed under the MIT License.
Thanks to kiwidude for creating and maintaining an excellent collection of calibre plugins, including Extract ISBN.