Skip to main content
When you use 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.
The cross-file analysis follows barrel re-exports (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:
TierConditionResult
Fully optimizedCompiler resolves all field accessselect injected with only used fields
Opaque fallbackCompiler can’t analyze a consumerNo select injected — all fields fetched
User-narrowedDeveloper manually narrows propsselect injected based on narrowed access
Most of the time you’re fully optimized — zero effort. The rest of this guide covers the opaque fallback and how to narrow your way back to optimization.

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 skips select 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 into node_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:
This is rare in practice, but can happen with complex barrel file structures.

Render callbacks and query indirection

Direct expressions and .map() callbacks on query data are fully optimized — the compiler traces field access through them automatically:
However, when query data passes through an indirection layer — a component callback that renames the parameter — the compiler loses the connection between the callback parameter and the original query variable:
This only affects field selection, not reactivity. Your app works correctly — data updates reactively as expected. The query just fetches all fields instead of only the ones you use. Prefer direct .map() on the query result for optimal field selection:
For animated lists, use <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:
The compiler now sees 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 importsimport { TaskCard } from './task-card'
  • Barrel re-exportsimport { TaskCard } from './components' where components/index.ts re-exports from ./task-card
  • Star re-exportsexport * from './task-card'
  • Renamed exportsexport { InternalCard as TaskCard } from './task-card'
  • Transitive chains — imports that go through multiple levels of re-exports
The compiler follows all of these automatically. This guide only applies to third-party npm packages, dynamic imports, and code patterns that can’t be statically analyzed.

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:
This tells the compiler to skip field selection for this query regardless of the analysis result. The query always fetches all exposed fields.

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.