Add field-level help text to any ERPNext Doctype without writing a single line of custom code per form.
Admins configure tooltips in one place. The system auto-generates a Client Script for each target Doctype on Save. Users see inline ⓘ icons with hover popovers and rich Markdown popups — cached in localStorage for instant rendering.
Admin saves "Dynamic Tooltip" for Sales Order
│
▼
Script Factory runs → generates "DT Auto - Sales Order" Client Script
│
User opens Sales Order form
│
├── localStorage cache hit? YES → render icons (0 API calls, <5ms)
└── NO → call get_tooltips API → cache result → render icons
Admin updates tooltip content → save → new CACHE_VER baked in
└── Next form open: stale cache detected → re-fetch automatically
Rendering:
- Hover → Bootstrap popover with plain-text tooltip
- Click →
frappe.ui.Dialogwith server-rendered Markdown (tables, lists, bold, code, links) - Both → popover shows hover text + "Click for more" hint; click opens the dialog
- Zero per-Doctype manual work — configure once, works everywhere
- Markdown Editor in admin UI with live preview
- localStorage caching with automatic version-based invalidation
- Master toggle (
Is Active) — disables tooltips without deleting content Regenerate Scriptbutton for one-click recoveryLoad Fieldsbutton — content-safe, never overwrites existing tooltip data- Server-side Markdown rendering via
frappe.utils.md_to_html()— no client-side parser
Requires bench CLI access.
# 1. Get the app
bench get-app https://github.com/SumanthUdupi/Frappe_Dynamic_Tooltip
# 2. Install on your site
bench --site your-site.localhost install-app dynamic_tooltip
# 3. Run migration to create Doctypes and columns
bench --site your-site.localhost migrate
# 4. Clear cache
bench --site your-site.localhost clear-cacheDirectory structure after install:
apps/dynamic_tooltip/
├── setup.py
└── dynamic_tooltip/
├── hooks.py ← app metadata + fixtures
├── modules.txt
├── api.py ← get_tooltips + get_doctype_fields (whitelisted)
└── dynamic_tooltip/
└── doctype/
├── dynamic_tooltip/
│ ├── dynamic_tooltip.json ← DocType definition
│ ├── dynamic_tooltip.py ← controller: validate, on_update (Script Factory), on_trash (cleanup)
│ └── dynamic_tooltip.js ← admin form: Load Fields, Regenerate Script
└── dynamic_tooltip_item/
├── dynamic_tooltip_item.json
└── dynamic_tooltip_item.py
Note for Frappe Cloud users: Use Method B below.
Import 7 files through the ERPNext UI in order. No terminal required.
Files in method_b_no_code/:
| # | File | What it creates |
|---|---|---|
| 1 | 01_doctype_dynamic_tooltip.json |
Parent Doctype |
| 2 | 02_doctype_dynamic_tooltip_item.json |
Child table Doctype |
| 3 | 03_server_script_get_tooltips.json |
API: get_tooltips |
| 4 | 04_server_script_tooltip_validation.json |
Before Save validation |
| 5 | 05_server_script_factory.json |
After Save: auto-generates Client Scripts |
| 6 | 06_server_script_cleanup.json |
After Delete: removes generated Client Scripts |
| 7 | 07_client_script_admin.json |
Admin form UI (Load Fields, Regenerate Script) |
Step-by-step:
Step 1 — Import the Doctypes (files 01 and 02)
- Go to Doctype List → top-right menu → Import
- Upload
01_doctype_dynamic_tooltip.json→ Save - Repeat for
02_doctype_dynamic_tooltip_item.json
Step 2 — Import Server Scripts (files 03–06)
- Go to Server Script List → top-right menu → Import
- Upload each file in order (03 → 04 → 05 → 06) → Save each
Step 3 — Import the Admin Client Script (file 07)
- Go to Client Script List → top-right menu → Import
- Upload
07_client_script_admin.json→ Save
Step 4 — Verify
Navigate to Dynamic Tooltip in the ERPNext menu. You should see the form with a "Load Fields" button and a "Script Info" section.
Alternative (fastest): Use the provided
dynamic_tooltip_setup_v2.jsconsole script — paste it into your browser DevTools console while logged in as System Manager. It deploys all 7 components in one run and handles updates idempotently.
- Go to Dynamic Tooltip → New
- Select your Target DocType (e.g. Sales Order)
- Click Load Fields — the child table populates with all visible fields
- For each field you want to annotate:
- Tooltip (Hover): plain text shown on icon hover
- Popup Content (Click): full Markdown — use bold, tables, lists, code blocks
- Active: checked by default; uncheck to hide the icon for that field
- Save — the system auto-generates
DT Auto - Sales OrderClient Script - Hard-reload any open Sales Order form to see the ⓘ icons
- Hover over the ⓘ icon next to any field label → quick tooltip popover
- Click the icon → full help dialog with rendered Markdown content
- Click Got it to dismiss
Just edit the Dynamic Tooltip record and Save. The Script Factory regenerates the Client Script with a new CACHE_VER. Users see fresh content on next form open — no manual cache clearing needed.
Uncheck Is Active on the parent record and Save. The Script Factory regenerates with IS_ACTIVE = false. All icons disappear from the target form instantly. Re-enable and Save to restore.
The Popup Content field supports full Markdown rendered server-side via frappe.utils.md_to_html():
## Field Guide
This field controls **billing**.
| Value | Meaning |
|-------|---------|
| `NET30` | Payment due in 30 days |
| `IMMEDIATE` | Due on receipt |
> Always match the Purchase Order terms.
See also: [Payment Terms](https://docs.erpnext.com)Supported: headings, bold/italic, strikethrough, inline code, tables, lists, blockquotes, links, del.
CACHE_VER is str(doc.modified) captured before any write-back. It is baked into the generated Client Script. When an admin saves an update, the script regenerates with a new CACHE_VER. On the user's next form open, cached.ver !== CACHE_VER → fresh API call → new cache written. Stale data is structurally impossible.
Auto-generated scripts are named DT Auto - {Doctype}. They are never confused with hand-written Client Scripts. Deleting the Dynamic Tooltip record removes the generated script automatically.
| Method | frappe.call method |
|---|---|
| A (Frappe App) | dynamic_tooltip.api.get_tooltips |
| B (No-Code / Server Script) | get_tooltips |
The rendering engine is otherwise identical.
PRs welcome. Please open an issue first for significant changes.
MIT