query() with the generated SDK, the Vertz compiler automatically analyzes which fields your components access and injects a select parameter — so the server only returns the columns you actually use. No manual field picking, no over-fetching.
How it works
The compiler performs compile-time static analysis at two levels: Single file: The compiler scans each.tsx file for query() calls, then tracks which fields are accessed on the query result throughout the file.
id is always included — it’s required for caching and optimistic updates.
Cross file: When you pass query data to a child component via props, the compiler follows the import to analyze what fields the child accesses, then merges those back into the parent’s select.
export { Foo } from './bar'), star re-exports (export * from './components'), renamed exports, and transitive chains — as long as the imports stay within your codebase.
The three tiers
Auto field selection operates in three tiers:| Tier | Condition | Result |
|---|---|---|
| Fully optimized | Compiler resolves all field access | select injected with only used fields |
| Opaque fallback | Compiler can’t analyze a consumer | No select injected — all fields fetched |
| User-narrowed | Developer manually narrows props | select injected based on narrowed access |
What triggers opaque fallback
The compiler marks field access as opaque when it can’t statically determine which fields are used. When any access is opaque, the query skipsselect injection entirely and fetches all fields. This is the safe default — missing data is worse than extra data.
Third-party components
The most common trigger. When you pass query data to a component from an npm package, the compiler can’t follow the import intonode_modules:
Dynamic imports
Circular re-exports
When two modules re-export from each other (directly or through a chain), the compiler detects the cycle and bails out — treating the component as unresolvable:Render callbacks and query indirection
Direct expressions and.map() callbacks on query data are fully optimized — the compiler traces field access through them automatically:
.map() on the query result for optimal field selection:
<List animate> which uses .map() children — field selection works automatically.
Spread, dynamic access, and function calls
These patterns within your own code also trigger opaque fallback:How to optimize at opaque boundaries
When you hit an opaque boundary, narrow the data in the parent by constructing an object with only the fields the child needs:issue.title and issue.number accessed directly in the parent file — so it knows exactly which fields to select. The third-party component receives the same data shape, but the query only fetches what’s needed.
Multiple opaque consumers
If the same query feeds multiple opaque components, narrow each one independently:When this doesn’t matter
Auto field selection resolves fields automatically for components within your own codebase that use standard imports. You don’t need to do anything special for:- Direct imports —
import { TaskCard } from './task-card' - Barrel re-exports —
import { TaskCard } from './components'wherecomponents/index.tsre-exports from./task-card - Star re-exports —
export * from './task-card' - Renamed exports —
export { InternalCard as TaskCard } from './task-card' - Transitive chains — imports that go through multiple levels of re-exports
Manual control
Providing your own select
If you pass a select parameter manually, the compiler respects it and skips auto field selection for that query:
Opting out with @vertz-select-all
If you want to explicitly fetch all fields — for example, when a query feeds many opaque consumers and narrowing isn’t practical — use the pragma comment:
Next steps
Data Fetching
The full
query() API — caching, refetching, SSR, optimistic updates.Fields, Relations & Filters
Control which fields, relations, and filters are exposed in your entity API.
Compiler Plugin
How the Vertz compiler transforms JSX and signals.