QUESTPIE
Build Your BackendData Modeling

Globals

Globals are singleton data objects for site-wide configuration like settings, branding, and SEO.

A global is a singleton — a single record with no list view, just a form. Use globals for site-wide configuration: settings, SEO defaults, navigation, footer content.

Defining a Global

globals/site-settings.ts
import { global } from "#questpie/factories";

export const siteSettings = global("site_settings")
	.fields(({ f }) => ({
		shopName: f.text().required().default("My App"),
		tagline: f.text().localized(),
		logo: f.upload({ to: "assets" }),
		contactEmail: f.email().required(),
	}))
	.admin(({ c }) => ({
		label: { en: "Site Settings", sk: "Nastavenia webu" },
		icon: c.icon("ph:gear"),
	}))
	.options({
		timestamps: true,
		versioning: true,
	})
	.access({
		read: true,
		update: ({ session }) => (session?.user as any)?.role === "admin",
	});

Builder Chain

Globals share most methods with collections:

MethodPurpose
.fields(({ f }) => {...})Define data fields
.admin(({ c }) => {...})Admin label and icon
.form(({ v, f }) => v.globalForm({...}))Form layout
.hooks({...})Lifecycle hooks
.access({...})Read/update access control
.options({...})Timestamps, versioning

Globals do not support .list(), .indexes(), .title(), or .preview() since there's only ever one record.

Server-Side API

// Read
const settings = await globals.siteSettings.get();

// Update
await globals.siteSettings.update({
	shopName: "New Name",
	tagline: "New tagline",
});

Client-Side API

// Read
const settings = await client.globals.siteSettings.get();

// Update (requires access)
await client.globals.siteSettings.update({
	shopName: "New Name",
});

Real-World Example

From the barbershop — site settings with nested objects, arrays, and access control:

globals/site-settings.ts
import { global } from "#questpie/factories";

export const siteSettings = global("site_settings")
	.fields(({ f }) => ({
		shopName: f.text().required().default("Sharp Cuts"),
		tagline: f.text().localized(),
		logo: f.upload({ to: "assets" }),

		navigation: f
			.object({
				label: f.text().required(),
				href: f.text().required(),
				isExternal: f.boolean().default(false),
			})
			.array()
			.localized(),

		contactEmail: f.email().required(),
		contactPhone: f.text(),
		address: f.text(),

		businessHours: f.object({
			monday: f.object({
				isOpen: f.boolean().default(true),
				start: f.time(),
				end: f.time(),
			}),
			// ... other days
		}),

		bookingSettings: f.object({
			minAdvanceHours: f.number().required(),
			maxAdvanceDays: f.number().required(),
			slotDurationMinutes: f.number().required(),
			allowCancellation: f.boolean().required(),
		}),

		metaTitle: f.text().localized(),
		metaDescription: f.textarea().localized(),
	}))
	.admin(({ c }) => ({
		label: { en: "Site Settings", sk: "Nastavenia webu" },
		icon: c.icon("ph:gear"),
	}))
	.form(({ v, f }) =>
		v.globalForm({
			fields: [
				{
					type: "section",
					label: { en: "Branding" },
					layout: "grid",
					columns: 2,
					fields: [f.shopName, f.tagline, f.logo],
				},
				{
					type: "section",
					label: { en: "Contact" },
					layout: "grid",
					columns: 2,
					fields: [f.contactEmail, f.contactPhone, f.address],
				},
				// ... more sections
			],
		}),
	)
	.options({ timestamps: true, versioning: true })
	.access({
		read: true,
		update: ({ session }) => (session?.user as any)?.role === "admin",
	});

On this page