Fields

File Field

The file field renders a file upload input in the admin UI. It can reference an upload-enabled collection (recommended) or store a plain URL string (legacy mode). Use it for PDFs, documents, spreadsheets, and any non-image file type. For images specifically, use f.image() instead.

Config options

OptionTypeDefaultDescription
requiredbooleanfalseMakes the field required in both the schema and the admin UI.
readOnlybooleanfalseRenders the input as disabled.
relationTostring—Slug of the upload collection to reference (e.g. "documents"). When set, uses the full upload system.
acceptstring[]—Accepted MIME types (e.g. ["application/pdf"]).
maxSizenumber—Maximum file size in bytes.
displayPreviewbooleantrueShow a file preview/icon in the admin UI.
hasManybooleanfalseAllow multiple file uploads.
labelstring | (data) => stringAuto-generatedCustom label for the admin UI.
descriptionstring—Help text displayed below the label.
conditionFieldCondition—Conditionally show or hide this field.
sidebarbooleanfalsePlace this field in the document sidebar.

Convex validator

  • With relationTo: delegates to f.upload() internally, stores as v.string() (file record ID) or v.array(v.string()) if hasMany.
  • Without relationTo (legacy): stores as v.string() (plain URL) or v.array(v.string()) if hasMany.

Example usage

import { f, defineVextroCollection } from "vextro";

export const resources = defineVextroCollection({
  slug: "resources",
  label: "Resources",
  collectionType: "content",
  tableName: "resources",
  fields: {
    title: f.text({ required: true }),
    // Upload collection integration (recommended)
    attachment: f.file({
      relationTo: "documents",
      accept: ["application/pdf", "application/msword"],
      maxSize: 25 * 1024 * 1024, // 25MB
    }),
    // Multiple files
    supportingDocs: f.file({
      relationTo: "documents",
      hasMany: true,
    }),
  },
});

Difference from upload and image

FieldPurposeDefault MIME filter
f.upload()Generic upload with explicit relationTo (required)None
f.image()Image uploads with image/* filterimage/*
f.file()Non-image files, optional relationToNone

When relationTo is provided, f.file() delegates internally to f.upload(). The key difference is that f.file() does not default to image MIME types and can also work in legacy mode (plain string storage) when relationTo is omitted.

Upload collection requirement

To use the upload system, you need an upload-enabled collection:

export const documents = defineVextroCollection({
  slug: "documents",
  label: "Documents",
  collectionType: "config",
  tableName: "documents",
  upload: true, // enables upload handling
  fields: {
    title: f.text(),
    description: f.textarea(),
  },
});
Previous
SEO
Next
Tabs