Input field selection
Functions can declare exactly which REST input fields they need on every invocation. The dispatcher trims the assembled input to that subtree before invoking your WASM module — your function sees a smaller, faster, exactly-shaped object. This is purely REST: you author a plain JSON tree of booleans matching the input contract for your function type. There is no GraphQL involved.Why opt in
- Faster dispatch. Smaller objects serialize to JSON faster; smaller
payloads cross the WASM ABI faster. A discount function that reads
cart.totalPriceand ignores everything else processes a 12 KB cart in ~200 bytes of JSON. - Smaller WASM payloads. Functions doing simple work don’t have to scan past 80% of the input. Each unneeded field costs JSON parse time in the QuickJS runtime that backs Javy dynamic-mode WASM.
- Explicit dependencies. Reading the manifest makes it obvious which fields the function actually reads. Future-you will thank present-you.
- Tighter security. Even though your function runs in a sandboxed WASM module, projecting away fields you don’t need limits the blast radius of accidental logging or output payloads.
Manifest
inputFields is a sibling field on each entry in extensions.functions:
Semantics
The tree mirrors the runtime input shape for your function type (see each function’s reference page). Leaves describe whether to keep a key; nested objects recurse.| Leaf value | Meaning |
|---|---|
true | Include the field at this position. If it’s an array or object, copy the entire subtree. |
{ ... } (plain object) | Recurse — include only the listed sub-fields of this value. |
false | Silently drop the key (same as omitting). Useful when generating manifests programmatically. |
| omitted key | Drop the field entirely. |
inputFields missing or {} | Full input is passed (backward compatible). |
null / undefined | Same as missing — no projection requested. |
Arrays project per-item
cart.lines is projected to
{ id, quantity, merchandise: { sku } }. Nested objects inside array
items recurse the same way. The projector walks every element, so:
- An empty array stays an empty array.
- A 200-item array projects 200 times — projection itself is O(n) over the array length.
Scalars at a sub-projection position
If your map declarescart: { totalPrice: { ... } } but totalPrice is
actually a number, the projector passes it through unchanged (it
can’t recurse into a scalar). This is forgiving by design: apps that
probe an optional object that some shops have flattened to a scalar
won’t break.
Missing keys
A key that’s in yourinputFields but missing on the runtime input is
omitted from the output (no undefined written). Reading
input.customer?.tags in your function code stays the right pattern.
Examples per function type
Each function type has its own input contract — these examples show typical projections.Discount (reads totals + codes, optionally the destination address)
shippingAddress (address1, address2, city, province, country, zip) is
available to discount functions for location-aware rules — e.g. ZIP-keyed
US sales-tax adjustments. Fields are empty strings until the customer
fills the address step at checkout.
Shipping rate (reads cart, destination)
Payment customization (reads payment methods, cart total)
Cart transform (reads lines + attributes)
Delivery customization (reads cart, options)
Order validation (reads cart, customer, address)
Fulfillment constraints (reads lines + attributes + location catalogue)
Local / pickup point options (reads cart + address)
Validation rules
The install endpoint validates theinputFields tree before persisting
the function. Errors are returned as HTTP 400 with a path-qualified
message so you know which key is wrong:
| Rule | Failure example | Error message |
|---|---|---|
| The root must be a plain object (or omitted). | "inputFields": "all" | inputFields must be a plain object, got string |
Every leaf must be true, false, or a nested plain object. | "id": 1 | inputFields.cart.lines.id must be true/false or a nested object (got number) |
| Arrays are not allowed as values. | "lines": ["id"] | inputFields.cart.lines must be a plain object, got array |
null is not allowed as a value. | "customer": null | inputFields.customer must be true/false or a nested object (got null) |
Backward compatibility
Manifests withoutinputFields (or with an empty {}) keep working
unchanged — they receive the full input. There is no version flag to
set. You can add inputFields to an existing manifest without changing
your function’s behaviour, as long as the projection includes every
field your function reads.
If you ship a new function version that reads a new field but forget to
add it to inputFields, the field arrives as undefined to your
function. Bump the projection at the same time as the field-read.
How the platform consults the projection at dispatch time
The flow inside the platform when dispatching a function is:- Per-function projection. Each function gets its own projected
input — different apps with different
inputFieldssee different trees of the same dispatch. - Empty / missing projection is a no-op. The projector returns the
input unchanged. Manifests without
inputFieldscost zero.
projectByFieldMap) lives in
apps/functions/input-field-projection.util.ts and is covered by
input-field-projection.util.spec.ts in the backend tests. The
validator (validateFieldMap in the same file) is the same one the
install endpoint calls — install-time validation and dispatch-time
projection share their understanding of what’s valid.
Performance impact
Concrete numbers from typical carts:| Cart size | No projection | With minimal projection |
|---|---|---|
| 1 line | ~3 KB input | ~200 B |
| 10 lines | ~15 KB input | ~600 B |
| 100 lines | ~120 KB input | ~5 KB |
Best practices
- Project to exactly what you read. Don’t over-project “just in case” — every extra field is a future maintenance question.
- Pin the version. Every time you change which fields your function
reads, bump the function version and update
inputFieldsin the same commit. - Use
truefor opaque subtrees. If you don’t know the inner shape ofmerchandise.metafieldsand want everything under it, just write"metafields": true— the entire subtree is copied as-is. - Don’t project
cart.linesto a scalar set if you also read per-line metafields. Metafields live undermerchandise.metafieldsper line, so recurse intomerchandiseeven if you only read one metafield path.
See also
- Network access — the other manifest-level field that controls function runtime behaviour.
- Function input contracts: Discount, Shipping rate, Payment customization, Cart transform, Delivery customization, Order validation, Fulfillment constraints.