Features

Hierarchy & Nested Categories

Collections can define hierarchical (nested) relationships using the hierarchy configuration. This enables tree navigation in the admin listing, breadcrumb navigation in the document editor, and path resolution helpers for consuming apps.

Configuration

Add hierarchy to your collection definition with a self-referencing parent field:

import { f, defineVextroCollection } from "vextro";

export const categories = defineVextroCollection({
  label: "Categories",
  tableName: "categories",
  hierarchy: {
    parentField: "parent",
  },
  fields: {
    title: f.text({ required: true }),
    parent: f.id("categories"),
    slug: f.slug({ sourceField: "title" }),
    description: f.textarea(),
  },
});

Options

OptionTypeDefaultDescription
parentFieldstring"parent"Field name that holds the parent document reference. Must be an f.id() field referencing the same table.
maxDepthnumber10Maximum nesting depth. Prevents runaway hierarchies and protects against circular references.

Admin UI Behavior

Tree View

When a collection has hierarchy configured, the listing page defaults to a tree layout. Documents are shown as an indented tree with expand/collapse toggles.

  • Root documents (no parent) appear at the top level
  • Click the chevron to expand/collapse children
  • Use "Expand All" / "Collapse All" in the toolbar
  • Toggle between tree, list, and grid layouts using the layout switcher

When editing a document in a hierarchical collection, breadcrumbs appear above the document title showing the full ancestry path:

Root Category > Parent Category > Current Category

Each ancestor is clickable and links to its edit page.

Safety Features

Circular Reference Prevention

Vextro automatically prevents circular references when setting a document's parent:

  • A document cannot be its own parent
  • A document cannot be set as a child of one of its own descendants
  • If the maximum nesting depth would be exceeded, the save is rejected

These checks run automatically via beforeChange hooks registered when hierarchy is configured.

Orphan Prevention

Deleting a document that has children is blocked by default. You must move or delete the children first. This prevents orphaned documents in the tree.

Path Resolution

Use the resolveFullPath utility to build human-readable paths:

import { resolveFullPath } from "vextro/hierarchy";

// Build a map of all documents
const allDocs = await ctx.db.query("categories").collect();
const docsMap = new Map(allDocs.map((d) => [d._id, d]));

const path = resolveFullPath(
  documentId,
  "parent",      // parentField
  "title",       // titleField
  docsMap,
  10,            // maxDepth
  " > "          // separator
);
// "Climbing > Harnesses > Full-body"

The resolveFullPath function operates on an in-memory document map. For large collections, consider caching the map or computing paths on write via afterChange hooks.

Additional Utilities

The vextro/hierarchy subpath also exports these helpers:

ExportDescription
buildTree(documents, parentField)Builds a TreeNode[] tree from a flat document array
resolveAncestors(docId, parentField, docsMap, maxDepth)Returns ancestor chain from root to immediate parent
collectDescendantIds(docId, documents, parentField)Collects all descendant IDs (useful for exclusion filters)
resolveHierarchyConfig(config)Resolves raw config with defaults applied

Best Practices

  • Use for category-sized collections (up to ~1000 documents). The tree view loads all documents at once.
  • Set useAsTitle on hierarchy collections so tree items have meaningful labels.
  • Keep maxDepth reasonable (5-15 levels). Deeply nested hierarchies are hard to navigate.
  • Combine with slugs for URL-friendly category paths in your consuming app.
Previous
Live Editing
Next
Hooks