Skip to main content

Frappe Framework v15 Background Jobs API

What are Background Jobs in Frappe v15?

In Frappe Framework v15, background jobs let you run long or expensive tasks asynchronously instead of blocking HTTP requests. Frappe uses a background job system based on a scheduler and worker processes (internally backed by Python RQ) to execute queued tasks in separate workers. docs.frappe.io+1
Typical examples include sending bulk emails, generating large reports, importing data, or syncing with external services.

At the core of this system are:

  • frappe.enqueue – enqueue a Python function
  • frappe.enqueue_doc – enqueue a controller method on a specific document
  • Queues (short, default, long and custom queues)
  • Worker processes (bench worker)
  • scheduler_events hook and configurable scheduler records

How do you enqueue a background job in Frappe v15?

Basic frappe.enqueue usage

You enqueue a Python method using frappe.enqueue. The method can be passed directly as a function or as a dotted module path string. docs.frappe.io

import frappe
def long_running_job(param1, param2):
# expensive tasks
pass
# Option 1: pass the function object
frappe.enqueue(
long_running_job,
queue="short",
param1="A",
param2="B",
)
# Option 2: pass dotted module path as string
frappe.enqueue(
"app.module.folder.long_running_job",
queue="short",
param1="A",
param2="B",
)

Full frappe.enqueue arguments (v15)

The official docs list the supported parameters like this: docs.frappe.io

frappe.enqueue(
method,                         # function or dotted path (str)
queue="default",                # "short", "default", or "long"
timeout=None,                   # override queue timeout (seconds)
is_async=True,                  # True = run in worker
now=False,                      # True = run immediately in current process
job_name=None,                  # (deprecated – see notes below)
enqueue_after_commit=False,     # enqueue only after DB commit
at_front=False,                 # push job at front of queue
track_job=False,                # track metadata in Background Task DocType
**kwargs,                       # passed to the method
)

job_name deprecation and using job_id instead

In Frappe v15, the migration guide deprecates job_name for deduplication and recommends using job_id together with is_job_enqueued to avoid duplicate jobs. GitHub
Pattern recommended in v15:

from frappe.utils.background_jobs import enqueue, is_job_enqueued
job_id = f"data_import::{self.name}"
if not is_job_enqueued(job_id):
enqueue(
"app.module.do_data_import",
job_id=job_id,
queue="long",
enqueue_after_commit=True,
self_name=self.name,
)

Guideline:

For new code on v15, prefer job_id + is_job_enqueued for deduplication instead of relying on job_name.

How do you enqueue DocType methods with frappe.enqueue_doc?

If you need to run a controller method on a specific document in the background, use frappe.enqueue_doc. docs.frappe.io

import frappe
frappe.enqueue_doc(
doctype="ToDo",
name="TODO-0001",
method="run_reminder", # controller method name
queue="long",
timeout=4000,
param="value",
)

Key fields:

  • doctype: DocType name
  • name: document name
  • method: method on the DocType’s controller (e.g., def run_reminder(self, param): …)
  • Other kwargs (like queue, timeout, param) are passed through to the job

This is ideal when the job’s logic should operate on a specific record and benefit from the DocType’s controller methods.

How do job queues work in Frappe v15?

Built-in queues and default timeouts

Frappe v15 ships with three default queues, configured with default timeouts: docs.frappe.io

Queue Default Timeout
short 300 seconds
default 300 seconds
long 1500 seconds

You select the queue via queue=”short” / “default” / “long” in frappe.enqueue.

You can also override the timeout per job using the timeout parameter.

Guideline:

  • Use short for quick tasks (notifications, small updates)
  • Use default for typical jobs (small imports, simple sync tasks)
  • Use long for heavy reports, big imports, and slow external API calls

How do you define custom queues?

To add custom queues, you configure them in common_site_config.json under the “workers” key: docs.frappe.io

{
"...": "...",
"workers": {
"myqueue": {
"timeout": 5000,
"background_workers": 4
}
}
}

Then you can enqueue to that queue:

frappe.enqueue(
"app.module.heavy_task",
queue="myqueue",
timeout=5000,
)

This lets you isolate heavy or specialised workloads into dedicated queues and workers.

How do background workers run in Frappe v15?

Default worker configuration

By default, Frappe defines three worker processes, one per built-in queue: docs.frappe.io

bench worker –queue short
bench worker –queue default
bench worker –queue long

In production, each of these may be replicated multiple times depending on your background_workers configuration.

Note:

Mapping a worker to a single queue is a convention, not a strict requirement. You can change this pattern based on your deployment needs.

Multi-queue consumption

Workers can consume from more than one queue:

bench worker --queue short,default
bench worker --queue long

This example merges short and default into one worker type and leaves long separate. Useful when you want to reduce overall process count while still prioritising heavier jobs.

Burst mode with –-burst

You can start a temporary worker that processes jobs until the queue becomes empty and then exits: docs.frappe.io

