Docs menu

Configuration

Every option of vesper() in astro.config.mjs and every key of vesper.config.ts, with its default.

Vesper reads two files, and neither is required. vesper() with no options and no vesper.config.ts is a complete setup: every collection gets a form built from its schema.

vesper() in astro.config.mjs

// astro.config.mjs
import { defineConfig } from 'astro/config';
import vesper from '@vesper/astro';

export default defineConfig({
  site: 'https://www.example.edu',
  integrations: [
    vesper({
      siteName: 'Riverside College',
      search: { filters: { news: ['tags'] } },
      a11y: { failBuildOn: 'error' },
    }),
  ],
});
OptionDefaultWhat it does
siteNameThe host of Astro's site, else your package's nameThe site's name in the studio's top bar and page titles, and on the search page.
studio.deploytruefalse leaves /admin out of production builds. It's still there in astro dev.
imageServicetrueWraps Astro's Sharp image service, when your site uses it, so the crops editors set are cut before Astro resizes each image. false leaves Astro's service alone; <Img> then uses Astro's own crop for each ratio, and manual crops aren't applied.
a11y{}The accessibility check at build. false turns it off.
search{}Site search. false turns it off: no index and no /search page.
githubOffGitHub mode: editors sign in with GitHub on the deployed site.
cloudOffThe cloud: editors sign in to Vesper, and saves are drafts until published.

a11y

