Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
openaec-foundation avatar

Frappe Core Search

  • 26 installs
  • 159 repo stars
  • Updated July 8, 2026
  • openaec-foundation/frappe_claude_skill_package

Helps with ai & agent building tasks.

About

frappe-core-search is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.

  • frappe-core-search
  • AI & Agent Building
  • AI-coding skill

Frappe Core Search by the numbers

  • 26 all-time installs (skills.sh)
  • Ranked #9,699 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/frappe_claude_skill_package --skill frappe-core-search

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs26
repo stars159
Last updatedJuly 8, 2026
Repositoryopenaec-foundation/frappe_claude_skill_package

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

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

  • Link Search API — search_link, search_widget, custom queries
  • Global & Website Search — Global search, WebsiteSearch, SQLiteSearch

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.