██╗   ██╗███████╗██████╗ ███████╗██╗   ██╗ ██████╗ ██╗██████╗
██║   ██║██╔════╝██╔══██╗██╔════╝██║   ██║██╔═══██╗██║██╔══██╗
██║   ██║█████╗  ██████╔╝█████╗  ██║   ██║██║   ██║██║██████╔╝
╚██╗ ██╔╝██╔══╝  ██╔══██╗██╔══╝  ╚██╗ ██╔╝██║   ██║██║██╔══██╗
 ╚████╔╝ ███████╗██║  ██║███████╗ ╚████╔╝ ╚██████╔╝██║██║  ██║
  ╚═══╝  ╚══════╝╚═╝  ╚═╝╚══════╝  ╚═══╝   ╚═════╝ ╚═╝╚═╝  ╚═╝

Getting Started

This guide walks you through creating a simple content-managed app with Verevoir in under five minutes. By the end you'll have a content model, an in-memory database, and a React editor — all running locally.

Install

npm install @verevoir/schema @verevoir/storage @verevoir/editor

Peer dependencies: react, react-dom, zod.

Define a Content Model

A content model describes the shape of your content. Define it once — Verevoir generates validation, TypeScript types, and editor metadata from the same definition.

import { defineBlock, text, richText, boolean } from '@verevoir/schema';

export const article = defineBlock({
  name: 'article',
  fields: {
    title: text('Title').max(120),
    body: richText('Body'),
    published: boolean('Published').default(false),
  },
});

See defining-content-models.md for the full field type reference.

Connect Storage

Verevoir is database-agnostic. For development, use the built-in MemoryAdapter. For production, swap in PostgresAdapter or build your own.

import { MemoryAdapter } from '@verevoir/storage';

const db = new MemoryAdapter();
await db.connect();

Create and retrieve documents:

const doc = await db.create('article', {
  title: 'Hello World',
  body: 'My first article.',
  published: false,
});

const fetched = await db.get(doc.id);

Add the Editor

The BlockEditor component renders a form from your content model definition. No configuration needed — it reads field metadata (labels, types, validation) directly from the block.

import { BlockEditor, useBlockForm } from '@verevoir/editor';
import { article } from './content-model';

function ArticleEditor({ value, onChange }) {
  const [state, actions] = useBlockForm(article, value, onChange);

  return <BlockEditor block={article} state={state} actions={actions} />;
}

Wire it to storage:

function App() {
  const [value, setValue] = useState({
    title: '',
    body: '',
    published: false,
  });

  const handleSave = async () => {
    article.validate(value); // throws on invalid data
    await db.create('article', value);
  };

  return (
    <div>
      <ArticleEditor value={value} onChange={setValue} />
      <button onClick={handleSave}>Save</button>
    </div>
  );
}

Validate

Every block has a .validate() method powered by Zod. Call it before saving to enforce your content model rules.

try {
  article.validate(value);
} catch (err) {
  console.error(err.issues); // Zod validation errors
}

Type-safe data is available via InferBlock:

import type { InferBlock } from '@verevoir/schema';

type Article = InferBlock<typeof article>;
// { title: string; body: string; published: boolean }

Next Steps

You now have schema, storage, and editor working together. From here: