$select and $expand
$select
The $select query option specifies which properties to include in the response.
Basic usage
GET /odata/Products?$select=name,priceReturns only the name and price properties for each entity.
Select all (default)
When $select is omitted, all declared properties are included.
GET /odata/ProductsThis is bad practice. Omitting $select returns every declared property, which may include sensitive data such as password hashes, API tokens, personal identifiers, or internal flags that should never reach the client. Always use an explicit $select to request only the properties the client actually needs.
Note: The package intentionally does not provide a property blacklist mechanism. Application scopes vary too widely for a one-size-fits-all filter — what counts as sensitive depends entirely on the domain. Instead, control exposure at the schema level: only declare properties in your entity type that are safe to serve. If a column should never leave the server, don't add it to the Edm. Use Eloquent's
$hiddenarray or a custom resolver to keep internal columns out of the entity type definition.
Wildcard
GET /odata/Products?$select=*Equivalent to omitting $select.
SQL-level optimization
The resolver applies $select at the SQL level when possible, reducing the amount of data fetched from the database. When $compute is present, SQL-level select is skipped (computed expressions may reference any column), and projection happens at the response level instead.
$expand
The $expand query option includes related entities inline in the response.
Basic usage
GET /odata/Flights?$expand=passengersResponse:
{
"value": [
{
"id": 1,
"origin": "lhr",
"passengers": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
}
]
}Multiple expansions
GET /odata/Flights?$expand=passengers,originAirportNested query options
You can apply query options to expanded navigation properties:
GET /odata/Flights?$expand=passengers($select=name;$top=5;$filter=age gt 18;$orderby=name asc)Supported nested options:
| Option | Purpose |
|---|---|
$select | Select properties within the expanded entity |
$filter | Filter expanded entities |
$orderby | Sort expanded entities |
$top | Limit expanded entities |
$skip | Offset expanded entities |
$count | Count expanded entities |
$expand | Nested expand (see below) |
Note: semicolons (;) separate nested options within the parentheses.
Nested $expand
Expand supports arbitrary nesting depth:
GET /odata/Projects(64)?$expand=contact_project($expand=contact,trade)This expands contact_project on the Project, and within each ContactProject expands both contact and trade. The Eloquent resolver translates this to dot-notation eager loading (contact_project.contact, contact_project.trade).
Virtual navigation properties
Navigation properties don't have to map to real Eloquent relations. A VirtualExpandResolverInterface implementation can provide computed data as an expand -- for example, KPIs aggregated from multiple tables:
GET /odata/Users(11)?$expand=kpis($filter=date eq 2024-01-15)The virtual resolver receives the parent entity's data and the expand's nested filter, computes the result, and returns it as a collection. See Custom Resolvers for the full API.
Virtual and real expands can be mixed freely:
GET /odata/Users(11)?$expand=position,budgets,kpis($filter=date eq 2024-01-15)Requirements for $expand
- The entity type must declare a navigation property for the expanded name
- The entity set must have a navigation property binding mapping the nav property to a target entity set
- For Eloquent relations: the resolver must be
EloquentEntitySetResolver(SQL resolver does not support expand) - For virtual expands: the target entity set's resolver must implement
VirtualExpandResolverInterface
Expand and Eloquent
Expand maps to Eloquent eager loading:
- Simple expand →
->with(['navName']) - Constrained expand →
->with(['navName' => function ($q) { ... }]) - Nested expand →
->with(['navName.childName'])
The PK and FK columns are always included in nested selects so Eloquent can match related rows to their parents.