Features
Conditional Fields
Any field in Vextro can be conditionally shown or hidden based on sibling field values. Conditional fields are always optional in the Convex schema; the required validation only applies when the field is visible.
Basic usage
import { f, defineVextroCollection } from "vextro";
export const pages = defineVextroCollection({
slug: "pages",
label: "Pages",
collectionType: "content",
tableName: "pages",
fields: {
linkType: f.select({ options: ["internal", "external"], required: true }),
internalPage: f.id("pages", {
condition: { field: "linkType", equals: "internal" },
}),
externalUrl: f.url({
required: true,
condition: { field: "linkType", equals: "external" },
}),
},
}); Condition operators
| Operator | Example | Description |
|---|---|---|
equals | { field: "status", equals: "published" } | Exact match. |
notEquals | { field: "type", notEquals: "hidden" } | Not equal. |
in | { field: "role", in: ["admin", "editor"] } | Value in set. |
notIn | { field: "status", notIn: ["trashed"] } | Value not in set. |
exists | { field: "image", exists: true } | Has truthy value. |
greaterThan | { field: "price", greaterThan: 0 } | Numeric greater than. |
lessThan | { field: "qty", lessThan: 100 } | Numeric less than. |
contains | { field: "title", contains: "sale" } | Substring match. |
matches | { field: "sku", matches: "^PRD-\\d+" } | Regex match. |
Logical combinators
Combine conditions with and, or, and not:
advancedField: f.text({
condition: {
and: [
{ field: "isAdvanced", equals: true },
{ field: "userRole", in: ["admin", "editor"] },
],
},
}),
standardOnly: f.text({
condition: { not: { field: "isPremium", equals: true } },
}), Custom functions
For complex logic, pass a function that receives named arguments (evaluated client-side only):
complexField: f.text({
condition: ({ siblingData }) => Number(siblingData.price ?? 0) > 1000,
}), The function receives three arguments:
| Argument | Type | Description |
|---|---|---|
data | Record<string, unknown> | Full document data from root. |
siblingData | Record<string, unknown> | Sibling field values at the current nesting level. |
parentTree | Array<{ fieldType, id }> | Ancestor containers from root to current level. |
Accessing parent data in nested arrays
When a field is deeply nested inside arrays, parentTree provides the IDs of each ancestor array row. This lets you look up values several levels up without manually traversing the document.
// Inside a nested array: sections[].items[].fields
discountLabel: f.text({
condition: ({ data, parentTree }) => {
// parentTree might be:
// [{ fieldType: "array", id: "sections_row_abc" },
// { fieldType: "array", id: "items_row_xyz" }]
//
// Use the first parent's ID to find the section in the
// full document, then check a value on that section row.
const sectionId = parentTree[0]?.id;
const sections = data.sections as Array<{ _id: string; type: string }>;
const section = sections?.find((s) => s._id === sectionId);
return section?.type === "pricing";
},
}), Typed conditions
ConditionFunctionArgs accepts two optional type parameters for data and siblingData. Pass Convex's generated Doc<TableName> type to get full type safety:
import type { Doc } from "../convex/_generated/dataModel";
import type { ConditionFunctionArgs } from "vextro";
// Typed document data
premiumField: f.text({
condition: ({ data }: ConditionFunctionArgs<Doc<"posts">>) => {
return data.price > 1000; // data.price is typed
},
}),
// Typed document + sibling row data (for nested arrays)
type ItemRow = Doc<"posts">["sections"][number]["items"][number];
nestedField: f.text({
condition: ({ data, siblingData }: ConditionFunctionArgs<Doc<"posts">, ItemRow>) => {
return siblingData.qty > 0; // siblingData.qty is typed
},
}), Both parameters default to Record<string, unknown>, so untyped usage still works without any type annotations.
Conditions also work on f.tabs(), f.row(), and f.collapsible(), controlling visibility of all contained fields at once.
Evaluation
Conditions use loose equality to handle FormData string coercion. A boolean true in the condition matches the string "true" from a form value.