Dynamic SOSL is a Salesforce Object Search Language statement constructed as a string at runtime and executed with Search.query(). It’s the SOSL counterpart to dynamic SOQL: instead of writing a static [FIND 'Acme' IN ALL FIELDS RETURNING Account, Contact], you build the search term, the field scope, and the returning clause from variables.
Static vs dynamic SOSL
// Static — compiled and bound at deploy time
List<List<SObject>> results = [
FIND 'Acme' IN ALL FIELDS
RETURNING Account(Id, Name), Contact(Id, Email)
];
// Dynamic — built and bound at runtime
String term = 'Acme';
String search = 'FIND \'' + String.escapeSingleQuotes(term) + '\'' +
' IN ALL FIELDS' +
' RETURNING Account(Id, Name), Contact(Id, Email)';
List<List<SObject>> results = Search.query(search);
Search.query() always returns a List<List<SObject>> — one inner list per object in the RETURNING clause, in the order you listed them.
When to use SOSL at all
Reach for SOSL — static or dynamic — when:
- You need to search across multiple objects in one call.
- You’re matching against full-text indexed fields (Name, Text, Long Text Area, Email, Phone, etc.) and want the platform’s search engine — fuzzy matching, stemming, synonyms — instead of
LIKE. - The search term spans the org-wide global search box experience.
SOSL is slower than a targeted SOQL with a LIKE on an indexed field for a single object. For one-object lookups, SOQL almost always wins. For “find anything that mentions Acme across Accounts, Contacts, and Cases” — SOSL is the tool.
Dynamic SOSL injection
Same problem as SOQL: concatenating user input is a vulnerability.
// VULNERABLE
String search = 'FIND \'' + userInput + '\' IN ALL FIELDS RETURNING Account(Id)';
List<List<SObject>> r = Search.query(search);
Defenses, in order of preference:
String.escapeSingleQuotes(input)— the primary defense for theFINDterm itself.- Bind variables — SOSL bind support arrived later than SOQL’s; the API is
Search.find()with bind syntax, and many shops still escape manually. - Validate object and field names against
Schema.getGlobalDescribe()— they can’t be parameterised, so they must be checked.
A useful pattern: configurable cross-object search
public static Map<String, List<SObject>> findAll(String term,
List<String> objectNames,
Map<String, List<String>> fieldsByObject) {
String safeTerm = String.escapeSingleQuotes(term);
List<String> returning = new List<String>();
for (String obj : objectNames) {
List<String> fields = fieldsByObject.get(obj);
returning.add(obj + '(' + String.join(fields, ', ') + ')');
}
String search = 'FIND \'' + safeTerm + '*\'' +
' IN ALL FIELDS' +
' RETURNING ' + String.join(returning, ', ') +
' LIMIT 50';
List<List<SObject>> results = Search.query(search);
Map<String, List<SObject>> byObject = new Map<String, List<SObject>>();
for (Integer i = 0; i < objectNames.size(); i++) {
byObject.put(objectNames[i], results[i]);
}
return byObject;
}
Note the trailing * on safeTerm — that’s SOSL’s wildcard (matches anything after the prefix). Salesforce search also supports ? for single-character wildcards.
Governor-limit angle
SOSL has its own per-transaction quota — 20 SOSL queries synchronously, 20 async. Each Search.query() call counts as one. Heavy global-search-style features should be debounced on the client and cached when possible.
What interviewers are really looking for
The basic answer names Search.query(). The senior answer covers: (1) SOSL is the right tool for cross-object full-text search, (2) dynamic SOSL has the same injection risk as dynamic SOQL and requires String.escapeSingleQuotes(), (3) it returns List<List<SObject>> in the order of the RETURNING clause, (4) the 20-query governor limit is separate from SOQL’s 100. Mention * and ? wildcards and you’ve shown end-to-end familiarity.
Verified against: SOQL/SOSL Reference — Dynamic SOSL, Search.query(). Last reviewed 2026-05-17.