Managing a library’s daily circulation statistics often involves performing the same routine every morning: log in to Koha, run a circulation report, export the results, download the CSV file, and then review the previous day’s activity.
For a busy library, this quickly becomes a repetitive task.
In my case, I wanted Koha to handle this automatically. The objective was simple:
- Run a report automatically every night.
- Generate a CSV containing all books currently issued as of midnight.
- Generate another CSV containing the circulation transactions that took place during the day, including issues, reissues and returns.
- Send both reports automatically to my email.
- Require no manual intervention.
This article explains how I implemented this automation on Koha 24.11.04.000 running on Ubuntu Linux.
The same approach can be adapted to other recent Koha versions and Debian/Ubuntu installations.
What We Are Going to Build
The final workflow looks like this:
Koha
│
│
Saved SQL Reports
│
▼
runreport.pl
│
▼
Linux Cron Job
│
Scheduled at 00:00
│
▼
Generate CSV files
│
▼
Koha SMTP / Mail
│
▼
Email Inbox
At midnight, the server automatically executes the saved Koha reports and sends the resulting CSV files to the configured email address.
The next morning, the reports are already waiting in the inbox.
Why Automate Daily Circulation Reports?
Koha already provides an excellent reporting system. However, running the same report manually every day is unnecessary when the server can do it automatically.
For example, a librarian may want to know:
- How many books are currently issued?
- Which books are currently out of the library?
- How many circulation transactions occurred during the day?
- How many books were issued?
- How many books were reissued or renewed?
- How many books were returned?
These reports are particularly useful for daily monitoring and maintaining circulation statistics.
Instead of manually generating them every morning, we can let Linux execute them on a schedule.
Requirements
Before starting, make sure you have:
- Koha installed on an Ubuntu/Debian server
- Access to the Koha staff interface
- Permission to create Koha SQL reports
- SSH or terminal access to the Koha server
- Koha’s email/SMTP functionality configured and working
- Linux Cron enabled
- A valid email address where the reports should be delivered
My environment was:
Operating System: Ubuntu Server Linux
Koha: 24.11.04.000
Report format: CSV
Email: Gmail SMTP configured on the Postfix server
Scheduler: Linux Cron
Step 1: Create the Required Reports in Koha
The first step is to create the reports that we want to automate.
The important point here is that we do not need to create a new reporting mechanism.
Koha already has a Reports module and provides the runreport.pl command-line utility for executing saved reports.
We can therefore:
- Create and test the report in Koha.
- Save it.
- Identify its Report ID.
- Execute that saved report from the Linux command line.
- Schedule its execution with Cron.
This is much cleaner than creating a separate PHP or Python reporting application.
Report 1: Books Currently Issued
My first report provides the list of books that are currently issued.
The report essentially answers:
“How many books are out of the library at this moment?”
The exact SQL will depend on your library’s requirements and the fields you want to include.
For example, a basic report can retrieve information about currently issued items using Koha’s circulation tables.
A report might include fields such as:
Card Number
Borrower Name
Barcode
Title
Author
Call Number
Issue Date
Due Date
Library
The important part is that the report should return only currently active issues.
After creating the report, save it in Koha.
For example:
Report Name:
Total Books Currently Issued
Koha assigns the saved report an ID.
For my setup, the report ID was:
17
Your Report ID will almost certainly be different.
Report 2: Daily Circulation Transactions
The second report is slightly different.
Instead of showing the current status of loans, I wanted a historical list of transactions that occurred during the day.
This includes transactions such as:
- Issues
- Reissues/Renewals
- Returns
This report allows me to see what actually happened in the library during the previous day.
For example:
Date Card No. Member Name Barcode Title Transaction
2026-09-25 ABC001 John Doe 10001 Book Title Issue
2026-09-25 ABC002 Jane Doe 10002 Another Book Return
2026-09-25 ABC003 Student A 10003 Library Book Renewal
Again, the exact SQL depends on the circulation information you want to capture.
Save this as another Koha report.
For example:
Report Name:
Daily Circulation Transactions
Make a note of its Report ID.
Step 3: Find the Koha runreport.pl Script
Koha provides a command-line script called:
runreport.pl
On my installation, the script was located at:
/usr/share/koha/bin/cronjobs/runreport.pl
You can find it on your server with:
sudo find /usr/share/koha -name runreport.pl 2>/dev/null
You may get:
/usr/share/koha/bin/cronjobs/runreport.pl
The exact location may vary depending on how Koha was installed.
Step 4: Important: Run the Script Through koha-shell
This was an important part of the setup.
Initially, I tried executing the script directly with Perl:
sudo perl /usr/share/koha/bin/cronjobs/runreport.pl ...
Koha returned:
Can't locate Koha/Script.pm in @INC
The problem was not the report itself.
The Koha Perl environment had not been initialized.
Koha’s Perl modules were therefore not available in the normal system Perl environment.
The solution is to execute the command inside the Koha instance’s shell environment.
First, identify your Koha instance:
sudo koha-list
For example:
library
Then enter the Koha shell:
sudo koha-shell library
Replace library with your actual Koha instance name.
Once inside the Koha shell, execute:
perl /usr/share/koha/bin/cronjobs/runreport.pl --help
If the command displays the available options, the Koha environment is working correctly.
This step is particularly important on package-based Debian/Ubuntu Koha installations.
Step 5: Test the Report Manually
Before configuring Cron, always test the command manually.
Do not jump directly to Cron.
That makes troubleshooting unnecessarily painful because then you have three suspects: Koha, Cron and email delivery. Humans apparently enjoy debugging three systems at once.
Start the Koha shell:
sudo koha-shell library
Then execute your report.
A typical command looks like:
perl /usr/share/koha/bin/cronjobs/runreport.pl \
--format=csv \
--csv-header \
17
Here:
--format=csv
requests CSV output.
--csv-header
adds column headings to the CSV.
17
is the saved Koha Report ID.
Replace 17 with your actual report ID.
Step 6: Test Email Delivery
Once the report itself works, test sending it by email.
The command I used was:
perl /usr/share/koha/bin/cronjobs/runreport.pl \
--format=csv \
--csv-header \
--email \
--attachment \
--to="me@rupindersingh.in" \
--from="circulation@librarymail.com" \
--subject="Daily Koha Circulation Report" \
17
Replace the following values:
me@rupindersingh.in
with the email address that should receive the report.
And:
circulation@librarymail.com
with your configured sender address.
The Report ID:
17
must also be replaced with your own report ID.
Understanding the Command
Let’s break it down.
--format=csv
--format=csv
tells Koha to generate the report as CSV.
CSV is particularly useful because it can be opened directly in Microsoft Excel, LibreOffice Calc, Google Sheets and other spreadsheet applications.
--csv-header
--csv-header
adds the report’s column headings to the CSV file.
Without headers, the report would contain data but the meaning of each column would be less obvious.
--email
--email
tells runreport.pl to send the report by email.
--attachment
--attachment
causes the report output to be sent as an attachment.
This is useful when the report contains many records.
Instead of putting thousands of rows into the body of an email, the recipient receives a CSV file.
--to
--to="me@rupindersingh.in"
specifies the recipient.
--from
--from="circulation@librarymail.com"
specifies the sender.
This should normally correspond to an email address configured for your library/Koha installation.
--subject
--subject="Daily Koha Circulation Report"
sets the email subject.
17
Finally:
17
is the Koha saved report ID.
Step 7: Verify the Email
After executing the command, check your inbox.
You should receive an email with a CSV attachment.
For example:
Subject:
Daily Koha Circulation Report
with an attachment similar to:
report.csv
Open the attachment and verify that the contents match the report you generated from the Koha staff interface.
Do this before moving to Cron.
Step 8: Automate the Report with Linux Cron
Once the command works manually, we can automate it.
Linux provides a scheduler called Cron.
Cron can execute commands automatically at specified times.
For example:
00:00 every day
can be represented as:
0 0 * * *
Step 9: Create a Shell Script
Although it is possible to put the entire Koha command directly into Cron, I recommend creating a small shell script.
This makes the setup easier to maintain and troubleshoot.
Create a directory:
sudo mkdir -p /opt/koha-automation
Create the script:
sudo nano /opt/koha-automation/daily-circulation-report.sh
Add:
#!/bin/bash
REPORT_ID="17"
TO_EMAIL="me@rupindersingh.in"
FROM_EMAIL="circulation@librarymail.com"
RUNREPORT="/usr/share/koha/bin/cronjobs/runreport.pl"
LOG_FILE="/var/log/koha-daily-circulation.log"
echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting Koha report" >> "$LOG_FILE"
perl "$RUNREPORT" \
--format=csv \
--csv-header \
--email \
--attachment \
--to="$TO_EMAIL" \
--from="$FROM_EMAIL" \
--subject="Daily Koha Circulation Report" \
"$REPORT_ID" \
>> "$LOG_FILE" 2>&1
STATUS=$?
echo "$(date '+%Y-%m-%d %H:%M:%S') - Report finished with exit code $STATUS" >> "$LOG_FILE"
Save the file.
Make it executable:
sudo chmod +x /opt/koha-automation/daily-circulation-report.sh
Step 10: Test the Shell Script
Run:
sudo koha-shell library
However, there is an important consideration here.
Because koha-shell initializes the Koha environment, your Cron execution also needs to run within that environment.
One reliable approach is to have the Cron job invoke the script through koha-shell.
For example, if your instance name is:
library
you can run:
sudo koha-shell library -- /opt/koha-automation/daily-circulation-report.sh
Depending on the exact koha-shell behavior in your Koha packaging, you may instead use the shell wrapper/environment mechanism provided by your installation.
The key principle is:
The Cron process must execute
runreport.plwith the Koha Perl environment initialized.
This is why simply copying the successful interactive command into a root Cron job can sometimes result in the same Koha/Script.pm error returning.
Step 11: Configure Cron
Edit the root user’s Cron table:
sudo crontab -e
For execution at midnight every day:
0 0 * * * sudo koha-shell library -- /opt/koha-automation/daily-circulation-report.sh
Replace:
library
with your actual Koha instance name.
If the Cron entry itself is already running as root, sudo inside the Cron command may not be necessary.
For example:
0 0 * * * koha-shell library -- /opt/koha-automation/daily-circulation-report.sh
The exact invocation should match the koha-shell syntax supported by your installed Koha package.
Step 12: Verify the Cron Job
Check the root Cron table:
sudo crontab -l
You should see your scheduled job.
For example:
0 0 * * * koha-shell library -- /opt/koha-automation/daily-circulation-report.sh
Step 13: Verify the Server Time
Cron uses the server’s system time.
Check it with:
timedatectl
For an Indian server, I recommend ensuring that the timezone is:
Asia/Kolkata
If necessary:
sudo timedatectl set-timezone Asia/Kolkata
Then verify:
date
This prevents a surprisingly common problem where someone schedules something for midnight and the server cheerfully interprets midnight as UTC.
Automating Multiple Reports
In my setup, I wanted two different reports.
Report 1
Current circulation status
This provides the list of all books currently issued as of midnight.
Report 2
Daily circulation transactions
This provides the transactions that occurred during the day, including:
- Issues
- Reissues/Renewals
- Returns
Both reports are sent automatically by email.
The important thing is that they are separate saved Koha reports.
This makes the reporting system much easier to maintain because each report has a single purpose.
Running Two Reports Automatically
You can execute multiple reports from the same automation script.
For example:
REPORT_CURRENT="17"
REPORT_DAILY="18"
Then execute the reports separately.
For example:
perl "$RUNREPORT" \
--format=csv \
--csv-header \
--email \
--attachment \
--to="$TO_EMAIL" \
--from="$FROM_EMAIL" \
--subject="Current Books Issued" \
"$REPORT_CURRENT"
and:
perl "$RUNREPORT" \
--format=csv \
--csv-header \
--email \
--attachment \
--to="$TO_EMAIL" \
--from="$FROM_EMAIL" \
--subject="Daily Circulation Transactions" \
"$REPORT_DAILY"
This gives you two separate emails and two separate CSV attachments.
Alternatively, your Koha version and configuration may allow multiple reports to be handled through a single invocation. I prefer keeping separate reports and clearly named email subjects because it makes troubleshooting and archiving easier.
A Better Naming Convention
If you receive reports every day, meaningful email subjects become very useful.
For example:
Koha - Current Books Issued - 25 Sep 2026
and:
Koha - Daily Circulation - 25 Sep 2026
You can generate the date dynamically in your shell script.
For example:
REPORT_DATE=$(date '+%d-%b-%Y')
Then:
--subject="Koha - Daily Circulation - $REPORT_DATE"
The resulting email will contain:
Koha - Daily Circulation - 25-Sep-2026
This makes searching your mailbox considerably easier.
Logging
It is a good idea to maintain a log file.
For example:
/var/log/koha-daily-circulation.log
You can check it using:
sudo tail -50 /var/log/koha-daily-circulation.log
For live monitoring:
sudo tail -f /var/log/koha-daily-circulation.log
You can also check whether Cron actually executed the command.
On Ubuntu:
sudo grep CRON /var/log/syslog | tail -20
If you use Postfix for email delivery, check:
sudo tail -f /var/log/mail.log
This is particularly useful when the report appears to have run but the email does not arrive.
Troubleshooting
1. Can't locate Koha/Script.pm
If you see:
Can't locate Koha/Script.pm in @INC
do not immediately reinstall Koha.
The first thing to check is whether you are running the report inside the Koha environment.
Use:
sudo koha-shell YOUR_INSTANCE
Then execute:
perl /usr/share/koha/bin/cronjobs/runreport.pl ...
In my case, this solved the problem.
2. The Report Runs but No Email Arrives
First check the mail system.
If using Postfix:
sudo tail -f /var/log/mail.log
Also verify that the sender address is valid:
circulation@librarymail.com
and that your SMTP configuration is functioning correctly.
You can also test Koha’s normal email notifications to make sure the problem isn’t specific to runreport.pl.
3. Cron Runs but the Report Does Not
Check:
sudo grep CRON /var/log/syslog | tail -30
Then check your custom report log:
sudo tail -50 /var/log/koha-daily-circulation.log
Cron has a very particular talent for doing exactly what you told it to do rather than what you meant.
4. The Report Works Manually but Not from Cron
This is usually an environment problem.
Your interactive shell may have Koha’s environment configured, while Cron has a much smaller environment.
That is why the report should be tested in the same environment in which Cron will execute it.
In particular, verify:
which perl
and:
sudo koha-shell YOUR_INSTANCE
Then test the report from there.
Security Considerations
The shell script contains an email address, but it should not normally contain your SMTP password if the server is already configured to handle SMTP through Postfix or Koha.
Make sure the script is not writable by ordinary users.
For example:
sudo chown root:root /opt/koha-automation/daily-circulation-report.sh
sudo chmod 750 /opt/koha-automation/daily-circulation-report.sh
Also avoid putting passwords directly into Cron entries.
Why This Approach Is Useful for Libraries
This is more than simply automating one report.
The same principle can be used for many routine library-management tasks.
For example, you can automate:
- Daily circulation reports
- Overdue item reports
- Items with high overdue fines
- Newly cataloged books
- Membership statistics
- Daily accession reports
- Items returned during the previous day
- Lost item reports
- Department-wise circulation
- Branch-wise circulation
- Collection statistics
- Pending holds/reservations
- Acquisition reports
Once a report exists in Koha and can be executed from the command line, Linux Cron can turn it into a scheduled operational report.
My Final Workflow
My automated setup now works like this:
12:00 AM
│
▼
Linux Cron starts
│
▼
Koha environment
│
▼
┌─────────┴─────────┐
│ │
▼ ▼
Current Issues Daily Transactions
│ │
▼ ▼
CSV file CSV file
│ │
└─────────┬─────────┘
│
▼
SMTP
│
▼
Email Inbox
│
▼
Next Morning
I no longer need to manually log in to Koha every morning just to run the same reports.
The server generates the reports automatically during the night, and the CSV files are waiting in my inbox when I start work.
Conclusion
Koha already provides a powerful reporting system, but combining it with Linux’s scheduling capabilities makes it considerably more useful for day-to-day library administration.
The important components are:
Koha Saved Reports
+
runreport.pl
+
koha-shell
+
Linux Cron
+
SMTP
Together, these provide a simple and reliable reporting pipeline.
The biggest lesson from the setup is that runreport.pl should be executed within the Koha environment. Running it directly with the system Perl interpreter can produce errors such as:
Can't locate Koha/Script.pm in @INC
Using koha-shell ensures that Koha’s Perl environment and modules are available.
For libraries already using Koha on Ubuntu or Debian, this is a relatively small automation that can eliminate a repetitive daily task and provide a consistent record of circulation activity.
And, as usual, the computer is perfectly happy to do the boring work at midnight while the librarian gets to do something more useful the next morning.


