Overview

Val Documentation

Val is a Content Management System (CMS) library that treats content as code, storing it in TypeScript or JavaScript files within your Git repository.

Overview

Val enables developers to manage content directly within their codebase, facilitating version control and streamlined development workflows. Currently, Val supports Next.js version 14 and above.

⚠️ Beta Notice

Val is currently in beta. While the API is relatively stable, some features may be incomplete, and the user experience is subject to change.

Key Features

📝 Content as Code

Manage your content directly within your codebase using TypeScript or JavaScript files, enabling version control and seamless integration with your development workflow.

🌍 Remote Internationalization

Val is developing support for remote i18n, allowing you to define certain languages in code while fetching others remotely.

🔄 Version Control Integration

Content changes are tracked alongside code changes in Git, providing full audit trails and collaborative editing capabilities.

Installation

Using the NextJS starter templates

If you're starting from scratch, the easiest way to get up and running with Val and NextJS is to use npm (or similar) create script:

npm create @valbuild

Integrating into an existing project

Make sure you have NextJS (version 14 or higher) installed

Install the packages:

npm install @valbuild/core@latest @valbuild/next@latest

Optionally, but recommend add the eslint-plugin package:

npm install -D @valbuild/eslint-plugin@latest

Run the init script:

npx @valbuild/init@latest

Manually

It is also possible to setup Val using the manual configuration guide.

See the manual configuration section below for detailed steps.

Additional setup

  • If you have a monorepo, or have a project where the project is located in a subdirectory relative to the github repository see the monorepos section
  • See formatting published content if you use prettier (or similar) Val to do it as well.
  • If you want editors to update content in production, read up on how to setup remote mode.

Getting started

Create your first Val content file

Content in Val is always defined in `.val.ts` (or `.js`) files.

NOTE: the init script will generate an example Val content file (unless you opt out of it).

Val content files are evaluated by Val, therefore they need to abide a set of requirements.

If you use the eslint plugins these requirements will be enforced. You can also validate val files using the @valbuild/cli: `npx -p @valbuild/cli val validate`.

  • they must export a default content definition (`c.define`) where the first argument equals the path of the file relative to the `val.config` file; and
  • they must be declared in the `val.modules` file; and
  • they must have a default export that is `c.define`; and
  • they can only import Val related files or types (using `import type { MyType } from "./otherModule.ts"`)

Val content file example

// ./examples/val/example.val.ts
import { s /* s = schema */, c /* c = content */ } from "../../val.config";

/**
 * This is the schema for the content. It defines the structure of the content and the types of each field.
 */
export const schema = s.object({
  /**
   * Basic text field
   */
  text: s.string(),
});

/**
 * This is the content definition. Add your content below.
 *
 * NOTE: the first argument is the path of the file.
 */
export default c.define("/examples/val/example.val.ts", schema, {
  text: "Basic text content",
});

The `val.modules` file

Once you have created your Val content file, it must be declared in the `val.modules.ts` (or `.js`) file in the project root folder.

import { modules } from "@valbuild/next";
import { config } from "./val.config";

export default modules(config, [
  // Add your modules here
  { def: () => import("./examples/val/example.val") },
]);

Using Val in Client Components

In client components you can access your content with the `useVal` hook:

// ./app/page.tsx
"use client";
import { useVal } from "../val/val.client";
import exampleVal from "../examples/val/example.val";

export default function Home() {
  const { text } = useVal(exampleVal);
  return <main>{text}</main>;
}

Using Val in React Server Components

In React Server components you can access your content with the `fetchVal` function:

// ./app/page.tsx
"use server";
import { fetchVal } from "../val/val.rsc";
import exampleVal from "../examples/val/example.val";

export default async function Home() {
  const { text } = await fetchVal(exampleVal);
  return <main>{text}</main>;
}

Schema Types

Val provides a comprehensive set of schema types for defining your content structure:

Primitive Types

s.string()

String type with optional validation

Available methods:
validate.maxLength(n).minLength(n).regex(pattern)
Example:
s.string().maxLength(100)

s.number()

Number type with optional validation

Available methods:
validate.min(n).max(n)
Example:
s.number().min(0).max(100)

s.boolean()

Boolean type

Available methods:
validate
Example:
s.boolean()

s.literal(value)

Literal value type

Available methods:
validate
Example:
s.literal("primary")

Complex Types

s.object({})

Object type with defined properties

Available methods:
validate
Example:
s.object({
  title: s.string(),
  count: s.number()
})

s.array(schema)

Array type with element schema

Available methods:
validate.maxLength(n).minLength(n)
Example:
s.array(s.string()).maxLength(10)

s.union(...s.literal(value))

Union type (one of multiple literal values)

Available methods:
validate
Example:
s.union(
  s.literal("small"),
  s.literal("large")
)

s.union("type", ...s.literal(value))

Discriminated (tagged) union type

Available methods:
validate
Example:
s.union(
  "type",
  s.object({
    type: s.literal("number"),
    num: s.number(),
  }),
  s.object({
    type: s.literal("string"),
    str: s.string(),
  })
)

s.keyOf(schema)

Key reference to another Val module

Available methods:
validate
Example:
s.keyOf(iconsSchema)

Media Types

s.image()

Image type with metadata

Available methods:
validate
Example:
s.image().nullable()

c.image(path, metadata)

Image content definition

Example:
c.image("/public/hero.jpg", {
  width: 1200,
  height: 800,
  alt: "Hero image"
})

Rich Content Types

s.richtext(options)

Rich text with configurable formatting options

Available methods:
validate
Example:
s.richtext({
  style: {
    bold: true,
    italic: true,
    lineThrough: true
  },
  block: {
    p: true,
    h1: true,
    h2: true,
    ul: true,
    ol: true
  },
  inline: {
    a: true,
    img: true
  }
})

