agentsclimarketplace

Frappe core search

Skill Impertio-Studio/Frappe_Claude_Skill_Package/skills/source/core/frappe-core-search

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.From its SKILL.md

Install
npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-core-search

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.5 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it

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

What ships with it: 2 files

10.6 KB alongside SKILL.md

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.