Query childPath
The childPath parameter allows you to query and return only specific nested portions of your data. This is especially useful for separating public and private data, improving performance, or working with complex data structures.
How childPath works:
- Navigation: childPath navigates to a nested position in your data
- Query context: The
childvariable in your query refers to the data at that nested position - Response structure: Results include the full path with childPath, so you know which parent item matched
// Data structure:
// {
// users: {
// matt123: {
// public: { name: "Matt", age: 25, city: "NYC" },
// private: { ssn: "123-45-6789", salary: 80000 }
// },
// john456: {
// public: { name: "John", age: 30, city: "LA" },
// private: { ssn: "987-65-4321", salary: 90000 }
// }
// }
// }
// Query WITHOUT childPath - queries full user objects
query({
path: ["users"],
query: "child.public.age > 21"
}).then(response => {
console.log(response.data);
// Returns: {
// matt123: { public: {...}, private: {...} },
// john456: { public: {...}, private: {...} }
// }
// You get FULL user objects including private data
});
// Query WITH childPath - queries only public portion.
// `child` inside the query refers to the data AT childPath ("public").
// The response value MIRRORS the database shape: each match is wrapped
// in the same childPath structure, so you can read it the same way you'd
// read the original tree.
query({
path: ["users"],
childPath: ["public"],
query: "child.age > 21" // child still refers to the "public" object
}).then(response => {
console.log(response.data);
// Returns: {
// matt123: { public: { name: "Matt", age: 25, city: "NYC" } },
// john456: { public: { name: "John", age: 30, city: "LA" } }
// }
// The "public" wrapper is preserved; "private" is not present because
// the query never walked into it.
});
// Multiple childPath levels — wrapper is nested to match
query({
path: ["users"],
childPath: ["public", "address"],
query: "child.city == 'NYC'" // child refers to the "address" object
}).then(response => {
console.log(response.data);
// Returns: {
// matt123: { public: { address: { city: "NYC", state: "NY" } } }
// }
// Each childPath segment shows up in the response, in order.
});
childPath Use Cases
// Use Case 1: Return a smaller payload
// childPath narrows what is READ and SENT, not what is PERMITTED. It is a
// shape and bandwidth tool, not an authorization one — see Query authorization.
query({
path: ["users"],
childPath: ["public"],
query: "child.verified == true"
}).then(response => {
// Only the "public" subtree is walked and returned. This does not grant
// or deny anything: if the query is authorized at all, it is authorized
// for every child at that path.
displayPublicProfiles(response.data);
});
// Use Case 2: Performance - Return only needed data
// When clients only need profile info, not full user objects
query({
path: ["users"],
childPath: ["profile"],
query: "child.country == 'USA'"
}).then(response => {
// Smaller response payload, faster transmission
renderUserProfiles(response.data);
});
// Use Case 3: Complex filtering on nested arrays
// Query specific nested collections
query({
path: ["orders"],
childPath: ["items"],
query: "child.quantity > 5"
}).then(response => {
// Returns: {
// order123: { items: { itemA: {quantity: 10, ...}, ... } }
// }
// The "items" wrapper is preserved so the result mirrors the DB shape.
console.log("Orders with high-quantity items:", response.data);
});
// Use Case 4: Separating data concerns
// Different parts of your app query different data sections
query({
path: ["products"],
childPath: ["inventory"],
query: "child.stock < 10"
}).then(response => {
// Warehouse dashboard only needs inventory data
showLowStockAlert(response.data);
});
When to use childPath:
- You want to exclude certain fields from the response payload (public vs private data)
- You need to improve query performance by returning less data
- You're querying nested collections or arrays within parent objects
- Not for access control — childPath is chosen by the caller, so it restricts nothing. Authorization is the single collection-level read check described in Query authorization.
Important: When using childPath, remember that child in your query refers to the data AT the childPath position, not the root object. Adjust your query conditions accordingly.
Numeric childPath segments preserve array shape. Segments inside childPath can be non-negative integers, and they walk into arrays in your data. The response wrapper is built to match: numeric segments produce arrays, string segments produce objects.
// Data structure:
// users.matt123 = { scores: [42, 87, 99], name: "Matt" }
// users.john456 = { scores: [10, 20, 30], name: "John" }
// childPath ending at index 0 of "scores"
query({
path: ["users"],
childPath: ["scores", 0],
query: "child > 25" // child refers to the integer at scores[0]
}).then(response => {
console.log(response.data);
// Returns: {
// matt123: { scores: [42] }
// }
// The wrapper preserves the array shape from the DB.
});
// childPath ending at index 1 of "scores"
query({
path: ["users"],
childPath: ["scores", 1],
query: "child > 25"
}).then(response => {
console.log(response.data);
// Returns: {
// matt123: { scores: [null, 87] }
// }
// Index 1 is preserved. The leading slot is null because the query only
// matched scores[1] — sparse-array slots become explicit nulls when JSON
// is sent over the WebSocket. The matched value is still at index 1.
});
JSON null padding for non-zero indices: When a numeric childPath segment is greater than 0, the slots before the matched index are filled with null in transit. This keeps the index correct on the client (so response.data.matt123.scores[1] works) at the cost of leading nulls. Iterate with care, or filter null entries if your code can't tolerate them.