Skip to main content

Using Frappe Utils in Frappe Framework v15

Frappe Framework v15 ships with a rich set of utility functions under the frappe.utils namespace. These helpers simplify common tasks such as date and time handling, formatting values, validating input, generating PDFs, sending emails, and working with Redis cache.

date and time handling, formatting values, validating input, generating PDFs, sending emails, and working with Redis cache. Frappe Docs
These functions are available directly from frappe.utils and nested modules like frappe.utils.data, frappe.utils.pdf, and frappe.utils.logger. The official list is not exhaustive—developers are encouraged to explore the framework codebase for more helpers.

How do I import and use frappe.utils?

You typically import utility functions directly from frappe.utils (or a nested module) into any Python file in your Frappe app:

from frappe.utils import today, now, add_to_date
from frappe.utils import money_in_words, validate_email_address
from frappe.utils.pdf import get_pdf

The functions behave as standard Python callables and can be used inside DocType controllers, whitelisted methods, background jobs, or any other server-side code.

Which date and time helpers are available in frappe.utils?

What is now() in Frappe?

now() returns the current datetime as a string in the format YYYY-MM-DD HH:MM:SS. It is convenient when you need a timestamp that respects the site’s timezone configuration.

from frappe.utils import now
timestamp = now() # e.g. '2021-05-25 06:38:52.242515'

How do I get a date object with getdate()?

getdate(string_date=None) converts a string in YYYY-MM-DD format to a datetime.date instance. If no argument is passed, it returns today’s date. An invalid date string will raise an exception.

from frappe.utils import getdate
today_date = getdate()
specific_date = getdate("2000-03-18")

How do I get today’s date as a string?

today() returns the current date as a string in YYYY-MM-DD format. Frappe Docs

from frappe.utils import today
today_str = today() # '2021-05-25'

How can I add or subtract time from a date in Frappe?

Use add_to_date(date, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, as_string=False, as_datetime=False) to perform date/datetime arithmetic. If date is None, the current datetime is used.

Key behavior:

  • as_string=False → returns a datetime object.
  • as_string=True, as_datetime=False → returns date string.
  • as_string=True, as_datetime=True → returns datetime string.

from datetime import datetime
from frappe.utils import add_to_date
base = datetime.now()
after_10_days = add_to_date(base, days=10, as_string=True)
after_2_months = add_to_date(base, months=2)

How do I calculate date differences?

Frappe provides three related helpers:

  • date_diff(date_2, date_1) → days between two dates.
  • days_diff(date_2, date_1) → similar to date_diff, returns day difference.
  • month_diff(date_2, date_1) → number of months between two dates.

from frappe.utils import today, add_to_date, date_diff, days_diff, month_diff
d1 = today()
d2 = add_to_date(d1, days=60)
days = days_diff(d2, d1)
months = month_diff(d2, d1)

How do I show time ago text like 1 hour ago?

pretty_date(iso_datetime) takes an ISO-formatted datetime string and returns a human-friendly relative representation such as “just now”, “1 hour ago”, or “1 week ago”.

from frappe.utils import pretty_date, now
pretty = pretty_date(now()) # e.g. 'just now'

How do I format a duration stored in seconds?

format_duration(seconds, hide_days=False) converts a duration (in seconds) into a readable string like 2h 46m 40s or 11d 13h 46m 40s. When hide_days=True, days are converted into hours.

from frappe.utils import format_duration
format_duration(50)                       # '50s'
format_duration(10000)                    # '2h 46m 40s'
format_duration(1000000)                  # '11d 13h 46m 40s'
format_duration(1000000, hide_days=True)  # '277h 46m 40s'

How can I format lists, money, and text with Frappe utils?

How do I join list items with “and” or “or”?

comma_and(some_list, add_quotes=True) returns a string such as “Apple, Ball and Cat”. If add_quotes is True, items are wrapped in quotes; otherwise they appear plain. If the input is not a list/tuple, it is returned as-is. There is also comma_or which behaves the same but uses “or” as the conjunction.

from frappe.utils import comma_and
comma_and([1, 2, 3])                        # "'1', '2' and '3'"
comma_and(["Apple", "Ball", "Cat"], False)  # 'Apple, Ball and Cat'

How do I convert amounts into words?

