SOSL (Salesforce Object Search Language) is a search language that queries Salesforce's full-text search index to find records across multiple objects in a single statement. Unlike SOQL, which queries one object at a time, a single SOSL statement can return matching Accounts, Contacts, Opportunities, and custom objects simultaneously — without knowing which object a search term lives in.
SOSL vs SOQL: The Fundamental Difference
SOQL and SOSL solve different problems. Understanding which to use requires understanding how Salesforce stores and retrieves data.
| SOQL | SOSL | |
|---|---|---|
| What it queries | Database fields on one object | Full-text search index across objects |
| Objects per query | One (plus joins via relationships) | Multiple simultaneously |
| Best for | Structured data retrieval with known filters | Search-style queries across objects |
| Requires knowing object | Yes | No |
| Supports wildcard | Limited (LIKE operator) | Yes (*, ?) |
| Min search term length | No minimum | 2 characters |
| Return type in Apex | List<SObject> | List<List<SObject>> |
The return type difference is the most common source of SOSL bugs. SOSL returns a List of Lists — one inner list per object in the RETURNING clause. You must index into the outer list to access each object's results.
Basic SOSL Syntax
The minimal SOSL statement has three components: the FIND clause, the search group, and the RETURNING clause.
// Minimal SOSL List<List<SObject>> results = [ FIND 'Acme' IN ALL FIELDS RETURNING Account(Id, Name), Contact(Id, FirstName, LastName) ];
The FIND clause contains the search term. The search group (IN ALL FIELDS) determines which fields are searched. The RETURNING clause lists the objects to return results from, with optional field selection.
// Accessing the results — one List per RETURNING object List<Account> accounts = (List<Account>) results[0]; List<Contact> contacts = (List<Contact>) results[1];
The Four Search Groups
The search group controls which fields the search index scans. Narrowing the search group improves performance.
| Search Group | Fields Searched | When to Use |
|---|---|---|
ALL FIELDS | Text, phone, email, URL fields | General search — widest coverage |
NAME FIELDS | Name fields only | Looking for a specific record name — fastest |
EMAIL FIELDS | Email fields only | Email-specific lookup |
PHONE FIELDS | Phone fields only | Phone-specific lookup |
// Fastest lookup when searching by name [FIND 'Acme' IN NAME FIELDS RETURNING Account(Id, Name)] // Email-specific search [FIND '[email protected]' IN EMAIL FIELDS RETURNING Contact(Id, FirstName, LastName, Email)]
Wildcards in SOSL
SOSL supports two wildcard characters. These work inside the FIND clause search term and allow partial matching.
- * (asterisk): matches zero or more characters —
'Acm*'matches Acme, Acmon, Acme Technologies - ? (question mark): matches exactly one character —
'Ace?'matches Acme but not Acmon
// Prefix wildcard — finds anything starting with 'sf' [FIND 'sf*' IN ALL FIELDS RETURNING Account] // Wildcards only work at end of terms, not start // '*force' is NOT supported — would need SOQL LIKE '%force%'
Wildcards cannot appear at the beginning of a search term. '*force' is invalid. Use SOQL's LIKE operator with a leading % when you need suffix or infix matching.
The RETURNING Clause in Detail
The RETURNING clause accepts standard SOQL-style clauses per object: field selection, WHERE, ORDER BY, and LIMIT.
List<List<SObject>> results = [ FIND 'tech*' IN ALL FIELDS RETURNING Account( Id, Name, Industry, AnnualRevenue WHERE IsActive__c = true ORDER BY AnnualRevenue DESC LIMIT 10 ), Contact( Id, FirstName, LastName, Email WHERE Department = 'Engineering' ) ];
The WHERE clause in RETURNING filters within the search results — it does not affect what SOSL searches. SOSL finds matching records first; WHERE then filters that result set.
SOSL Governor Limits
SOSL shares the 100-query-per-transaction limit with SOQL. Additional SOSL-specific limits:
- Maximum 2,000 records returned per SOSL query
- Default limit of 200 records per object in RETURNING (override with LIMIT)
- Minimum search term: 2 characters
- Single search term only — no multi-term OR logic in FIND (use separate queries or OR/AND operators within the term)
// AND/OR within SOSL search term [FIND 'Acme OR Salesforce' IN NAME FIELDS RETURNING Account] [FIND 'Acme AND Technologies' IN ALL FIELDS RETURNING Account]
Dynamic SOSL
When the search term comes from user input, build the SOSL string dynamically using Search.query() instead of a static literal. This avoids injection risks and allows runtime construction.
String searchTerm = 'Acme*'; // from user input // Escape special characters before interpolating String escaped = String.escapeSingleQuotes(searchTerm); List<List<SObject>> results = Search.query( 'FIND \'' + escaped + '\' IN ALL FIELDS ' + 'RETURNING Account(Id, Name)' );
Never interpolate unsanitised user input directly into a SOSL string. Use String.escapeSingleQuotes() before injecting user-provided search terms into dynamic SOSL.
SOSL Without a RETURNING Clause
Omitting RETURNING causes SOSL to search all searchable objects and return Id and Name only. This is rarely what you want in production code — it can return thousands of records from unexpected objects.
// Avoid in production — returns everything from all searchable objects [FIND 'Acme' IN ALL FIELDS] // Always specify RETURNING in production code [FIND 'Acme' IN ALL FIELDS RETURNING Account(Id, Name), Contact(Id, Name)]
Practical Pattern: Global Search Handler
The canonical use case for SOSL is a global search feature where a user types a term and the UI should return matching records across object types.
public class GlobalSearchController { public static Map<String,List<SObject>> search(String term) { Map<String,List<SObject>> resultMap = new Map<String,List<SObject>>(); // Minimum 2 chars required if (String.isBlank(term) || term.length() < 2) return resultMap; String escaped = String.escapeSingleQuotes(term + '*'); List<List<SObject>> raw = Search.query( 'FIND \'' + escaped + '\' IN ALL FIELDS ' + 'RETURNING ' + 'Account(Id, Name LIMIT 5), ' + 'Contact(Id, FirstName, LastName LIMIT 5), ' + 'Opportunity(Id, Name LIMIT 5)' ); resultMap.put('accounts', raw[0]); resultMap.put('contacts', raw[1]); resultMap.put('opportunities', raw[2]); return resultMap; } }
SOSL in Test Classes
SOSL in test methods does not use the real search index — it returns empty results unless you explicitly seed the test data using Test.setFixedSearchResults().
@isTest static void testSearch() { Account a = new Account(Name = 'Acme Technologies'); insert a; // Tell the test framework what SOSL should return Test.setFixedSearchResults(new List<Id>{ a.Id }); List<List<SObject>> results = [ FIND 'Acme' IN ALL FIELDS RETURNING Account ]; System.assertEquals(1, results[0].size()); }
Forgetting Test.setFixedSearchResults() is the most common cause of SOSL tests passing with zero assertions. Always seed the search results explicitly in test context.
When to Use SOSL — The Decision Rule
Use SOSL when:
- You don't know which object a search term will be found in
- You need to search across multiple objects in a single user action
- You're building a search or typeahead feature
- You want full-text, wildcard-style matching across text fields
Use SOQL when:
- You know exactly which object you're querying
- You need to filter by specific field values (status, date, lookup)
- You need aggregate functions (COUNT, SUM, GROUP BY)
- You're querying related records via relationship traversal
Related: SOQL Aggregate Functions covers how to aggregate SOQL results with COUNT, SUM, AVG, MIN, MAX, and GROUP BY — the complement to SOSL for structured data retrieval.