KeyDefaultWhat it does
failBuildOnfalse'error' fails the build when the report has an error (axe's critical and serious issues). 'warning' fails it on any issue. The report is written first.
KeyDefaultWhat it does
filtersNonePer collection, the fields offered as filters: text, lists of text, selects and references.
sortNonePer collection, the fields results can be sorted by: dates, numbers and text.
exclude[]Pages to leave out, by path, with * and **, such as '/tags/**'.
noJsLinkThe home pageWhere /search sends visitors who have no JavaScript.
noJsFallbackNone'duckduckgo': without JavaScript, the search form searches the site on DuckDuckGo. Needs Astro's site.
stringsEnglishThe search interface's words in other languages, keyed by <html lang>.
vesper({
  search: {
    filters: { blog: ['tags', 'author'] },
    sort: { blog: ['publishedAt'] },
    exclude: ['/tags/**'],
    strings: { fr: { button: 'Rechercher' } },
  },
});

github

GitHub mode is on in built sites only; astro dev always saves to your files.

KeyDefaultWhat it does
relayRequiredThe address of your sign-in relay: the oauth-relay template from Vesper's repository, deployed as a Cloudflare Worker.
repoRead from the host's build environment, then gitThe repository, as owner/name.
branchThe repository's default branchWhere publishes go. If git can't tell, main, and the build log says so.
rootRead from gitThe site's folder inside the repository, for a monorepo.
apiUrlhttps://api.github.comFor GitHub Enterprise Server: its API, such as https://github.example.edu/api/v3.
vesper({ github: { relay: 'https://vesper-github-relay.your-account.workers.dev' } });

cloud

The cloud is on in built sites only. A site can have GitHub mode too, and the sign-in screen then offers both.

KeyDefaultWhat it does
projectThe VESPER_PROJECT variableThe site's project ID, from its settings in the dashboard. It's an identifier, not a secret.
app, apihttps://app.vespercms.com, https://api.vespercms.comFor staging and tests only, and always set together, as here or with VESPER_CLOUD_APP and VESPER_CLOUD_API.
vesper({ cloud: { project: 'site_…' } });

cloud: true reads the project ID from VESPER_PROJECT.

vesper.config.ts

vesper.config.ts sits next to astro.config.mjs (.mts, .js and .mjs work too). It holds how the studio presents your content and where entries live on the site, never field definitions. Every key is optional. A key Vesper doesn't know is ignored with a warning, so a config written for a newer version still loads, and a value of the wrong type is reported with what it should be. A change to the file restarts the dev server.

// vesper.config.ts
import { defineConfig } from '@vesper/astro/config';

export default defineConfig({
  timeZone: 'America/New_York',
  collections: {
    pages: { url: '/:path', tree: true, maxDepth: 3, icon: 'panels-top-left' },
    news: { label: 'News', url: '/news/:slug', listFields: ['publishedAt'], icon: 'newspaper' },
    settings: { label: 'Site settings', singleton: true },
  },
  images: { ratios: { wide: 16 / 9, square: 1 } },
});
KeyWhat it holds
collectionsHow the studio presents each collection, and where its entries live.
imagesThe aspect ratios your site crops to, and the upload size limit.
a11yThe rules that block a save, and the colours your site draws text on.
redirectsHosts to write native redirect rules for.
timeZoneThe site's time zone.
markdocLabels and help text for your Markdoc tags.
mdxMDX components the body editor shows as blocks.
widgetsYour own editor controls.

Roles and what each person may edit are set in the cloud's dashboard, not here. A roles key is ignored with a warning.

collections

Keyed by the collection's name in src/content.config.ts. A name that isn't a collection gets a warning.

KeyDefaultWhat it does
labelFrom the name: blogPosts is "Blog posts"The collection's name in the studio, and its Section filter in search.
iconA page; an outline for a tree; sliders for a singletonA Lucide icon name, as that site lists it, such as newspaper. Install the lucide package to use one.
urlNoneWhere each entry lives on the site: a pattern or a function, below.
treefalseShows the collection as an outline: folders are sections, and an optional _order.yaml in each folder sets its order. Collections loaded with glob() only.
maxDepthNo limitFor a tree: how many levels deep a page may be, 1 or more.
singletonfalseThe collection holds one entry, and the studio opens it directly.
listFieldsNoneTop-level fields shown as columns in the entry list. The first date field among them sorts the list, newest first.
newEntryPath:slugWhere new entries are saved, inside the collection's folder, with :slug, :year, :month and :day. Trees place new pages under their parent instead.
placementEach field's own .meta({ placement })Per top-level field, 'main' for the main form or 'details' for the Details section.
headingStart2The level the body's top headings should have, from 1 to 6. Set 1 when the body holds the page's h1.
searchtruefalse leaves the collection's pages out of site search.
scheduleNoneReserved for scheduled publishing, which isn't available yet.

A url pattern is built from tokens:

  • :id is the entry's ID.
  • :path is the ID without a trailing /index. A top-level index is the collection's root.
  • :slug is the last part of the path.
  • :title, or any other field's name, is that field's value.
  • :publishedAt|yyyy, |mm and |dd are parts of a date field. A date with a time is read in the site's timeZone.

Addresses follow Astro's base, trailingSlash and build.format. The studio, links, redirects, breadcrumbs, search and the build's accessibility report all use the same url. For anything a pattern can't say, give a function. It gets the entry's id, collection and data (the frontmatter, typed unknown, so check a field before using it) and returns its path on the site. Keep it pure, with no server-only imports, because the studio runs it in the browser too:

collections: {
  news: { url: '/news/:publishedAt|yyyy/:slug' },
  people: { url: (entry) => `/people/${entry.id}` },
},

/admin and /_vesper are Vesper's, so no page may live there. See Pages and URLs for trees, section pages and moves.

images

KeyDefaultWhat it does
ratiosOnly ogNamed aspect ratios, as width / height, such as { wide: 16 / 9, square: 1 }. The studio previews each crop, and <Img ratio="wide"> renders it. og (1.91:1, for social images) is always there.
maxUploadEdge3000Uploads whose longer edge is bigger, in pixels, are scaled down in the browser before they're saved.

See Images.

a11y

KeyDefaultWhat it does
blockPublishOn[]Rules whose notes stop a save, such as ['missing-alt']. In the cloud they stop a publish, not a draft.
backgroundsNoneThe colours your site draws text on, by name, as hex: { Page: '#ffffff', Footer: '#1d2433' }. Colour fields with contrast are checked against each.
a11y: {
  blockPublishOn: ['missing-alt'],
  backgrounds: { 'Page, light': '#ffffff', 'Page, dark': '#12151f' },
},

The rule names are on Accessibility checks.

redirects

KeyDefaultWhat it does
nativeDetectedHosts to write their own redirect rules for: netlify, cloudflare, vercel, apache or nginx. Netlify, Cloudflare Pages and Vercel are detected from the build environment; name apache or nginx yourself.

Every host also gets an HTML redirect page at each old address. See Hosting.

timeZone

An IANA time zone name, such as 'America/New_York'. The studio shows and takes times in it, and stores them as ISO 8601 with the offset. Date tokens in url and dates in search results use it too. Without it, each editor sees their own time zone, and addresses use UTC.

markdoc

Your tags stay in markdoc.config.mjs, and the studio builds each tag's settings from their attributes. These hints only add words for editors:

markdoc: {
  tags: {
    callout: {
      label: 'Callout',
      summary: 'type',
      attributes: {
        type: { labels: { note: 'Note', tip: 'Tip', warning: 'Warning' } },
        title: { description: 'Shown above the callout.' },
      },
    },
  },
},
KeyWhat it does
labelThe tag's name in the studio.
descriptionHelp text for the tag.
summaryThe attribute whose value the block's header shows: "Callout · Warning".
attributesPer attribute: label, description, labels for each value, and widget, such as 'color'.

mdx

MDX components declare their props nowhere the studio can read, so they're edited as blocks only when you declare them here, with a Zod schema from astro/zod. Other components stay as protected source.

import { z } from 'astro/zod';
import { defineConfig } from '@vesper/astro/config';

export default defineConfig({
  mdx: {
    components: {
      YouTube: { label: 'YouTube video', props: z.object({ id: z.string(), title: z.string() }) },
      Aside: { label: 'Aside', children: true, props: z.object({ title: z.string().optional() }) },
    },
  },
});
KeyDefaultWhat it does
labelThe component's nameIts name in the studio.
descriptionNoneHelp text.
summaryNoneThe prop the block's header shows.
childrenfalsetrue shows a frame whose content stays editable.
inlinefalsetrue inserts the component inside a line of text.
propsNoneA Zod object schema for its props. Saves check props with it, as your site does.

See Markdoc and MDX.

widgets

Your own React controls for fields:

widgets: {
  'store-hours': {
    component: () => import('./src/cms/StoreHours.tsx'),
    accepts: (field) => field.kind === 'array',
    match: (field) => field.path.at(-1) === 'hours',
  },
},
KeyDefaultWhat it does
componentRequiredLoads the component, which receives WidgetProps from @vesper/astro/config.
acceptsAny fieldWhich fields it may be used on.
matchNoneClaims fields on its own. Without it, a field asks for the widget with .meta({ widget: 'store-hours' }).

Vesper compiles widget files with React's automatic runtime, so your site doesn't need @astrojs/react. Start each widget file with /** @jsxImportSource react */ so TypeScript checks it as React. A widget that fails to load falls back to the built-in control, with a note.

See Fields.

This page as Markdown, for AI tools.