Utility Methods

.nullable()

Makes any schema type nullable

Example:
s.string().nullable()

.optional()

Makes any schema type optional

Example:
s.string().optional()

.validate()

Custom validation function

Example:
s.string().validate(str => {
   if (str === "test") {
     return "Test is not allowed";
   }
   return str;
})

Advanced Usage

Type Inference

Extract TypeScript types from your schemas:

import { t } from "@/val.config";

const userSchema = s.object({
  name: s.string(),
  age: s.number(),
  email: s.string().nullable()
});

export type User = t.inferSchema<typeof userSchema>;
// Type: { name: string; age: number; email: string | null }

Schema Composition

Compose complex schemas from smaller parts:

const addressSchema = s.object({
  street: s.string(),
  city: s.string(),
  zipCode: s.string()
});

const userSchema = s.object({
  name: s.string(),
  address: addressSchema,
  alternateAddresses: s.array(addressSchema)
});

Conditional Content

Use unions for conditional content structures:

const contentBlockSchema = s.union(
  "type",
  s.object({
    type: s.literal("text"),
    content: s.richtext()
  }),
  s.object({
    type: s.literal("image"),
    src: s.image(),
    caption: s.string().nullable()
  }),
  s.object({
    type: s.literal("video"),
    url: s.string(),
    thumbnail: s.image().nullable()
  })
);

API Documentation

Core Functions

initVal(config)

Normally you do not need to use this function directly, it should be setup by the init script. Initialize Val with configuration options. Returns schema builders and configuration.

Parameters:
  • config.projectstring- Your project identifier (org/repo)
  • config.gitBranchstring- Current git branch (optional)
  • config.gitCommitstring- Current git commit SHA (optional)
  • config.defaultTheme'light' | 'dark'- Default theme for Val Studio
Returns:
{ s, c, val, config }
Example:
const { s, c, val, config } = initVal({
    project: "valbuild/web",
    defaultTheme: "dark"
  });

useVal(moduleVal)

Fetch content data from a Val schema definition (server-side only).

Parameters:
  • moduleValValModule- The module definition to fetch content for
Returns:
T
Example:
const content = useVal(pageVal);

useValRoute(moduleVal, params)

Use this with page.val.ts modules that use the .route() method (client-side only).

Parameters:
  • moduleValValModule- The module definition to fetch content for
  • paramsPromise<Record<string, string>> | unknown- The parameters to fetch content for, used to resolve the path
Returns:
T
Example:
const content = useValRoute(pageVal, params);

fetchVal(moduleVal)

Fetch content data from a Val module definition (server-side only).

Parameters:
  • moduleValValModule- The module definition to fetch content for

fetchVal(moduleVal)

Fetch content data from a Val module definition (server-side only).

Parameters:
  • moduleValValModule- The module definition to fetch content for
Returns:
Promise<T>
Example:
const content = await fetchVal(pageVal);

fetchValRoute(moduleVal, params)

Use this with page.val.ts modules that use the .route() method (server-side only).

Parameters:
  • moduleValValModule- The module definition to fetch content for
  • paramsPromise<Record<string, string>> | unknown- The parameters to fetch content for, used to resolve the path
Returns:
Promise<T>
Example:
const content = await fetchValRoute(pageVal, params);

c.define(path, schema, data)

Define content with schema validation and default data.

Parameters:
  • pathstring- File path identifier
  • schemaSchema- Schema definition
  • dataT- Default content data
Returns:
ValSchema<T>
Example:
export default c.define("/content/page.val.ts", schema, {
    title: "Hello World"
  });

React Components

ValProvider

YouContext provider that enables Val functionality throughout your app.

Props:
  • configValConfig- Configuration object from initVal
  • childrenReactNode- Child components
Example:
<ValProvider config={config}>
      <App />
    </ValProvider>

ValRichText

Rich text component that displays Val rich text content.

Props:
  • childrenRichText- Val rich text content
  • themeValRichTextTheme- Val rich text theme
Example:
<ValRichText theme={{
    bold: "font-bold",
    italic: "italic",
    lineThrough: "underline",
    ul: "list-disc pl-4",
    ol: "list-decimal pl-4",
    }
}>
    {children}
</ValRichText>

ValImage

A wrapper around the NextJS image component which can be used with Val image content.

Props:
  • srcImage- Val image content
Example:
<ValImage src={image}/>

Server-Side Functions

initValRsc(config, modules, nextjs)

Normally you do not need to use this function directly, it should be setup by the init script. Initialize Val for React Server Components with Next.js integration.

Parameters:
  • configValConfig- Val configuration
  • modulesValModules- Val modules definition
  • nextjsNextJSIntegration- Next.js integration options
Returns:
{ fetchValStega, fetchValRouteStega, fetchValRouteUrl }
Example:
const { fetchValStega: fetchVal, fetchValRouteStega: fetchValRoute, fetchValRouteUrl } = initValRsc(config, valModules, {
    draftMode,
    headers,
    cookies,
  });

Client-Side Functions

initValClient(config, modules, nextjs)

Normally you do not need to use this function directly, it should be setup by the init script. Initialize Val for Client Components with Next.js integration.

Parameters:
  • configValConfig- Val configuration
  • modulesValModules- Val modules definition
  • nextjsNextJSIntegration- Next.js integration options (optional)
Returns:
{ useVal, useValRoute, useValRouteUrl }
Example:
const { useVal, useValRoute, useValRouteUrl } = initValClient(config, valModules, {
     draftMode,
     headers,
     cookies,
   });

Resources

For more detailed information and advanced usage, visit the Val Next.js README.