bench worker --queue short --burst

This is handy when:

  • You periodically expect spikes (e.g., nightly batch jobs)
  • You want to scale out workers via cron or a systemd timer
  • You don’t need extra workers running 24/7

How do you schedule recurring background jobs in Frappe v15?

Using scheduler_events hook

To run tasks at fixed intervals, use the scheduler_events hook in your app’s hooks.py: docs.frappe.io+1

# app/hooks.py
scheduler_events = {
"hourly": [
"app.scheduled_tasks.update_database_usage"
],
}
# app/scheduled_tasks.py
import frappe
def update_database_usage():
# your recurring task logic
pass

Important:

After changing any scheduler events in hooks.py, run bench migrate so the scheduler picks up the changes. docs.frappe.io

Available scheduler events

Frappe defines several event keys: docs.frappe.io
hourly, daily, weekly, monthly – run at those intervals

  • hourly_long, daily_long, weekly_long, monthly_long – same, but using the long worker (for heavy tasks)
  • all – runs every 4 minutes (configurable via scheduler_interval in common_site_config.json)
  • cron – arbitrary cron strings parsed by croniter

Example with multiple event types:

scheduler_events = {
"daily": [
"app.scheduled_tasks.manage_recurring_invoices"
],
"daily_long": [
"app.scheduled_tasks.take_backups_daily"
],
"cron": {
"15 18 * * *": [
"app.scheduled_tasks.delete_all_barcodes_for_users"
],
"*/6 * * * *": [
"app.scheduled_tasks.collect_error_snapshots"
],
},
}

What are configurable scheduler events?

In cases where end users must configure the schedule themselves (e.g., admin-selected frequency), Frappe provides:

  • Scheduler Event DocType
  • Scheduled Job Type DocType

This approach does not require scheduler_events in hooks.py. Instead, you create records at runtime. docs.frappe.io

Example (Python):

import frappe
# Create Scheduler Event record
sch_eve = frappe.new_doc("Scheduler Event")
sch_eve.scheduled_against = "Process Payment Reconciliation"
sch_eve.save()
# Link Scheduled Job Type
job = frappe.new_doc("Scheduled Job Type")
job.frequency = "Cron"
job.scheduler_event = sch_eve.name
job.cron_format = "0/5 * * * *" # every five minutes
job.save()

The Scheduled Job Type interval can be adjusted later by users and persists across bench migrate. docs.frappe.io

Note:
Scheduler-triggered jobs run as Administrator, so any documents created without overriding user will be owned by Administrator.

Practical ERPNext / Industry Use Cases

Background jobs are critical in production ERPNext systems across industries:

  • Manufacturing:
    • Auto-generate Material Requests from Sales Orders in the background
    • Schedule capacity planning or MRP runs during off-peak hours
  • Trading / Retail:
    • Bulk invoice printing or PDF generation via background tasks
    • Periodic stock reconciliation checks
  • Services / SaaS:
    • Recurring subscription invoicing (daily_long)
    • Usage-based billing routines via cron scheduler
  • Compliance & Backup:
    • Automated daily backups on daily_long
    • Log aggregation and snapshot collection via cron events

The Background Jobs API allows you to design these workflows without locking user sessions or timing out HTTP requests.

Best Practices for Background Jobs in Frappe v15

  1. Use the right queue
    • short for quick tasks, long for heavy processes.
  2. Always use enqueue_after_commit for DB-dependent logic
    • Ensures job reads committed state and doesn’t fire if the transaction fails. docs.frappe.io+1
  3. Deduplicate repeated jobs with job_id + is_job_enqueued
    • Prevents multiple imports or backups from stacking up. GitHub
  4. Keep job functions small and focused
    • Easier to debug and reason about.
  5. Log and handle exceptions
    • Use try/except and log to Error Log or a custom DocType.
  6. Avoid heavy synchronous operations inside jobs
    • A job should not start its own bench commands or long shell loops unnecessarily.
  7. Monitor worker health
    • Use existing bench commands and system monitoring tools to track worker crashes or Redis issues.

Troubleshooting Background Jobs

“Jobs are not running or stuck in queue”

  • Check workers:
    • ps aux | grep ‘bench worker’
  • Verify Redis is running.
  • Confirm the job’s queue (short, default, long or custom) matches a running worker’s –queue parameter. docs.frappe.io+1

“My scheduled job never fires”

  • Ensure scheduler_events is correctly defined and bench migrate has been run. docs.frappe.io
  • Confirm scheduler is enabled (bench doctor can help).
  • For configurable Scheduler Events, check Scheduler Event and Scheduled Job Type records.

“Too many long-running jobs slowing down system”

  • Move heavy jobs to a dedicated custom queue (myqueue).
  • Add more workers only for that queue via background_workers. docs.frappe.io
  • Consider using hourly_long/daily_long instead of hourly/daily for heavy scheduler tasks.
Click to rate this post!
[Total: 0 Average: 0]