Binary files

Working with Files

Files in Val

Val supports generic file uploads for any file type, including videos, PDFs, audio files, and more. Files work similarly to images, but with more flexibility in file types.

Key Differences from Images

The main differences when working with files instead of images:

  • Use s.file() instead of s.image() in your schema

  • Use s.file() instead of s.image() for non-image files - the content object looks the same, but carries only path and mimeType

  • You can specify accepted file types with the accept option (e.g., accept: 'video/*', accept: '.pdf,.doc')

  • When you use accept: 'video/*', Val Studio will render it as a video with preview

Schema and Content

const mediaSchema = s.object({
  video: s.file({ accept: "video/*" }),
  document: s.file({ accept: ".pdf,.doc,.docx" }),
  audio: s.file({ accept: "audio/*" }),
  anyFile: s.file(), // Accepts any file type
});

export default c.define("/content/media.val.ts", mediaSchema, {
  video: { path: "/public/val/video.mp4", mimeType: "video/mp4" },
  document: { path: "/public/val/guide.pdf", mimeType: "application/pdf" },
  audio: { path: "/public/val/podcast.mp3", mimeType: "audio/mpeg" },
  anyFile: { path: "/public/val/data.json", mimeType: "application/json" },
});

Collections: many files in one module

s.file() defines one field. For a shared pool of files - a document library, a set of downloads - use s.files() instead. Note the plural: s.files() defines an entire module rather than a field, the same way s.images() does for images.

A collection is a record keyed by file path, whose values carry the mimeType Val read off each file. Both accept and directory are required, and no two collections may share a directory.

// content/documents.val.ts
export default c.define(
  "/content/documents.val.ts",
  s.files({
    accept: "application/pdf",
    directory: "/public/val/documents",
  }),
  {
    // Entries appear when you upload - you do not write them by hand
    "/public/val/documents/report_a1b2c.pdf": {
      mimeType: "application/pdf",
    },
  },
);

Picking from a collection

Pass the collection module to s.file() and the field becomes a picker for it, exactly as s.image(galleryVal) does for images. The value is then only the path: the mimeType is stored once, in the collection, so a field that repeats it is a validation error.

// content/page.val.ts
import documentsVal from "./documents.val";

const pageSchema = s.object({
  datasheet: s.file(documentsVal),
});

export default c.define("/content/page.val.ts", pageSchema, {
  // Only the path: the collection has the mimeType
  datasheet: { path: "/public/val/documents/report_a1b2c.pdf" },
});

Where to find collections in Val Studio

Like image galleries, collections are listed under Media in Val Studio rather than in the Explorer file tree.

Using Files in Components

When using files in your components, access the URL using the .url property:

Basic usage

import { fetchVal } from "@/val/val.rsc";
import mediaVal from "./media.val";

export default async function MediaPage() {
  const { video, document, audio } = await fetchVal(mediaVal);
  
  return (
    <div>
      <video src={video.url} controls className="w-full" />
      
      <a href={document.url} download className="btn">
        Download PDF
      </a>
      
      <audio src={audio.url} controls />
    </div>
  );
}

Using val.attrs and val.raw

For advanced use cases where you need to control visual editing attributes, you can use val.attrs() and val.raw() with the file URL:

import { val } from "@/val.config";
import { fetchVal } from "@/val/val.rsc";
import mediaVal from "./media.val";

export default async function MediaPage() {
  const { video } = await fetchVal(mediaVal);
  
  return (
    <video
      controls
      className="w-full"
      {...val.attrs(video.url)}
    >
      <source src={val.raw(video.url)} type="video/mp4" />
      Your browser does not support the video tag.
    </video>
  );
}

More Information

For detailed information about adding metadata, working with remote files, and using the CLI or VS Code extension, see the Images Guide. The workflow is identical - just use s.file() instead of s.image().

For a comprehensive guide on working specifically with videos, see the Videos Guide.