money_in_words(number, main_currency=None, fraction_currency=None) converts a numeric amount into a localized string with currency and fractional unit. If main_currency is not provided, Frappe falls back to default currency settings or INR.

from frappe.utils import money_in_words
money_in_words(900)                        # 'INR Nine Hundred and Fifty Paisa only.'
money_in_words(900.50, 'USD')              # 'USD Nine Hundred and Fifty Centavo only.'
money_in_words(900.50, 'USD', 'Cents')     # 'USD Nine Hundred and Fifty Cents only.'

How do I generate abbreviations/initials for a name?

get_abbr(string, max_len=2) returns initials from a given string. It’s used heavily in Frappe/ERPNext for avatar initials and placeholders.

from frappe.utils import get_abbr
get_abbr("Gavin")                          # 'G'
get_abbr("Coca Cola Company")              # 'CC'
get_abbr("Mohammad Hussain Nagaria", 3)    # 'MHN'

How can I safely mask sensitive strings?

mask_string(input_string, mask_char=”*”, show_first=4, show_last=3) hides the middle portion of a string, showing only configurable leading and trailing characters. This is useful for masking phone numbers, card numbers, or IDs.

from frappe.utils import mask_string
mask_string("1234567890")      # "1234***890"
mask_string("12345")           # "******"

How do I generate random tokens and unique sequences?

  • random_string(length) → returns a random alphanumeric string of given length. Frappe Docs
  • unique(seq) → returns a list with duplicates removed while preserving order.

from frappe.utils import random_string, unique
token = random_string(40)
distinct = unique([1, 2, 3, 1, 1]) # [1, 2, 3]

How do I validate JSON, URLs, email addresses, and phone numbers?

How can I validate a JSON string?

validate_json_string(string) raises frappe.ValidationError if the string is not valid JSON. Wrap calls in a try/except block when validating user input.

import frappe
from frappe.utils import validate_json_string
try:
validate_json_string('{"player": "one", "score": 199}')
except frappe.ValidationError:
frappe.throw("Invalid JSON payload")

How do I check if a URL is valid?

validate_url(txt, throw=False, valid_schemes=None) returns True or False depending on validity. When throw=True, it raises a ValidationError for invalid URLs. You can restrict allowed schemes using valid_schemes.

from frappe.utils import validate_url
validate_url('google')                              # False
validate_url('https://google.com')                  # True
validate_url('https://google.com', throw=True)      # may raise

How do I extract or validate email addresses?

validate_email_address(email_str, throw=False) scans the given string and returns a string containing a single email or a comma-separated list of valid addresses. If no valid email is found and throw=True, frappe.InvalidEmailAddressError is raised; otherwise, an empty string is returned.

from frappe.utils import validate_email_address
single = validate_email_address('rushabh@erpnext.com')
multiple = validate_email_address(
'some text, rushabh@erpnext.com, some other text, faris@erpnext.com'
)
# 'rushabh@erpnext.com, faris@erpnext.com'

How do I validate phone numbers in Frappe?

validate_phone_number(phone_number, throw=False) returns True if the string is a valid phone number. If invalid and throw=True, it raises frappe.InvalidPhoneNumberError.

from frappe.utils import validate_phone_number
validate_phone_number('753858375')                 # True
validate_phone_number('+91-75385837')              # True
validate_phone_number('invalid')                   # False

How can I generate PDFs from HTML using Frappe?

What is get_pdf() and how do I use it?

get_pdf(html, options=None, output=None) converts an HTML string into a PDF. It uses pdfkit and PyPDF2 internally. If output is a PdfFileWriter instance, pages are appended to it; otherwise, the function returns a byte stream of the generated PDF.
The function is typically imported from frappe.utils.pdf:

import frappe
from frappe.utils.pdf import get_pdf
@frappe.whitelist(allow_guest=True)
def generate_invoice():
html = "<h1>Invoice</h1>"
frappe.local.response.filename = "invoice.pdf"
frappe.local.response.filecontent = get_pdf(html)
frappe.local.response.type = "pdf"

This pattern is common when returning a PDF as an HTTP response in ERPNext or custom Frappe apps.

How do I use Redis cache via frappe.cache()?

What does frappe.cache() return?

