mcpField
F
frappe-core-search

frappe-core-search

github.com/impertio-studioDevelopment198112 installsUpdated 30w ago
Repository

About frappe-core-search

A Frappe development skill covering link-field, global, website full-text, and SQLite search. It helps developers choose, configure, and customize search behavior across Frappe applications.

Key features

  1. 01Link-field autocomplete configuration
  2. 02Custom link query patterns
  3. 03Global search indexing and setup
  4. 04SQLiteSearch scoring and spelling correction
  5. 05Search subsystem selection guidance

Use cases

  1. 01Make DocType records searchable by readable fields
  2. 02Customize link-field results with filters
  3. 03Set up cross-DocType search
  4. 04Build SQLiteSearch indexes for project data

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/impertio-studio/frappe_claude_skill_package --skill frappe-core-search

After installing, type/frappe-core-searchin Claude Code to confirm it loaded.

Bundled filesreferences/global-website-search.mdreferences/link-search-api.md

Details

From the project README

Frappe Search System

Four Search Subsystems

SubsystemModulePurposeReal-time?
Link Field Searchfrappe.desk.searchAutocomplete in link fieldsYes
Global Searchfrappe.utils.global_searchCross-doctype search (desk + web)No (15min sync)
FullTextSearchfrappe.search.full_text_searchWhoosh-based index (website)On rebuild
SQLiteSearch [v15+]frappe.search.sqlite_searchFTS5 with scoring + spellingYes (5min queue)

Decision Tree

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

Link Field Search

Configuring search_fields (Most Common Need)
# 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).

How Link Search Works
  1. User types in link field → calls search_link(doctype, txt)
  2. Searches across: name + title_field + search_fields
  3. Allowed field types: Data, Text, Small Text, Long Text, Link, Select, Autocomplete, Read Only, Text Editor
  4. Prefix matches rank higher than substring matches
  5. Respects enabled/disabled fields automatically
Custom Link Query
# 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)
Per-Field Query Override
// 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 }
        }));
    }
});

Global Search

Enabling

Set in_global_search = 1 on DocType fields that should be searchable.

How It Works
  • Indexed fields stored in __global_search table
  • Synced via Redis queue every 15 minutes
  • Uses DB-native fulltext: MariaDB MATCH...AGAINST, PostgreSQL TSVECTOR
  • Permission-filtered results
Rebuilding Index
# 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()
hooks.py Configuration
# Default doctypes for global search
global_search_doctypes = {
    "Default": [
        {"doctype": "Contact"},
        {"doctype": "Customer"},
        {"doctype": "Sales Order"},
    ]
}

SQLiteSearch [v15+]

Creating Custom Search
# 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 {}
Register in hooks.py
sqlite_search = ['my_app.search.ProjectSearch']
Features (automatic)
  • Spelling correction: Trigram-based fuzzy matching
  • Recency boosting: 1.8x (24h) → 1.5x (7d) → 1.2x (30d) → 1.1x (90d)
  • Resumable indexing: Progress tracked, atomic replacement
  • Auto-scheduling: Build every 3h, queue every 5min, doc events trigger updates

Anti-Patterns

NEVERALWAYSWhy
Omit search_fields on DocTypeSet search_fields for user-friendly namesUsers 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 searchUse parameterized queries (%(txt)s)SQL injection risk
Index all fields in global searchOnly in_global_search=1 on key fieldsBloats table, slows 15-min sync
Use global search for real-timeUse link field search for real-timeGlobal search has 15-min sync delay
Skip get_search_filters() in SQLiteSearchImplement permission filteringReturns all results regardless of access
Index cancelled/deleted docsSet filters in INDEXABLE_DOCTYPESStale results confuse users

Version Differences

Featurev14v15+
Link search caching--@http_cache(max_age=60)
link_fieldname param--Added
page_length default2010
SQLiteSearch (FTS5)--Full implementation
Spelling correction--Trigram-based
Recency boosting--Time-based multipliers
sqlite_search hook--Available
Global searchYesYes
Whoosh FullTextSearchYesYes (legacy)

Reference Files

Comments

Markdown supported