QUESTPIE
Build Your WorkspaceViews

Form Views

Configure edit forms — sections, sidebar, tabs, grid layouts, and reactive visibility.

Form views control the edit interface for collections and globals. Configure them with .form().

Basic Form

.form(({ v, f }) => v.collectionForm({}))

Without config, the form renders all fields in a single column.

Sections

Group fields into labeled sections:

.form(({ v, f }) =>
  v.collectionForm({
    fields: [
      {
        type: "section",
        label: { en: "Contact Information" },
        layout: "grid",
        columns: 2,
        fields: [f.name, f.email, f.phone],
      },
      {
        type: "section",
        label: { en: "Profile" },
        fields: [f.bio],
      },
      {
        type: "section",
        label: { en: "Services" },
        fields: [f.services],
      },
    ],
  }),
)

Section Options

OptionTypeDescription
type"section"Required
labelstring | i18nSection heading
descriptionstring | i18nSection description
layout"grid" | "stack"Field layout
columnsnumberGrid columns (with layout: "grid")
fieldsField[]Fields in this section

Place fields in a sidebar panel:

.form(({ v, f }) =>
  v.collectionForm({
    sidebar: {
      position: "right",
      fields: [f.isActive, f.avatar, f.status],
    },
    fields: [
      // Main content sections...
    ],
  }),
)

Computed Fields

Auto-compute field values from other fields:

.form(({ v, f }) =>
  v.collectionForm({
    fields: [
      {
        type: "section",
        fields: [
          f.name,
          {
            field: f.slug,
            compute: {
              handler: ({ data }) => {
                if (data.name && !data.slug) {
                  return slugify(data.name);
                }
                return undefined;
              },
              deps: ({ data }) => [data.name, data.slug],
              debounce: 300,
            },
          },
        ],
      },
    ],
  }),
)
OptionTypeDescription
handler(ctx) => valueCompute function
deps(ctx) => any[]Reactive dependencies
debouncenumberDebounce in milliseconds

Conditional Visibility

Show or hide fields based on other field values:

.form(({ v, f }) =>
  v.collectionForm({
    fields: [
      {
        type: "section",
        fields: [
          f.cancelledAt,
          {
            field: f.cancellationReason,
            hidden: ({ data }) => data.status !== "cancelled",
          },
        ],
      },
    ],
  }),
)

See Visibility for more patterns.

Real-World Example

From the barbershop — barbers collection with sidebar, grid layout, sections, and computed slug:

collections/barbers.ts
.form(({ v, f }) =>
  v.collectionForm({
    sidebar: {
      position: "right",
      fields: [f.isActive, f.avatar],
    },
    fields: [
      {
        type: "section",
        label: { en: "Contact Information" },
        layout: "grid",
        columns: 2,
        fields: [
          f.name,
          {
            field: f.slug,
            compute: {
              handler: ({ data }) => {
                if (data.name && !data.slug?.trim()) {
                  return slugify(data.name);
                }
                return undefined;
              },
              deps: ({ data }) => [data.name, data.slug],
              debounce: 300,
            },
          },
          f.email,
          f.phone,
        ],
      },
      {
        type: "section",
        label: { en: "Profile" },
        fields: [f.bio],
      },
      {
        type: "section",
        label: { en: "Services" },
        fields: [f.services],
      },
      {
        type: "section",
        fields: [f.socialLinks],
      },
      {
        type: "section",
        fields: [f.workingHours],
      },
    ],
  }),
)

On this page