Globals
Site-wide data, edited as forms
A Global is a named, schema-defined Content document with meta fields only, holding site-wide data such as menus, a banner or footer text. It has no body and is never a page. An editor changes it in the Dashboard's Globals section as a form, and the site reads it with one helper, loadGlobal. One declaration drives all of it: the Globals section's forms, the type loadGlobal returns, and keeping Globals out of the page routes and the Pages list.
Declare Globals on the site object
Each Global is a schema built with createSchema, exactly as for pages, with metaFields describing its form. It has no blocks, so an empty registry will do. Pass the Globals by name to defineSite:
// src/lib/site.ts
import { createBlockRegistry, createSchema } from 'uncial/core';
import { defineSite } from 'uncial-cms';
import { siteOptions } from '../../site.options.js';
interface Link {
label: string;
href: string;
}
const noBlocks = createBlockRegistry([]);
const isSitePath = (value: unknown) => typeof value === 'string' && value.startsWith('/');
const link = {
itemLabel: 'link',
fields: {
label: { default: '', required: true },
href: { default: '/', required: true, validate: isSitePath }
}
};
const banner = createSchema(noBlocks, {
metaFields: {
message: { default: '' },
visible: { default: false }
}
});
const menus = createSchema(noBlocks, {
metaFields: {
header: { default: [] as Link[], list: link },
footer: {
default: [] as Array<{ heading: string; links: Link[] }>,
list: {
itemLabel: 'column',
fields: {
heading: { default: '', required: true },
links: { default: [] as Link[], list: link }
}
}
}
}
});
export const site = defineSite({ ...siteOptions, globals: { banner, menus } });A field's control follows its default, as on a page's metadata panel: a string is a text field, a boolean a checkbox, and a list a stack of items with add, reorder and remove. Lists nest, so a footer can hold columns that each hold links. A list must declare its item fields or a single value, and createSchema throws on one that declares neither. The Globals section lists each Global by name. To show a friendlier label beside the name, set title on the schema object: { ...createSchema(noBlocks, { metaFields }), title: 'Site banner' }.
Read a Global
loadGlobal(site, name) returns a Global's published value, normalized against its schema. Its type comes from the schema, so banner below is { message: string; visible: boolean }. A Global that has never been saved returns its schema's defaults, so a fresh site renders without seed files.
// src/routes/+layout.server.ts
import { loadGlobal } from 'uncial-cms/sveltekit';
import { site } from '$lib/site.js';
export const load = async () => ({
banner: await loadGlobal(site, 'banner'),
menus: await loadGlobal(site, 'menus')
});On a static site this runs at build time and reads the content dir. In Astro, the same loadGlobal is exported from uncial-cms/astro for frontmatter. A server-forge site reads at request time from its content store instead, and passes it in: loadGlobal(site, 'banner', { store }).
Where Globals live
Globals go through the same forge as pages, with no new storage:
On the github and local forges, a Global is a JSON file at
<contentDir>/_globals/<name>.json.On the server forge, it is a record of kind
globalat the path/_globals/<name>/.
The _globals namespace is reserved. The SvelteKit and Astro route factories never route it, listPages and the Pages section never list it, and a server store's page listings leave it out, all with no exclude from the host. A site that walks its content dir itself, as these docs do for their sidebar, should skip _globals too.
Saving publishes
A Global has no Draft: saving it publishes it, on every forge.
On github, a save is a commit, as it is for a page. The Globals section then shows the same building… and live status as an Editor variant, because a static site's readers see the change only once the site rebuilds.
On the local forge, the save writes the file in your checkout.
On the server forge, the save writes and publishes in one action.
A Global's fields are validated before the save, down to each item of a nested list. An error names its field and shows beside the offending item, so an editor can fix a missing link label in the third footer column without hunting for it. If someone else saved the Global after the form was opened, the save is refused and the conflict banner appears, as it does for a page. It offers to download the editor's version, reload the latest, or dismiss, and nothing is overwritten silently.
Who may edit
Without a can option, anyone signed in may edit every Global. A host that passes can decides per Global: can('global-edit', name). When it refuses, the Global's form shows read-only, without a Save button. See Permissions.