Saudi Riyal Symbol in Frappe / ERPNext

Author
Aug 22, 2026
Saudi Riyal Symbol in Frappe / ERPNext

The Saudi Riyal Symbol (U+20C1) on Frappe/ERPNext

The official Saudi Riyal Sign is U+20C1, assigned in Unicode 17.0.

Two things have to be true for it to appear correctly: the symbol value must be stored in the Currency doctype, and a font containing the glyph must be available wherever the page is rendered. Recent ERPNext ships the value. The font is the part you have to handle.

On version 16. A newer Frappe release can ship the correct symbol value. It cannot install a font on your server or on your users' machines. Every font step below is still required on v16.

Pick your section:


<table><tr><td bgcolor="#EAF6EC">

Section 1 — With zatca_erpgulf

The browser side is already done. The app bundles the font and loads it automatically. Nothing to install on any client machine, on any operating system.

What ships with the app:

  • zatca_erpgulf/public/fonts/saudi_riyal.ttf — served at /assets/zatca_erpgulf/fonts/saudi_riyal.ttf

  • zatca_erpgulf/public/css/saudi_riyal.css — an @font-face rule scoped with unicode-range: U+20C1

  • Both registered via app_include_css and web_include_css in hooks.py

This covers the desk UI, forms and the customer portal.

1.1 Install the font on the server — for PDFs

PDF generation does not use the app's web assets. wkhtmltopdf resolves fonts through fontconfig from the filesystem, so the bundled font has to be copied out.

Do not download the font again. Copy the one the app already ships — same file, no network, guaranteed to match what the browser is served.

cd /path/to/frappe-bench

mkdir -p ~/.local/share/fonts
cp apps/zatca_erpgulf/zatca_erpgulf/public/fonts/saudi_riyal.ttf ~/.local/share/fonts/
fc-cache -f
fc-list ':charset=20c1' family file

saudi_riyal in that output means it worked.

No sudo needed — fontconfig reads the bench user's font directory, and that user runs the PDF workers. This also works on Frappe Cloud and in containers where you have no root.

If you have root and want it available to every user on the box:

cd /path/to/frappe-bench

sudo mkdir -p /usr/share/fonts/truetype/saudi-riyal
sudo cp apps/zatca_erpgulf/zatca_erpgulf/public/fonts/saudi_riyal.ttf \
        /usr/share/fonts/truetype/saudi-riyal/
sudo fc-cache -f
fc-list ':charset=20c1' family file

Then restart so the workers pick up the new font list:

bench restart          # or: sudo supervisorctl restart all

1.2 Verify the PDF

sudo apt install -y poppler-utils
bench --site "$SITE" console
pdf = frappe.get_print("Sales Invoice", "<your-invoice-name>", as_pdf=True)
open('/tmp/t.pdf','wb').write(pdf)
pdffonts /tmp/t.pdf | grep -i riyal

saudi_riyalregular means the symbol rendered — wkhtmltopdf only embeds fonts it actually used during layout.

If you are on the Chrome PDF generator, step 1.1 may be unnecessary — Chrome honours @font-face from the app's CSS directly. Check which you are on:

frappe.db.get_single_value("Print Settings", "pdf_generator")

Either way, run the pdffonts check rather than assuming.

1.3 Print preview and direct print

Print formats render in an isolated document that does not load app_include_css, so the bundled font does not reach them. The preview then falls back to fonts on the viewer's own machine — see the platform table.

Add this in Print Settings → Custom CSS:

@font-face {
  font-family: 'saudi_riyal';
  src: url('/assets/zatca_erpgulf/fonts/saudi_riyal.ttf') format('truetype');
  unicode-range: U+20C1;
}
body, .print-format {
  font-family: 'saudi_riyal', 'Noto Sans Arabic', sans-serif;
}

No build needed — the font file is already served by the app.

1.4 If your print format sets its own fonts

A rule like this in a custom format overrides the above and brings the blank box back, because the * selector wins and Arial has no riyal glyph:

.print-format,
.print-format * {
    font-family: Arial, Tahoma, sans-serif;
}

Put the font at the front of that stack, in the format's own Style block:

@font-face {
    font-family: 'saudi_riyal';
    src: url('/assets/zatca_erpgulf/fonts/saudi_riyal.ttf') format('truetype');
    unicode-range: U+20C1;
}

.print-format,
.print-format * {
    font-family: 'saudi_riyal', Arial, Tahoma, sans-serif;
}

Because of unicode-range, Arial still handles every other character.

</td></tr></table>


<table><tr><td bgcolor="#EDF2FA">

Section 2 — Without zatca_erpgulf

You install the font yourself, in two places: on the server for PDFs, and as a webfont for browsers.

Set your site name once:

SITE=your-site-name
cd /path/to/frappe-bench

2.1 Install the font on the server

The Saudi Central Bank publishes the symbol as SVG/EPS/PNG artwork, not as a font. Use the community font, licensed SIL OFL 1.1:

