Skip to content

$select and $expand

$select

The $select query option specifies which properties to include in the response.

Basic usage

GET /odata/Products?$select=name,price

Returns only the name and price properties for each entity.

Select all (default)

When $select is omitted, all declared properties are included.

GET /odata/Products

This 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 $hidden array 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=passengers

Response:

json
{
  "value": [
    {
      "id": 1,
      "origin": "lhr",
      "passengers": [
        {"id": 1, "name": "Alice"},
        {"id": 2, "name": "Bob"}
      ]
    }
  ]
}

Multiple expansions

GET /odata/Flights?$expand=passengers,originAirport

Nested 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:

OptionPurpose
$selectSelect properties within the expanded entity
$filterFilter expanded entities
$orderbySort expanded entities
$topLimit expanded entities
$skipOffset expanded entities
$countCount expanded entities
$expandNested 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.

OData: MIT | Core: BSL 1.1 | SDK: Commercial License