Fields
Relationship Field
The relationship field (created via f.id()) stores a reference to one or more documents in another Convex collection. It renders a searchable picker in the admin UI that lets editors browse and select related documents. Use this field for author references, category assignments, tag lists, or any cross-collection link.
The underlying Convex validator is v.id(tableName) for single relationships or v.array(v.id(tableName)) for multiple, wrapped in v.optional() unless marked as required.
Config options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
tableName | string | * | The Convex table to reference (first positional argument) | |
required | boolean | false | Makes the field required in the schema and admin UI | |
readOnly | boolean | false | Renders the picker as non-editable | |
placeholder | string | Placeholder text for the search input | ||
label | string | function | Field name | Custom label for the admin UI | |
description | string | Help text displayed below the field label | ||
multiple | boolean | false | Allow selecting multiple documents | |
hasMany | boolean | false | Alias for multiple | |
minItems | number | Minimum number of selections (only for multiple) | ||
maxItems | number | Maximum number of selections (only for multiple) | ||
pickerFilter | FieldCondition | Filter documents shown in the picker | ||
pickerSort | { field: string; direction?: "asc" | "desc" } | Sort order for picker results | ||
displayAs | string | ((doc) => string) | Custom display renderer for relationship values in list views and editors. When a string, uses that field name from the related document. When a function, receives the related document and returns a display string (functions are registered client-side via setRelationshipRenderers()). | ||
scopeFilter | boolean | false | Auto-filter relationship options by the active scope in multi-tenant setups. When true and the target collection is scoped, the picker only shows options matching the current scope value. | |
condition | FieldCondition | Condition for showing or hiding this field | ||
sidebar | boolean | false | Place this field in the document sidebar | |
listColumn | boolean | false | Show as a default column in list views |
Example usage
import { f, defineVextroCollection } from "vextro";
export const posts = defineVextroCollection({
slug: "posts",
label: "Posts",
collectionType: "content",
tableName: "posts",
fields: {
title: f.text({ required: true }),
author: f.id("users", {
required: true,
label: "Author",
listColumn: true,
}),
category: f.id("categories", {
required: true,
pickerFilter: { field: "status", equals: "active" },
pickerSort: { field: "name", direction: "asc" },
}),
tags: f.id("tags", {
hasMany: true,
minItems: 1,
maxItems: 10,
label: "Tags",
description: "Select between 1 and 10 tags",
}),
relatedPosts: f.id("posts", {
multiple: true,
maxItems: 5,
label: "Related Posts",
pickerFilter: { field: "status", equals: "published" },
}),
},
}); Admin options
The relationship picker displays a searchable list of documents from the target collection. The picker uses the target collection's useAsTitle field for display labels. When pickerFilter is set, the picker only shows documents matching the condition, which is useful for filtering out draft or inactive records.
For multiple relationships, selected items appear as an ordered list that supports drag-to-reorder. The minItems and maxItems constraints are validated on save.
Custom display rendering
Use displayAs to control how relationship values appear in list views and the editor. Pass a field name string to use that field from the related document, or a function for custom formatting:
// Use a specific field for display
author: f.id("users", {
displayAs: "email",
})
// Custom function (registered client-side)
author: f.id("users", {
displayAs: (doc) => `${doc.firstName} ${doc.lastName}`,
}) Client-side functions
Function values for displayAs are not serializable. They are stored as a "__custom" marker in the admin config and must be registered client-side using setRelationshipRenderers(). String values work without any client-side setup.
Scope filtering
In multi-tenant setups where collections use scope configuration, enable scopeFilter to automatically limit the picker to documents matching the active scope:
category: f.id("categories", {
scopeFilter: true,
label: "Category",
description: "Only shows categories from the current tenant",
}) When scopeFilter is true and the target collection has a scope field, the relationship picker filters options to those matching the active scope value. This prevents editors from accidentally referencing documents from other tenants or regions.
Conditional display
reviewer: f.id("users", {
label: "Reviewer",
condition: { field: "status", equals: "in_review" },
description: "Assign a reviewer for this document",
})