
Frappe Core Utils
- 1 installs
- 159 repo stars
- Updated July 8, 2026
- openaec-foundation/erpnext_anthropic_claude_development_skill_package
Covers Frappe utility functions in frappe.utils for date/time, number and money formatting, string operations, validation, and file paths.
About
A reference skill for Frappe utility functions covering date, number, string, and validation helpers in frappe.utils. A developer uses it to reuse built-in helpers instead of stdlib code that breaks timezone or locale handling.
- frappe.utils for date/time, number/money, string, and validation
- Avoids stdlib alternatives that break timezone or locale handling
Frappe Core Utils by the numbers
- 1 all-time installs (skills.sh)
- Ranked #3,836 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill frappe-core-utilsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 159 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/erpnext_anthropic_claude_development_skill_package ↗ |
What it does
Covers Frappe utility functions in frappe.utils for date/time, number and money formatting, string operations, validation, and file paths.
Files
Frappe Utility Functions
Quick Reference: Python
| Need | Function | Returns |
|---|---|---|
| Current date | nowdate() / today() | datetime.date |
| Current datetime | now_datetime() | datetime.datetime |
| Parse date string | getdate(str) | datetime.date |
| Parse datetime string | get_datetime(str) | datetime.datetime |
| Add days | add_days(date, n) | datetime.date |
| Add months | add_months(date, n) | datetime.date |
| Date difference | date_diff(end, start) | int (days) |
| Format for user | format_date(dt) | str (user locale) |
| Relative time | pretty_date(dt) | str ("2 hours ago") |
| Safe float | flt(val, precision) | float |
| Safe int | cint(val) | int |
| Safe string | cstr(val) | str |
| Safe bool | sbool(val) | bool |
| Safe division | safe_div(a, b) | float [v15+] |
| Money format | fmt_money(amt, currency) | str |
| Money in words | money_in_words(amt, cur) | str |
| Strip HTML | strip_html(text) | str |
| List to prose | comma_and(items) | str ("a, b, and c") |
| Validate email | validate_email_address(e) | str or "" |
| Validate URL | validate_url(url) | bool |
| Parse JSON | parse_json(s) | Any |
| Files path | get_files_path(is_private) | str |
| Site path | get_site_path(*parts) | str |
| Unique list | unique(seq) | list |
| Hash | generate_hash(s, length) | str |
ALL imports: from frappe.utils import nowdate, flt, ... in controllers/whitelisted methods.In Server Scripts: Use frappe.utils.nowdate() directly — NO import statements allowed.---
Decision Tree: "Which function do I use?"
Need a date/time value?
├─ Current date → nowdate() or today()
├─ Current datetime → now_datetime()
├─ Parse a string → getdate() or get_datetime()
├─ Add/subtract time → add_days(), add_months(), add_to_date()
├─ Difference → date_diff() (days), month_diff(), time_diff_in_seconds()
├─ Period boundary → get_first_day(), get_last_day(), get_quarter_start()
└─ Display to user → format_date(), format_datetime(), pretty_date()
Need a number?
├─ Convert safely → flt(), cint(), cstr(), sbool()
├─ Round → rounded() (banker's rounding)
├─ Safe divide → safe_div(a, b, default=0) [v15+]
├─ Format money → fmt_money(amount, currency)
└─ Money to words → money_in_words(amount, currency)
Need string processing?
├─ HTML → strip_html(), escape_html(), is_html()
├─ Join list → comma_and(), comma_or(), comma_sep()
├─ Markdown ↔ HTML → to_markdown(), md_to_html()
└─ Mask sensitive → mask_string(input, show_first=4) [v16+]
Need validation?
├─ Email → validate_email_address(email, throw=False)
├─ URL → validate_url(url, valid_schemes=["https"])
├─ Phone → validate_phone_number(phone, throw=False)
├─ JSON → validate_json_string(s)
└─ IBAN → validate_iban(iban) [v16+]
Need file/path?
├─ Public files → get_files_path()
├─ Private files → get_files_path(is_private=True)
├─ Site directory → get_site_path("private", "backups")
├─ Bench root → get_bench_path()
└─ File size → get_file_size(path, format=True)---
Critical Anti-Patterns
NEVER use Python stdlib when frappe.utils exists
| NEVER (stdlib) | ALWAYS (frappe.utils) | Why |
|---|---|---|
datetime.datetime.now() | now_datetime() | Ignores system timezone |
datetime.date.today() | nowdate() | Ignores system timezone |
float(val) | flt(val, precision) | Crashes on None/empty |
int(val) | cint(val) | Crashes on None/empty |
round(val, 2) | rounded(val, 2) | Inconsistent rounding |
val1 / val2 | safe_div(val1, val2) | ZeroDivisionError [v15+] |
json.loads(s) | parse_json(s) | Crashes on None/empty |
json.dumps(obj) | frappe.as_json(obj) | Inconsistent serialization |
"{:,.2f}".format(a) | fmt_money(a, currency) | Ignores locale/currency |
os.path.join(...) | get_site_path(...) | Breaks multi-tenancy |
", ".join(items) | comma_and(items) | No localized "and" |
dt.strftime(fmt) | format_date(dt) | Ignores user preference |
re.sub(r'<.*?>', '', h) | strip_html(h) | Misses edge cases |
Server Script Sandbox
# ❌ NEVER in Server Scripts
from frappe.utils import nowdate, flt
import json
# ✅ ALWAYS in Server Scripts (no imports allowed)
today = frappe.utils.nowdate()
amount = frappe.utils.flt(doc.amount, 2)
data = frappe.parse_json(doc.json_field)---
JavaScript Quick Reference
| Need | Function |
|---|---|
| Escape HTML | frappe.utils.escape_html(txt) |
| HTML to text | frappe.utils.html2text(html) |
| Check if HTML | frappe.utils.is_html(txt) |
| Parse JSON | frappe.utils.parse_json(str) |
| Validate URL | frappe.utils.is_url(txt) |
| Title case | frappe.utils.to_title_case(str) |
| Join with "and" | frappe.utils.comma_and(list) |
| Unique array | frappe.utils.unique(list) |
| Copy clipboard | frappe.utils.copy_to_clipboard(txt) |
| Scroll to element | frappe.utils.scroll_to(el) |
| Is mobile | frappe.utils.is_mobile() |
| Throttle | frappe.utils.throttle(fn, delay) |
| Debounce | frappe.utils.debounce(fn, delay) |
| Format value | frappe.format(value, df, options, doc) |
| Duration display | frappe.utils.get_formatted_duration(secs) |
---
Version Differences
| Function | v14 | v15 | v16 |
|---|---|---|---|
safe_div() | -- | Added | Yes |
duration_to_seconds() | -- | Added | Yes |
guess_date_format() | -- | Added | Yes |
validate_duration_format() | -- | Added | Yes |
mask_string() | -- | -- | Added |
validate_iban() | -- | -- | Added |
validate_name() | -- | -- | Added |
safe_json_loads() | -- | -- | Added |
groupby_metric() | -- | -- | Added |
| Core functions | Yes | Yes | Yes |
---
Reference Files
- Date/Time Functions — Complete date/time API with signatures
- Number & Money Functions — flt, fmt_money, rounding
- String & Validation Functions — HTML, join, validate
- JavaScript Utilities — Client-side frappe.utils.*
- Anti-patterns — stdlib vs frappe.utils comparison
Anti-patterns — frappe.utils
The Golden Rule
NEVER use Python stdlib for operations that frappe.utils provides.
Frappe utilities handle None/empty values, respect timezone settings,
use locale-aware formatting, and work correctly in multi-tenant setups.
---
Date/Time Anti-patterns
Using datetime module directly
# ❌ WRONG — ignores system timezone configuration
import datetime
today = datetime.date.today()
now = datetime.datetime.now()
formatted = now.strftime("%Y-%m-%d")
# ✅ CORRECT — respects system timezone
from frappe.utils import nowdate, now_datetime, get_datetime_str
today = nowdate()
now = now_datetime()
formatted = get_datetime_str(now)Using strftime for user-facing dates
# ❌ WRONG — ignores user's date format preference
display = doc.posting_date.strftime("%d/%m/%Y")
# ✅ CORRECT — uses user's configured format
from frappe.utils import format_date
display = format_date(doc.posting_date)Manual month-end calculation
# ❌ WRONG — reimplements what frappe.utils provides
import calendar
_, last_day = calendar.monthrange(2024, 2)
month_end = datetime.date(2024, 2, last_day)
# ✅ CORRECT
from frappe.utils import get_last_day
month_end = get_last_day("2024-02-15")Manual date arithmetic with relativedelta
# ❌ WRONG — extra dependency, edge cases
from dateutil.relativedelta import relativedelta
next_quarter = date + relativedelta(months=3)
# ✅ CORRECT — handles month-end edge cases
from frappe.utils import add_months
next_quarter = add_months(date, 3)---
Number Anti-patterns
Unsafe type conversion
# ❌ WRONG — crashes on None, empty string, or invalid input
amount = float(doc.amount) # TypeError: float() argument must be a string or a real number, not 'NoneType'
qty = int(doc.qty) # ValueError: invalid literal for int() with base 10: ''
# ✅ CORRECT — handles None, empty, invalid gracefully
from frappe.utils import flt, cint
amount = flt(doc.amount, 2) # 0.0 if None/empty
qty = cint(doc.qty) # 0 if None/emptyUnguarded division
# ❌ WRONG — ZeroDivisionError
percentage = completed / total * 100
# ✅ CORRECT (v15+)
from frappe.utils import safe_div
percentage = safe_div(completed, total) * 100
# ✅ CORRECT (v14)
percentage = (flt(completed) / flt(total) * 100) if total else 0Manual currency formatting
# ❌ WRONG — ignores locale, currency symbol, number format
display = "${:,.2f}".format(amount)
display = f"€ {amount:.2f}"
# ✅ CORRECT — locale-aware, respects system number format
from frappe.utils import fmt_money
display = fmt_money(amount, currency="USD")
display = fmt_money(amount, currency="EUR")Python round() instead of Frappe rounded()
# ❌ WRONG — Python uses different rounding behavior
value = round(2.335, 2) # 2.33 (Python banker's rounding)
# ✅ CORRECT — consistent with Frappe's rounding
from frappe.utils import rounded
value = rounded(2.335, 2) # Frappe's consistent rounding---
String Anti-patterns
Manual HTML stripping
# ❌ WRONG — misses nested tags, attributes, edge cases
import re
clean = re.sub(r'<[^>]+>', '', html_content)
# ✅ CORRECT
from frappe.utils import strip_html
clean = strip_html(html_content)Manual list joining
# ❌ WRONG — no localized "and", poor readability
names = ", ".join(user_list)
# ✅ CORRECT — "Alice, Bob, and Charlie"
from frappe.utils import comma_and
names = comma_and(user_list)Manual JSON handling
# ❌ WRONG — crashes on None/empty/invalid
import json
data = json.loads(doc.json_field) # ValueError on empty
output = json.dumps(result)
# ✅ CORRECT
from frappe.utils import parse_json
data = parse_json(doc.json_field) # handles None/empty
output = frappe.as_json(result)
# v16+: With default fallback
from frappe.utils import safe_json_loads
data = safe_json_loads(doc.json_field, default={})---
File Path Anti-patterns
Manual path construction
# ❌ WRONG — breaks multi-tenancy, hardcodes structure
import os
path = os.path.join("/home/frappe/frappe-bench/sites", site_name, "public", "files")
private = os.path.join("/home/frappe/frappe-bench/sites", site_name, "private", "files")
# ✅ CORRECT — respects bench/site structure
from frappe.utils import get_files_path, get_site_path
path = get_files_path() # public files
private = get_files_path(is_private=True) # private files
custom = get_site_path("private", "backups") # any site subdirectory---
Server Script Anti-patterns
# ❌ NEVER in Server Scripts (RestrictedPython blocks ALL imports)
from frappe.utils import nowdate, flt
import json
import datetime
# ✅ ALWAYS in Server Scripts — use frappe namespace directly
today = frappe.utils.nowdate()
amount = frappe.utils.flt(doc.amount, 2)
data = frappe.parse_json(doc.custom_json)
now = frappe.utils.now_datetime()
formatted = frappe.utils.fmt_money(doc.total, currency=doc.currency)---
Validation Anti-patterns
Manual email regex
# ❌ WRONG — incomplete regex, misses edge cases
import re
if re.match(r'^[\w.-]+@[\w.-]+\.\w+$', email):
pass
# ✅ CORRECT — RFC-compliant, handles multiple emails
from frappe.utils import validate_email_address
valid = validate_email_address(email) # returns email or ""Manual URL validation
# ❌ WRONG
if url.startswith("http"):
pass
# ✅ CORRECT — validates scheme, structure
from frappe.utils import validate_url
if validate_url(url, valid_schemes=["https"]):
pass---
Summary Rule
If you find yourself importingdatetime,json,os.path,re,calendar,
math, orhumanize— STOP. Checkfrappe.utilsfirst.
In 90% of cases, the function you need already exists and handles edge cases better.
Date/Time Functions — frappe.utils
Current Date/Time
from frappe.utils import nowdate, now_datetime, nowtime, today
nowdate() # datetime.date — current date in system timezone
today() # datetime.date — alias for nowdate()
now_datetime() # datetime.datetime — current datetime in system timezone
now() # str "yyyy-mm-dd hh:mm:ss" — current datetime as string
nowtime() # str — current time as formatted stringParsing
from frappe.utils import getdate, get_datetime, get_timedelta, get_timestamp
getdate("2024-03-15") # datetime.date(2024, 3, 15)
getdate(None) # datetime.date.today()
get_datetime("2024-03-15 10:30:00") # datetime.datetime
get_timedelta("5:30:00") # datetime.timedelta
get_timestamp(datetime_obj) # float — Unix timestampFormatting
from frappe.utils import (
get_datetime_str, get_date_str, get_time_str,
format_date, format_time, format_datetime,
format_duration, pretty_date
)
# Internal format (for storage/API)
get_datetime_str(dt) # "2024-03-15 10:30:00"
get_date_str(dt) # "2024-03-15"
get_time_str(dt) # "10:30:00"
# User-facing format (respects user preferences)
format_date(dt) # "15-03-2024" (per user date_format)
format_date(dt, "dd/mm/yyyy") # Force specific format
format_time(dt) # "10:30 AM"
format_datetime(dt) # "15-03-2024 10:30 AM"
# Duration
format_duration(10000) # "2h 46m 40s"
format_duration(100000, hide_days=False) # "1d 3h 46m 40s"
# Relative time
pretty_date("2024-03-15 10:00:00") # "2 hours ago", "just now", etc.Arithmetic
from frappe.utils import add_days, add_months, add_years, add_to_date
add_days("2024-03-15", 10) # datetime.date(2024, 3, 25)
add_days("2024-03-15", -5) # datetime.date(2024, 3, 10)
add_months("2024-01-31", 1) # datetime.date(2024, 2, 29) — handles month-end
add_years("2024-02-29", 1) # datetime.date(2025, 2, 28) — handles leap years
# Full arithmetic with add_to_date
add_to_date("2024-01-15",
years=1, months=2, weeks=1, days=3,
hours=5, minutes=30, seconds=15,
as_string=False, # return datetime object
as_datetime=True # return datetime (not date)
)Differences
from frappe.utils import date_diff, month_diff, time_diff_in_seconds, time_diff_in_hours
date_diff("2024-03-20", "2024-03-15") # 5 (int days)
month_diff("2024-06-15", "2024-01-15") # 5.0 (float months)
time_diff_in_seconds("10:30:00", "10:00:00") # 1800.0
time_diff_in_hours("18:00:00", "09:00:00") # 9.0Period Boundaries
from frappe.utils import (
get_first_day, get_last_day,
get_first_day_of_week, get_last_day_of_week,
get_quarter_start, get_quarter_ending,
get_year_start, get_year_ending,
is_last_day_of_the_month
)
get_first_day("2024-03-15") # datetime.date(2024, 3, 1)
get_last_day("2024-03-15") # datetime.date(2024, 3, 31)
get_first_day_of_week("2024-03-15") # Monday of that week
get_quarter_start("2024-08-15") # datetime.date(2024, 7, 1)
get_quarter_ending("2024-08-15") # datetime.date(2024, 9, 30)
get_year_start("2024-08-15") # datetime.date(2024, 1, 1)
get_year_ending("2024-08-15") # datetime.date(2024, 12, 31)
is_last_day_of_the_month("2024-03-31") # TrueTimezone
from frappe.utils import (
get_system_timezone,
convert_utc_to_system_timezone,
convert_utc_to_timezone,
get_datetime_in_timezone
)
get_system_timezone() # "Asia/Kolkata"
convert_utc_to_system_timezone(utc_dt) # datetime in system TZ
convert_utc_to_timezone(utc_dt, "US/Eastern") # datetime in specified TZ
get_datetime_in_timezone(dt, "Europe/Amsterdam") # convert to specified TZOther Date Utilities
from frappe.utils import (
get_weekdays, get_weekday,
get_timespan_date_range, # v15+
guess_date_format, # v15+
duration_to_seconds, # v15+
get_eta
)
get_weekdays() # ["Monday", "Tuesday", ...]
get_weekday("2024-03-15") # "Friday"
get_timespan_date_range("Last Quarter") # (start_date, end_date)
guess_date_format("15/03/2024") # "dd/mm/yyyy"
duration_to_seconds("2h 30m") # 9000.0
get_eta(start_dt, 0.75) # estimated completion datetimeJavaScript Utilities — frappe.utils
String & Data
// HTML
frappe.utils.escape_html('<script>alert("xss")</script>') // "<script>..."
frappe.utils.unescape_html("<p>") // "<p>"
frappe.utils.html2text("<p>Hello <b>World</b></p>") // "Hello World"
frappe.utils.strip_whitespace(html) // Remove empty paragraphs and excess breaks
frappe.utils.is_html("<p>test</p>") // true
frappe.utils.is_html("plain text") // false
// JSON
frappe.utils.is_json('{"a": 1}') // true
frappe.utils.parse_json('{"a": 1}') // {a: 1} — safe parse with fallback
// URL
frappe.utils.is_url("https://example.com") // true
// String formatting
frappe.utils.to_title_case("hello world") // "Hello World"
frappe.utils.comma_and(["a", "b", "c"]) // "a, b, and c"
frappe.utils.comma_or(["a", "b", "c"]) // "a, b, or c"
frappe.utils.comma_sep(["a", "b"], ", ") // "a, b"
// Array operations
frappe.utils.unique([1, 2, 2, 3]) // [1, 2, 3]
frappe.utils.sort(list, "property_name") // sort by property
frappe.utils.remove_nulls({a: 1, b: null}) // {a: 1}
frappe.utils.intersection([1,2,3], [2,3,4]) // [2, 3]
frappe.utils.arrays_equal([1,2], [1,2]) // trueFormatting & Numbers
// Format any value per fieldtype
frappe.format(1234.56, {fieldtype: "Currency"}) // "$ 1,234.56"
frappe.format("2024-03-15", {fieldtype: "Date"}) // "15-03-2024" (user pref)
// Number formatting
frappe.utils.shorten_number(1234567) // "1.2M"
frappe.utils.shorten_number(1500, 1) // "1.5K"
frappe.utils.get_number_of_decimals(3.14) // 2
// Duration
frappe.utils.get_formatted_duration(3661) // "1h 1m 1s"
frappe.utils.seconds_to_duration(3661) // {hours: 1, minutes: 1, seconds: 1}
frappe.utils.duration_to_seconds("1h 30m") // 5400
// Type validation
frappe.utils.validate_type("42", "number") // 42 (converts)DOM & UI
// Scroll with highlight
frappe.utils.scroll_to(element, {
animate: true,
offset: -50,
callback: () => console.log("scrolled"),
highlight: true
});
// Clipboard
frappe.utils.copy_to_clipboard("text to copy");
// Shows "Copied to clipboard" toast automatically
// Sound
frappe.utils.play_sound("click"); // plays /assets/frappe/sounds/click.mp3
// Responsive breakpoints
frappe.utils.is_xs() // < 576px
frappe.utils.is_sm() // 576-768px
frappe.utils.is_md() // 768-992px
frappe.utils.is_mobile() // < 768px (most commonly used)
frappe.utils.is_mac() // macOS detectionURL & Routing
// Current route
frappe.get_route() // ["Form", "Sales Order", "SO-001"]
frappe.set_route("Form", "Customer", "CUST-001")
// URL parameters
frappe.utils.get_args_dict_from_url("?status=Open&type=Lead")
// {status: "Open", type: "Lead"}
frappe.utils.get_url_from_dict({status: "Open", type: "Lead"})
// "status=Open&type=Lead"
// File links
frappe.utils.get_file_link("/files/image.png")
// Full URL to the fileFile & Media
frappe.utils.is_image_file("photo.jpg") // true
frappe.utils.is_image_file("doc.pdf") // false
frappe.utils.is_video_file("clip.mp4") // true
frappe.utils.file_name_ellipsis("very_long_filename_example.pdf", 20)
// "very_long_fi...le.pdf"
// Image resizing
frappe.utils.resize_image(file, (dataURL) => {
// Use resized image data URL
});Functional Utilities
// Throttle — execute at most once per delay
const throttled = frappe.utils.throttle(() => {
frappe.call({method: "search", args: {q: input.value}});
}, 300);
input.addEventListener("input", throttled);
// Debounce — wait until activity stops
const debounced = frappe.utils.debounce(() => {
save_draft();
}, 1000);
textarea.addEventListener("input", debounced);
// Sleep (Promise-based)
await frappe.utils.sleep(500); // wait 500ms
// Deep equality
frappe.utils.deep_equal({a: 1, b: [2]}, {a: 1, b: [2]}) // trueSecurity (from common.js)
// XSS sanitization
xss_sanitise('<img onerror="alert(1)">') // sanitized string
// Redirect protection
sanitise_redirect("https://evil.com") // blocked
sanitise_redirect("/app/home") // allowed
// HTML stripping
strip_html("<p>Hello <b>World</b></p>") // "Hello World"
// Abbreviations
get_abbr("Coca Cola", 2) // "CC"Number & Money Functions — frappe.utils
Type Conversion
from frappe.utils import flt, cint, cstr, sbool
# flt — safe float conversion with precision
flt(None) # 0.0
flt("") # 0.0
flt("123.456", 2) # 123.46
flt(100) # 100.0
# cint — safe integer conversion
cint(None) # 0
cint("") # 0
cint("42") # 42
cint(3.7) # 3
# cstr — safe string conversion
cstr(None) # ""
cstr(42) # "42"
cstr(["a", "b"]) # "['a', 'b']"
# sbool — flexible boolean ("Yes"/"No"/"1"/"0"/True/False)
sbool("Yes") # True
sbool("No") # False
sbool("1") # True
sbool("0") # False
sbool(True) # True
sbool(None) # FalseRounding & Math
from frappe.utils import rounded, safe_div
# rounded — banker's rounding (consistent with Frappe)
rounded(2.345, 2) # 2.35
rounded(2.335, 2) # 2.34 (banker's rounding)
rounded(100.5, 0) # 100.0
# safe_div — zero-division protection [v15+]
safe_div(100, 3) # 33.333...
safe_div(100, 0) # 0.0 (default)
safe_div(100, 0, default=-1) # -1Money Formatting
from frappe.utils import fmt_money, money_in_words, in_words
# fmt_money — locale-aware currency formatting
fmt_money(2399.50, currency="USD") # "$ 2,399.50"
fmt_money(2399.50, currency="EUR") # "€ 2,399.50"
fmt_money(2399.50, precision=0) # "2,400"
# money_in_words — amount to text
money_in_words(950, "USD")
# "USD Nine Hundred and Fifty only."
money_in_words(1234.56, "EUR")
# "EUR One Thousand, Two Hundred and Thirty Four and Fifty Six Cents only."
# in_words — integer to words (no currency)
in_words(50) # "Fifty"
in_words(1234) # "One Thousand, Two Hundred and Thirty Four"
# round_based_on_smallest_currency_fraction
from frappe.utils import round_based_on_smallest_currency_fraction
round_based_on_smallest_currency_fraction(99.999, "USD") # 100.0Casting by Fieldtype
from frappe.utils import cast
# Cast value to match Frappe fieldtype
cast("Currency", "123.45") # 123.45 (float)
cast("Int", "42") # 42 (int)
cast("Check", "1") # True (bool)
cast("Date", "2024-03-15") # datetime.dateCommon Patterns
Safe Calculation with Precision
# ALWAYS use flt() for arithmetic on document fields
total = flt(doc.qty, 2) * flt(doc.rate, 2)
doc.amount = flt(total, 2)
# NEVER do this — crashes on None fields
total = float(doc.qty) * float(doc.rate) # TypeError if NoneCurrency Display
# In a print format or notification
formatted = fmt_money(doc.grand_total, currency=doc.currency)
words = money_in_words(doc.grand_total, doc.currency)Safe Division in Percentage Calculations
# v15+: Use safe_div
completion = safe_div(completed_tasks, total_tasks) * 100
# v14: Guard manually
completion = (flt(completed_tasks) / flt(total_tasks) * 100) if total_tasks else 0String & Validation Functions — frappe.utils
String Processing
from frappe.utils import (
strip_html, escape_html, is_html,
comma_and, comma_or, comma_sep,
to_markdown, md_to_html, markdown,
unique, random_string, get_abbr, mask_string
)
# HTML operations
strip_html("<p>Hello <b>World</b></p>") # "Hello World"
escape_html('<script>alert("xss")</script>') # "<script>..."
is_html("<p>test</p>") # True
is_html("plain text") # False
# List joining (localization-aware)
comma_and(["Alice", "Bob", "Charlie"]) # "Alice, Bob, and Charlie"
comma_or(["red", "blue", "green"]) # "red, blue, or green"
comma_sep(["a", "b", "c"]) # "a, b, c"
# Markdown ↔ HTML
to_markdown("<h1>Title</h1><p>Text</p>") # "# Title\n\nText"
md_to_html("# Title\n\nText") # "<h1>Title</h1>\n<p>Text</p>"
markdown("**bold** text", sanitize=True) # "<p><strong>bold</strong> text</p>"
# Data utilities
unique([1, 2, 2, 3, 1]) # [1, 2, 3] — preserves order
random_string(10) # "aB3xK9mP2q"
get_abbr("Coca Cola Company") # "CC"
get_abbr("Coca Cola Company", max_len=3) # "CCC"
# Privacy masking [v16+]
mask_string("1234567890") # "1234***890"
mask_string("1234567890", show_first=2, show_last=2) # "12******90"
mask_string("secret", mask_char="#") # "secr##"More String Utilities
from frappe.utils import (
bold, filter_strip_join, strip,
get_string_between, list_to_str
)
bold("important") # "<b>important</b>"
filter_strip_join(["a", "", "b", None, "c"]) # "a, b, c"
strip(" hello ") # "hello"
get_string_between("(hello)", "(", ")") # "hello"
list_to_str(["a", "b"], sep=" | ") # "a | b"Validation Functions
from frappe.utils import validate_email_address
# Returns valid email(s) or empty string
validate_email_address("user@example.com") # "user@example.com"
validate_email_address("invalid-email") # ""
validate_email_address("user@example.com", throw=True) # raises on invalid
# Handles multiple emails
validate_email_address("a@b.com, c@d.com") # "a@b.com, c@d.com"URL
from frappe.utils import validate_url
validate_url("https://example.com") # True
validate_url("not-a-url") # False
validate_url("ftp://files.example.com") # True
validate_url("http://example.com", valid_schemes=["https"]) # False
validate_url("https://example.com", throw=True) # raises on invalidPhone
from frappe.utils import validate_phone_number
validate_phone_number("+1-555-123-4567") # True
validate_phone_number("invalid") # False
validate_phone_number("+31612345678", throw=True) # raises on invalid
# v16+: With country code validation
from frappe.utils import validate_phone_number_with_country_code
validate_phone_number_with_country_code("+31612345678", "phone")JSON
from frappe.utils import validate_json_string
validate_json_string('{"key": "value"}') # True
validate_json_string('not json') # FalseIBAN [v16+]
from frappe.utils import validate_iban, is_valid_iban
validate_iban("NL91ABNA0417164300") # None (valid)
validate_iban("INVALID", throw=True) # raises
is_valid_iban("NL91ABNA0417164300") # TrueData Manipulation
from frappe.utils import (
parse_json, safe_json_loads,
has_common, is_subset,
generate_hash, sha256_hash,
evaluate_filters, compare,
create_batch
)
# JSON parsing (safe — handles None/empty)
parse_json('{"key": "value"}') # {"key": "value"}
parse_json(None) # None (no crash)
parse_json("") # "" (no crash)
# v16+: With default fallback
safe_json_loads('invalid', default={}) # {}
# Set operations
has_common(["a", "b"], ["b", "c"]) # True
is_subset(["a"], ["a", "b", "c"]) # True
# Hashing
generate_hash("my-string", length=8) # "a1b2c3d4"
sha256_hash("my-string") # full SHA-256 hash
# Filter evaluation
evaluate_filters(
{"status": "Open", "priority": "High"},
[["status", "=", "Open"], ["priority", "=", "High"]]
) # True
# Batch processing
for batch in create_batch(large_list, batch_size=500):
process(batch)Encoding
from frappe.utils import safe_encode, safe_decode, encode
safe_encode("hello") # b"hello" (str to bytes, UTF-8)
safe_decode(b"hello") # "hello" (bytes to str, UTF-8)
encode({"key": "value"}) # '{"key": "value"}' (JSON string)Related skills
Backend & APIsbackend