sudo mkdir -p /usr/share/fonts/truetype/saudi-riyal
cd /usr/share/fonts/truetype/saudi-riyal
sudo curl -sLO https://raw.githubusercontent.com/emran-alhaddad/Saudi-Riyal-Font/main/fonts/regular/saudi_riyal.ttf
sudo fc-cache -f
fc-list ':charset=20c1' family file

saudi_riyal should appear in the output.

Do not substitute a Flutter or icon font. Packages such as saudi_riyal_symbol on pub.dev map the glyph to a private-use codepoint (U+E900) and also carry the letters r i y a l for ligature support. fontconfig will not match them for U+20C1, and forcing it via CSS lets the stray Latin glyphs hijack ordinary text.

Restart so PDF workers see it:

bench restart

2.2 Check the currency symbol

Recent ERPNext already ships SAR with U+20C1, so this is normally just verification.

Go to Awesome bar → Currency → SAR and look at the Symbol field. The field looks nearly blank at normal size because the glyph is thin — that is normal.

To confirm by its bytes rather than by eye:

bench --site "$SITE" mariadb -e \
  "SELECT HEX(symbol) FROM tabCurrency WHERE name='SAR';"

E28381 is UTF-8 for U+20C1 and is correct. EFB7BC is the old ligature and needs changing — copy the riyal character, paste it into Currency → SAR → Symbol, Save.

2.3 Verify the PDF

sudo apt install -y poppler-utils
bench --site "$SITE" console
pdf = frappe.get_print("Quotation", "<your-quotation-name>", as_pdf=True)
open('/tmp/t.pdf','wb').write(pdf)
pdffonts /tmp/t.pdf | grep -i riyal

2.4 Serve the font to browsers

The desk and print preview render on the client machine. Without a webfont, anyone on an older OS sees a blank box — see the platform table.

Copy saudi_riyal.woff2 from the font repo into any installed app's public folder, then add this in Print Settings → Custom CSS:

@font-face {
  font-family: 'saudi_riyal';
  src: url('/assets/your_app/fonts/saudi_riyal.woff2') format('woff2');
  unicode-range: U+20C1;
}
body, .print-format {
  font-family: 'saudi_riyal', 'Noto Sans Arabic', sans-serif;
}

For the desk UI as well as print, register the same CSS through app_include_css in that app's hooks.py. If the key already exists as a string, convert it to a list rather than adding a second assignment — a second assignment silently discards the first.

app_include_css = [
    "/assets/your_app/css/existing.css",
    "/assets/your_app/css/saudi_riyal.css",
]
web_include_css = ["/assets/your_app/css/saudi_riyal.css"]
bench build --app your_app
bench --site "$SITE" clear-cache

</td></tr></table>


Reference

Use Frappe's currency helpers in templates

Anything formatted through Frappe's own helpers picks up the symbol automatically:

{{ doc.get_formatted("grand_total") }}
{{ frappe.utils.fmt_money(row.rate, currency=doc.currency) }}

Prefer these over pasting the symbol into the template. A hardcoded symbol stays riyal even when the invoice is in AED or USD.

Which machines already have the glyph

Platform

Has the glyph

macOS 15.4 Sequoia and later

Yes

macOS 15.3 and earlier

No

Windows 11 24H2 with the Feb 2025 cumulative update or later

Yes

Windows 11 24H2 unpatched, 23H2 and earlier

No

Windows 10, all versions

No

iOS 18.4 and later

Yes

Android 15 and earlier

Mostly no

Linux desktop

Only with Noto 24.9 or later

Verify these against current vendor release notes before relying on them — the rollout was staggered across patch releases.

A downloaded PDF is generated on the server and is correct regardless of the viewer's machine. If preview is the only place it fails, that is cosmetic.

Keep unicode-range

unicode-range: U+20C1;

This font contains only the riyal glyph. The range confines it to that one character so every letter and digit still comes from your normal fonts. Without it, a single-purpose font sits in the fallback chain for everything.

Vertical alignment

The symbol sits slightly high against digits at large sizes. If it looks off:

.print-format { --riyal-shift: -0.08em; }

then apply vertical-align: var(--riyal-shift) to the element wrapping the amount. At normal document sizes this is usually unnecessary.

What the symbol looks like

Two horizontal strokes crossed by two diagonals — a stylised ر. It is not the old ligature and it is not a rendering fault. Before debugging, compare against a machine known to have the glyph.

What a Frappe release can and cannot fix

Fixed by a newer Frappe

Still your job

Currency symbol value in the DB

Yes

Font on the server, for PDFs

No

1.1 / 2.1

Font for the desk UI on old client machines

No

Bundled in zatca_erpgulf / 2.4

Font in print preview and direct print

No

1.3 / 2.4

Custom formats hardcoding a symbol or font stack

No

1.4

Author
Written by ERPGulf Team