skills/source/core/frappe-core-search/SKILL.md
Use when implementing search functionality in Frappe v14-v16. Covers link field search (search_link), global search, FullTextSearch (Whoosh), SQLiteSearch FTS5 [v15+], Awesomebar customization, search_fields configuration, custom search queries, and website search. Prevents common mistakes with missing search_fields and permission filtering. Keywords: search, search_link, global_search, FullTextSearch, Awesomebar,, search not finding, link field empty, autocomplete not working, global search missing results. search_fields, standard_queries, SQLiteSearch, FTS5, Whoosh.
npx skillsauth add OpenAEC-Foundation/ERPNext_Anthropic_Claude_Development_Skill_Package frappe-core-searchInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Subsystem | Module | Purpose | Real-time? |
|-----------|--------|---------|:----------:|
| Link Field Search | frappe.desk.search | Autocomplete in link fields | Yes |
| Global Search | frappe.utils.global_search | Cross-doctype search (desk + web) | No (15min sync) |
| FullTextSearch | frappe.search.full_text_search | Whoosh-based index (website) | On rebuild |
| SQLiteSearch [v15+] | frappe.search.sqlite_search | FTS5 with scoring + spelling | Yes (5min queue) |
What search do you need?
│
├─ Link field autocomplete (user types in a Link field)?
│ ├─ Default behavior sufficient → Configure search_fields on DocType
│ └─ Custom logic needed → standard_queries hook or query parameter
│
├─ Cross-doctype search (user searches for anything)?
│ ├─ Desk users → Global Search (auto-enabled)
│ │ └─ Set in_global_search=1 on important fields
│ └─ Website visitors → web_search() or WebsiteSearch (Whoosh)
│
├─ Custom full-text search for your app [v15+]?
│ └─ SQLiteSearch subclass + sqlite_search hook
│ → Spelling correction, recency boost, custom scoring
│
└─ Awesomebar customization?
└─ Client-side: override build_options or use search dialog
# In DocType JSON or via customize form
{
"search_fields": "customer_name, customer_group",
"title_field": "customer_name",
"show_title_field_in_link": 1
}
ALWAYS set search_fields — Without it, users can only search by name (often a code like CUST-001).
search_link(doctype, txt)name + title_field + search_fieldsenabled/disabled fields automatically# hooks.py — override search for a specific DocType
standard_queries = {
"Customer": "my_app.queries.customer_query"
}
# my_app/queries.py — MUST be @frappe.whitelist()
@frappe.whitelist()
def customer_query(doctype, txt, searchfield, start, page_length, filters,
as_dict=False, reference_doctype=None,
ignore_user_permissions=False):
# Return list of dicts: [{"value": name, "description": label}, ...]
return frappe.db.sql("""
SELECT name, customer_name as description
FROM `tabCustomer`
WHERE (name LIKE %(txt)s OR customer_name LIKE %(txt)s)
AND status = 'Active'
ORDER BY customer_name
LIMIT %(start)s, %(page_length)s
""", {"txt": f"%{txt}%", "start": start, "page_length": page_length},
as_dict=True)
// In Client Script or Form JS
frappe.ui.form.on("Sales Order", {
setup(frm) {
frm.set_query("customer", () => ({
filters: { status: "Active", territory: frm.doc.territory }
}));
}
});
Set in_global_search = 1 on DocType fields that should be searchable.
__global_search tableMATCH...AGAINST, PostgreSQL TSVECTOR# Rebuild for specific DocType
from frappe.utils.global_search import rebuild_for_doctype
rebuild_for_doctype("Sales Order")
# Rebuild everything
from frappe.utils.global_search import rebuild
rebuild()
# Default doctypes for global search
global_search_doctypes = {
"Default": [
{"doctype": "Contact"},
{"doctype": "Customer"},
{"doctype": "Sales Order"},
]
}
# my_app/search.py
from frappe.search.sqlite_search import SQLiteSearch
class ProjectSearch(SQLiteSearch):
INDEX_SCHEMA = {
"metadata_fields": ["project", "owner", "status"],
"tokenizer": "unicode61 remove_diacritics 2 tokenchars '-_'",
}
INDEXABLE_DOCTYPES = {
"Task": {
"fields": ["name", {"title": "subject"}, {"content": "description"},
"modified", "project"],
"filters": {"status": ("!=", "Cancelled")}
},
"Project": {
"fields": ["name", {"title": "project_name"}, {"content": "notes"},
"modified", "status"],
}
}
def get_search_filters(self, query, scope=None):
"""Permission filtering — return additional WHERE conditions"""
return {}
sqlite_search = ['my_app.search.ProjectSearch']
| NEVER | ALWAYS | Why |
|-------|--------|-----|
| Omit search_fields on DocType | Set search_fields for user-friendly names | Users can't find records by name codes |
| Custom query without @frappe.whitelist() | Decorate with @frappe.whitelist() | Silently fails — rejected by security check |
| Raw SQL without params in search | Use parameterized queries (%(txt)s) | SQL injection risk |
| Index all fields in global search | Only in_global_search=1 on key fields | Bloats table, slows 15-min sync |
| Use global search for real-time | Use link field search for real-time | Global search has 15-min sync delay |
| Skip get_search_filters() in SQLiteSearch | Implement permission filtering | Returns all results regardless of access |
| Index cancelled/deleted docs | Set filters in INDEXABLE_DOCTYPES | Stale results confuse users |
| Feature | v14 | v15+ |
|---------|:---:|:----:|
| Link search caching | -- | @http_cache(max_age=60) |
| link_fieldname param | -- | Added |
| page_length default | 20 | 10 |
| SQLiteSearch (FTS5) | -- | Full implementation |
| Spelling correction | -- | Trigram-based |
| Recency boosting | -- | Time-based multipliers |
| sqlite_search hook | -- | Available |
| Global search | Yes | Yes |
| Whoosh FullTextSearch | Yes | Yes (legacy) |
tools
Use when implementing OAuth providers, Connected Apps, Webhooks, Payment Gateways, or Data Import/Export in Frappe. Prevents authentication failures from wrong OAuth flow, missed webhook deliveries, and data corruption during bulk imports. Covers OAuth2 provider/client, Connected App DocType, Webhook DocType, Payment Gateway integration, Data Import, Data Export, frappe.integrations module. Keywords: OAuth, Connected App, Webhook, Payment Gateway, Data Import, Data Export, integration, API key, OAuth2, webhook trigger, connect to external service, OAuth setup, webhook configuration, import data, export data..
development
Use when implementing hooks.py configurations in a Frappe custom app. Covers step-by-step workflows for doc_events, scheduler_events, override/extend_doctype_class, permission hooks, extend_bootinfo, fixtures, asset injection, website hooks, and doctype_js. Prevents broken transactions, missed migrations, and multi-app conflicts. Keywords: hooks.py, doc_events, scheduler_events, override doctype,, how to add hook, when to use doc_events, scheduler setup, override existing behavior. extend doctype class, permission hook, scheduler job, fixtures, doctype_js, extend_bootinfo, website hooks.
development
Use when building a custom Frappe app from scratch. Covers bench new-app walkthrough, app structure decisions, adding DocTypes, hooks, patches, fixtures management, development workflow (bench migrate, build, clear-cache), testing, packaging, installing on another site, version management, and app dependencies for v14/v15/v16. Keywords: create custom app, new frappe app, bench new-app, app structure, module creation, doctype creation, fixtures, patches, deployment, packaging, data migration, patch file, patches.txt, migrate data between DocTypes, create new app from scratch.
development
Use when building Document Controllers in a custom Frappe app: file creation, lifecycle hooks, validation, autoname, submittable workflows, controller override, child table controllers, flags system, migration from hooks.py and Server Scripts. Keywords: how to implement controller, which hook to use, validate vs on_update, override controller, submittable document, autoname, flags, extend_doctype_class, controller testing, child table controller, which hook to use, when does validate run, how to override save, document lifecycle.