{
  "category": "Text functions",
  "display_name": "SEARCH",
  "entry_kind": "built_in_function",
  "eval_api": {
    "method": "POST",
    "url": "https://handbook.dnacalc.com/api/eval"
  },
  "function_id": "FUNC.SEARCH",
  "implementation_state": {
    "answer_bank": null,
    "detail": "Recorded: 57 workbook cells.",
    "dnacalc_commit": "9e1884369a678158dd82d3d74188daf986995e87",
    "in_catalogue": true,
    "state": "matches",
    "state_label": "Matches every recorded Excel answer",
    "summary": "Matches all 57 recorded answers",
    "workbook_corpus": {
      "cells": 57,
      "matched": 57
    }
  },
  "implementations": [
    {
      "commit": "9e1884369a678158dd82d3d74188daf986995e87",
      "flavour": "excel-compatibility-candidate",
      "language": "Rust",
      "name": "CalcFunc",
      "own_code": [
        "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text_functions.rs#L303-L303"
      ],
      "repository": "https://github.com/DnaCalc/DnaCalc",
      "shared_code": [
        "catalogue/text_functions::count_surrogate_pairs",
        "catalogue/text_functions::start",
        "catalogue/text_functions::search",
        "ported/text_kernels::text",
        "ported/text_kernels::text_from_str",
        "ported/text_kernels::unit_width",
        "ported/text_kernels::character_boundary",
        "ported/text_kernels::slice_character_count",
        "ported/text_kernels::truncated",
        "ported/text_kernels::positive",
        "ported/text_kernels::fold_unit",
        "ported/text_kernels::search_units_equal",
        "ported/text_kernels::search_pattern_matches_prefix",
        "ported/text_kernels::search",
        "ported/text_kernels::push_singleton_or_original",
        "ported/text_kernels::push_lower",
        "ported/text_kernels::lower"
      ],
      "source": "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text_functions.rs#L303-L303",
      "state": "matches",
      "suite": null,
      "summary": "Matches all 57 recorded answers"
    }
  ],
  "json_url": "https://handbook.dnacalc.com/api/functions/FUNC.SEARCH.json",
  "name": "SEARCH",
  "notes": [],
  "schema": "efh.function-reference/v1",
  "section_order": [
    "what-it-does",
    "try-it",
    "quirks",
    "how-to-implement",
    "version-differences",
    "alternatives"
  ],
  "sections": {
    "alternatives": "\n### Relationships\n\n\n- **`FIND`** — the case-sensitive, literal counterpart. See the comparison table above. Neither\n  supersedes the other; both are current.\n- **[SEARCHB](FUNC.SEARCHB.md)** — the byte-position sibling, documented by Microsoft on the\n  same page and published by the Handbook as its own entry.\n- **[SUBSTITUTE](FUNC.SUBSTITUTE.md)** — the by-content editor. Note the asymmetry that trips\n  people: `SEARCH` is case-**in**sensitive and takes wildcards, while `SUBSTITUTE` is\n  case-**sensitive** and takes none. A formula that locates with `SEARCH` and edits with\n  `SUBSTITUTE` is using two different matching rules on the same string.\n- **[REPLACE](FUNC.REPLACE.md)** — the by-position editor, and the usual consumer of a `SEARCH`\n  result.\n- **`ISNUMBER(SEARCH(x, y))`** is the idiomatic case-insensitive \"contains\" test, precisely\n  because `SEARCH` errors rather than returning zero.\n- **`XMATCH`** with its wildcard match mode does pattern matching over a lookup array rather\n  than within a single string — a different job that reuses the same wildcard vocabulary.\n- **`REGEXTEST` / `REGEXEXTRACT`** are the modern route when the three-character wildcard\n  language is not enough.\n\n\n",
    "how-to-implement": "\n### Notes for implementers\n\n\nThe wildcard engine is the whole implementation. Case folding is the easy half; deciding what\n`*` and `?` mean over UTF-16 code units is the hard half, because `?` matching \"any single\ncharacter\" has to choose between one code unit and one scalar value, and those differ on every\nastral character.\n\nCase-insensitivity is locale-sensitive in the same way `PROPER`'s capitalization is: Turkish\ndotted and dotless i, and full-versus-simple case folding, both change what matches. An\ninvariant fold is a defensible default and is a decision to record, not a neutral one.\n\n`start_num` bounds are checked before the search, and the check is against `LEN(within_text)`,\nnot against `LEN(within_text) + 1`, so scanning from just past the end is documented as an\nerror rather than as a not-found. That is a genuine asymmetry with the way many string\nlibraries behave.\n\nThe returned position is absolute, not relative to `start_num`. An implementation that runs the\nsearch over a suffix has to add the offset back.\n\n\n",
    "quirks": "\n### SEARCH versus FIND\n\n\nThis is the most-confused pair in the text category. The two functions have the same signature\nand different semantics on two axes at once:\n\n| | `SEARCH` | `FIND` |\n|---|---|---|\n| Case | **not** case sensitive | case sensitive |\n| `find_text` is | a **wildcard pattern** (`?`, `*`, `~`) | a **literal** string |\n| Byte sibling | `SEARCHB` | `FINDB` |\n| Not found | `#VALUE!` | `#VALUE!` |\n| Position returned | from the start of `within_text` | from the start of `within_text` |\n\nMicrosoft states the case axis explicitly on the `SEARCH` page — if you want a case-sensitive\nsearch, use `FIND` — and documents the wildcard set on the same page.\n\nThe practical selection rule:\n\n- Matching a substring **exactly as given**, including a literal `*` or `?` — use `FIND`.\n- Matching **case-insensitively**, or matching a pattern — use `SEARCH`.\n- Matching case-insensitively but needing `find_text` treated literally — you need `SEARCH`\n  with `find_text` escaped, or `FIND` over `UPPER`ed copies of both arguments. There is no\n  single function that does it.\n\n\n\n### Wildcards\n\n\nThe documented pattern language is three characters:\n\n| Character | Meaning |\n|---|---|\n| `?` | Matches any single character |\n| `*` | Matches any sequence of characters, including none |\n| `~` | Escapes the following `?`, `*` or `~`, matching it literally |\n\nWhat the documentation does **not** settle, and this page will not invent:\n\n- Whether the match is leftmost-shortest or leftmost-longest when `*` could match several\n  ways. Since only the start position is returned, the difference is usually invisible — but\n  not always, and it is unverified.\n- Whether `~` before a character that is not `?`, `*` or `~` is an error, is dropped, or is\n  matched literally.\n- Whether a `find_text` consisting only of `*` returns `start_num`, 1, or something else.\n\nEach of these is on the probe list below.\n\n\n\n### Result and edge cases\n\n\nReturns `Number` — a 1-based character position.\n\nDocumented behaviours:\n\n- `find_text` not found → `#VALUE!`. There is no \"not found\" sentinel and no zero return.\n  Wrapping in `IFERROR` is the conventional guard.\n- `start_num` outside `1 … LEN(within_text)` → `#VALUE!`.\n\nNot documented, and therefore left open here: what `SEARCH(\"\", A1)` returns. An empty pattern\nmatching at the current position is the usual convention and would give `start_num`, but\nMicrosoft's page does not say so and the Handbook has not checked.\n\n\"Character\" is a UTF-16 code unit, consistent with `LEN` and the rest of the text family; see\n[The value universe](../model/01-value-universe.md).\n\n\n### Errors\n\n\nAs documented on Microsoft's `SEARCH, SEARCHB` page:\n\n| Error | Condition |\n|---|---|\n| `#VALUE!` | `find_text` is not found in `within_text`. |\n| `#VALUE!` | `start_num` is not greater than 0, or is greater than the length of `within_text`. |\n\nNon-numeric text in `start_num` and non-convertible values in the text slots surface `#VALUE!`\nunder the shared coercion rules, and error values in any argument propagate. The Handbook has\nnot verified any of this against Excel.\n\n\n",
    "sources": "- Microsoft, \"SEARCH, SEARCHB functions\" —\n  <https://support.microsoft.com/en-us/office/search-function-9ab04538-0e55-4719-a72e-b6f54513b495>\n  (signature, case-insensitivity and the pointer to `FIND` for case-sensitive matching, the\n  wildcard set including the tilde escape, the `start_num` skip usage, and the two documented\n  `#VALUE!` conditions).\n- Handbook, [The value universe](../model/01-value-universe.md) — text as UTF-16 code units.\n- Handbook, [Coercion and lifting](../model/02-coercion-and-lifting.md) — coercion of the\n  numeric and text argument slots and error propagation.\n\n\n",
    "version-differences": "No comparison between specific Excel builds is recorded here yet. This does not establish that all builds behave alike.",
    "what-it-does": "\n`SEARCH(find_text, within_text, [start_num])` returns the 1-based position, **counted from the\nstart of `within_text`**, of the first place at or after `start_num` where `find_text` matches.\n\nTwo properties define it, and both are in Microsoft's documentation:\n\n1. **The match is not case sensitive.**\n2. **`find_text` is a wildcard pattern**, not a literal: `?` matches any single character, `*`\n   matches any sequence of characters, and `~` escapes the next `?` or `*` so it can be matched\n   literally.\n\nThe second property is the one that gets missed. `SEARCH` is not \"`FIND` with the case\nswitched off\" — it is a small pattern matcher. Any `find_text` that came from user data or from\nanother cell may contain `*` or `?`, and if it does, `SEARCH` will not look for it literally.\n\nThe returned position is measured from the beginning of `within_text` in both cases, regardless\nof `start_num`. `start_num` moves where the scan begins; it does not move the origin of the\ncoordinate system. `SEARCH(\"a\", \"abca\", 2)` is 4, not 3.\n\n\n\n### Arguments\n\n\n| Argument | Meaning | Default |\n|---|---|---|\n| `find_text` | The pattern to look for. Required. | — |\n| `within_text` | The string to look in. Required. | — |\n| `start_num` | 1-based character position at which the scan begins. Optional. | 1 |\n\nMicrosoft describes `start_num` as the way to skip a specified number of characters — the\ndocumented use case is finding the *second* occurrence of something by restarting the scan\npast the first.\n\n`start_num` is a numeric slot subject to ordinary to-number coercion; `find_text` and\n`within_text` are text slots. See\n[Coercion and lifting](../model/02-coercion-and-lifting.md).\n\n\n"
  },
  "signature": {
    "display": "SEARCH(find_text, within_text, [start_num])",
    "parameters": [
      {
        "description": null,
        "name": "find_text",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "within_text",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "start_num",
        "optional": true,
        "repeats": false
      }
    ],
    "trailing_repeats": false
  },
  "sources": {
    "metadata": {
      "admission": {
        "label": "implemented",
        "reason": null
      },
      "arity": {
        "max": 3,
        "min": 2
      },
      "category": "Text functions",
      "classification": {
        "arg_preparation_profile": "ValuesOnlyPreAdapter",
        "coercion_lift_profile": "None",
        "determinism_class": "Deterministic",
        "error_collapse_profile": "None",
        "fec_dependency_profile": "None",
        "host_interaction_class": "None",
        "kernel_signature_class": "Custom",
        "rich_value_usage": "RichBlind",
        "surface_fec_dependency_profile": "RefOnly",
        "thread_safety_class": "SafePure",
        "volatility_class": "NonVolatile"
      },
      "descriptions": {
        "long": null,
        "short": "Position of one text inside another, ignoring letter case and allowing wildcards",
        "short_source": "dna-calc-own"
      },
      "docs": {
        "english_description": "Finds one text value within another (not case-sensitive)",
        "microsoft_url": "https://support.microsoft.com/en-us/office/search-function-9ab04538-0e55-4719-a72e-b6f54513b495"
      },
      "entry_kind": "built_in_function",
      "localized_family_names": null,
      "localized_names": [
        {
          "locale": "ar-SA",
          "name": "SEARCH"
        },
        {
          "locale": "bg-BG",
          "name": "SEARCH"
        },
        {
          "locale": "cs-CZ",
          "name": "HLEDAT"
        },
        {
          "locale": "da-DK",
          "name": "SØG"
        },
        {
          "locale": "de-DE",
          "name": "SUCHEN"
        },
        {
          "locale": "el-GR",
          "name": "SEARCH"
        },
        {
          "locale": "en-AU",
          "name": "SEARCH"
        },
        {
          "locale": "en-GB",
          "name": "SEARCH"
        },
        {
          "locale": "en-US",
          "name": "SEARCH"
        },
        {
          "locale": "es-ES",
          "name": "HALLAR"
        },
        {
          "locale": "et-EE",
          "name": "SEARCH"
        },
        {
          "locale": "fi-FI",
          "name": "KÄY.LÄPI"
        },
        {
          "locale": "fr-FR",
          "name": "CHERCHE"
        },
        {
          "locale": "he-IL",
          "name": "SEARCH‏"
        },
        {
          "locale": "hr-HR",
          "name": "SEARCH"
        },
        {
          "locale": "hu-HU",
          "name": "SZÖVEG.KERES"
        },
        {
          "locale": "id-ID",
          "name": "SEARCH"
        },
        {
          "locale": "it-IT",
          "name": "RICERCA"
        },
        {
          "locale": "ja-JP",
          "name": "SEARCH"
        },
        {
          "locale": "ko-KR",
          "name": "SEARCH"
        },
        {
          "locale": "lt-LT",
          "name": "SEARCH"
        },
        {
          "locale": "lv-LV",
          "name": "SEARCH"
        },
        {
          "locale": "nb-NO",
          "name": "SØK"
        },
        {
          "locale": "nl-NL",
          "name": "VIND.SPEC"
        },
        {
          "locale": "pl-PL",
          "name": "SZUKAJ.TEKST"
        },
        {
          "locale": "pt-BR",
          "name": "LOCALIZAR"
        },
        {
          "locale": "pt-PT",
          "name": "PROCURAR"
        },
        {
          "locale": "ro-RO",
          "name": "SEARCH"
        },
        {
          "locale": "ru-RU",
          "name": "ПОИСК"
        },
        {
          "locale": "sk-SK",
          "name": "SEARCH"
        },
        {
          "locale": "sl-SI",
          "name": "SEARCH"
        },
        {
          "locale": "sr-Latn-RS",
          "name": "SEARCH"
        },
        {
          "locale": "sv-SE",
          "name": "SÖK"
        },
        {
          "locale": "th-TH",
          "name": "SEARCH"
        },
        {
          "locale": "tr-TR",
          "name": "ARA"
        },
        {
          "locale": "uk-UA",
          "name": "SEARCH"
        },
        {
          "locale": "vi-VN",
          "name": "Các hàm SEARCH"
        },
        {
          "locale": "zh-CN",
          "name": "SEARCH"
        },
        {
          "locale": "zh-HK",
          "name": "SEARCH"
        },
        {
          "locale": "zh-TW",
          "name": "SEARCH"
        }
      ],
      "signature": {
        "display": "SEARCH(find_text, within_text, [start_num])",
        "parameters": [
          {
            "description": null,
            "name": "find_text",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "within_text",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "start_num",
            "optional": true,
            "repeats": false
          }
        ],
        "trailing_repeats": false
      },
      "surface_name": "SEARCH",
      "version_marker": null,
      "xlcall": null
    },
    "microsoft": "https://support.microsoft.com/en-us/office/search-function-9ab04538-0e55-4719-a72e-b6f54513b495"
  },
  "try_it": {
    "formula": "=SEARCH(\"BOOK\",\"handbook\")"
  },
  "url": "https://handbook.dnacalc.com/functions/SEARCH.html",
  "variants": [
    {
      "code": "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text_functions.rs#L303-L303",
      "difference": "A surrogate pair counts as two characters.",
      "error_bound": null,
      "evidence": [
        {
          "commit": "",
          "locator": "",
          "note": "",
          "path": "https://support.microsoft.com/en-gb/excel/compatibility-versions",
          "repository": ""
        }
      ],
      "key": {
        "cpu": "Any",
        "excel_build_range": "Compatibility Version 1, all Excel versions"
      },
      "kind": "environment",
      "name": "SEARCH at Compatibility Version 1",
      "onecalc_udf": null,
      "state": "selected",
      "state_summary": "Implemented; selected by the workbook's Compatibility Version."
    },
    {
      "code": "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text_functions.rs#L303-L303",
      "difference": "A surrogate pair counts as one character. Characters combined from several code points still count separately.",
      "error_bound": null,
      "evidence": [
        {
          "commit": "",
          "locator": "",
          "note": "",
          "path": "https://support.microsoft.com/en-gb/excel/compatibility-versions",
          "repository": ""
        }
      ],
      "key": {
        "cpu": "Any",
        "excel_build_range": "Compatibility Version 2, Excel for Microsoft 365"
      },
      "kind": "environment",
      "name": "SEARCH at Compatibility Version 2",
      "onecalc_udf": null,
      "state": "selected",
      "state_summary": "Implemented; selected by the workbook's Compatibility Version."
    }
  ],
  "versions": []
}