frappe.cache() returns a Redis connection wrapper (RedisWrapper), which is derived from redis.Redis. You can use it to store and retrieve key–value pairs efficiently, for example for caching computed results or feature flags.

import frappe
cache = frappe.cache()
cache.set("name", "frappe")
value = cache.get("name") # b'frappe'

Best practice: Use simple, namespaced keys (e.g., “my_app:setting:flag”) and avoid storing large payloads unless necessary.

How can I send emails using frappe.sendmail()?

What is the signature of frappe.sendmail()?

The core email helper is:

sendmail(
recipients=[],
sender="",
subject="No Subject",
message="No Message",
as_markdown=False,
template=None,
args=None,
**kwargs
)
``` :contentReference[oaicite:26]{index=26}
- `recipients`: list of email addresses.
- `sender`: explicit sender; defaults to current user or default outgoing account.
- `subject`: email subject.
- `message` / `content`: email body.
- `as_markdown`: converts markdown to HTML.
- `template`: Jinja template under `templates/emails`.
- `args`: context for rendering the template. :contentReference[oaicite:27]{index=27}
### How do I send a templated email?
```python
import frappe
from frappe import _
recipients = ["gavin@erpnext.com", "hussain@erpnext.com"]
frappe.sendmail(
recipients=recipients,
subject=_("Birthday Reminder"),
template="birthday_reminder",
args=dict(
reminder_text="Don't forget to wish them!",
birthday_persons=birthday_persons,
message="Have a great year ahead!",
),
header=_("Birthday Reminder")
)

The Jinja template might live in templates/emails/birthday_reminder.html and can use the values passed in args.

How do I attach files to emails?

You can pass an attachments list to sendmail, where each item is a dict describing a file, commonly with file_url referencing a File document.

frappe.sendmail(
["faris@frappe.io", "hussain@frappe.io"],
message="## hello, *bro*",
attachments=[{"file_url": "/files/hello.png"}],
as_markdown=True,
)

How can I create filtered list links in Frappe Desk?

What does get_filtered_list_url() do?

get_filtered_list_url(doctype, docnames=None) builds a List View URL with filters that show multiple specific documents at once. This is useful after bulk creation when you want users to see all created records together in the Desk List.

from frappe.utils import get_filtered_list_url
url = get_filtered_list_url("Work Order", [
"MFG-WO-2025-00027",
"MFG-WO-2025-00028",
"MFG-WO-2025-00029",
])
# 'http://<site>/app/work-order?name=["in",["MFG-WO-2025-00027",...]]'

What is get_filtered_list_link() for?

get_filtered_list_link(doctype, docnames=None, label=None) returns an HTML <a> tag with a clean link instead of a long URL. It’s perfect for showing a single clickable link in notifications or message dialogs.

from frappe.utils import get_filtered_list_link
link_html = get_filtered_list_link("Work Order", [
"MFG-WO-2025-00027",
"MFG-WO-2025-00028",
])
# e.g. '<a href="http://<site>/app/work-order?name=[...]">Work Order</a>'

When should I use file locks in Frappe utils?

How do I avoid race conditions while writing to files?

Frappe includes a filelock utility in frappe.utils.synchronization. It provides a named lock to synchronize processes, preventing concurrent writes to the same resource.

from frappe.utils.synchronization import filelock
import json
def update_important_config(config, file_obj):
with filelock("config_name"):
json.dump(config, file_obj)

This is especially relevant in multi-worker setups where multiple processes might try to modify the same file or shared resource.

Best practices for using Frappe utils in ERPNext customizations

  • Use high-level helpers first: Prefer today(), getdate(), and add_to_date() over manual datetime parsing to stay consistent with site configuration and timezone handling.
  • Validate user input early: Use validate_url, validate_email_address, validate_phone_number, and validate_json_string close to the source of input to avoid invalid data reaching business logic.
  • Leverage formatting utilities: Apply money_in_words, format_duration, and pretty_date to keep UI output readable and localized.
  • Cache carefully: Use frappe.cache() for data that is expensive to compute and safe to reuse, and remember to invalidate/reset cache when underlying data changes.
  • Keep templates thin: When using frappe.sendmail() with Jinja templates, keep business logic in Python and use templates purely for presentation.
Click to rate this post!
[Total: 0 Average: 0]