Macros
loader
The content loaders: markdown, folder, json, git. Each takes a literal glob (or a repo spec), and becomes a source for content({ loader }). The macro owns the import.meta.glob plumbing. Thus you never write it.
On this page
import.meta.og.loader.* is how you point a content collection at static files. You give it a literal glob (or, for git, a repo spec). The macro owns the import.meta.glob(…) wrapper, and gives content() a finished source.
import { content } from 'ogygia/content';
export const docs = content({
loader: import.meta.og.loader.markdown('./content'),
schema
});That is the full loader: a directory. No glob, no import.meta.glob, no { eager }, no wrapper import. Each loader owns an opinionated pattern under the directory that you point it at (markdown: each .svx / .md, recursively). Compare the old shape, markdown(import.meta.glob('./content/**/*.svx', { eager: true })), and you see what the macro absorbs.
The four loaders
Each takes a directory, and returns a Source. They differ only in what they parse. Each derives its own opinionated file set under that directory. Thus you never author a glob for the conventional case.
// markdown / .svx — every *.svx / *.md below the directory; headings free on `meta.headings`
import.meta.og.loader.markdown('./docs')
// filesystem convention — +doc.svx pages + +meta.json section labels, NN- ordering
import.meta.og.loader.folder('../content/docs')
// JSON data files — every *.json below the directory
import.meta.og.loader.json('./authors')
// straight from another git repository — no committed copy, no sync script
import.meta.og.loader.git('sveltejs/svelte@main:documentation/docs')An argument with glob magic (*, {}, ?), or a single file, is passed through verbatim: markdown('./docs/**/*.svx') still means exactly that. This is the escape hatch for a corpus that the convention does not fit. The directory is the default form.
markdown(glob): a markdown /.svxcollection. Bodies compile through the markdown pipeline. Themeta.headingsof each entry is filled, for on-page tables of contents.folder(glob): the filesystem-convention preset. Its default glob picks up+doc.svxpages and+meta.jsonsection files:NN-prefixes become order, and each+meta.jsonbecomes the label of a section. This is what a docs site uses.json(glob): a data collection, one entry per JSON file.git(spec): sources a collection directly from another repository. Thespecisowner/repo[@ref][:path]. The plugin materializes a shallow checkout at build (cached undernode_modules/.ogygia; thus a warm CI clones nothing), and points the glob at it. This is how you build a docs site on the source of someone else, without a committed copy or a sync script.
Each accepts the same second opts argument that the underlying builder takes. folder and git forward folder options (page, meta, convention). Thus ordering and labels are yours to shape.
One option belongs to the macro itself, consumed at compile and never forwarded: preset, a literal name from content.presets. It opts this full collection into a markdown variant (different overrides, themes, transformers), and does not touch the app default:
loader: import.meta.og.loader.folder('../content/blog', { preset: 'plain' })Each opted-in file compiles as its own module variant. Thus another collection that globs the same file, under another preset or none, renders independently. The name must be literal (it selects config at build). An unknown name is a build error that lists the configured names.
Lazy by default
The macro emits a lazy glob. Each matched file becomes its own chunk, loaded when get(id) reads it, not inlined into one very large module. For a large corpus on a serverless host, that is the difference between a lean function, and one that carries megabytes of compiled markdown. The catalog paths (list, the nav, the search index) still read frontmatter across the corpus. That fan-out is paid one time, and cached.
The rules
- The glob (or spec) is a literal string. A template literal with
${…}interpolation is a runtime value that the build cannot resolve. Thus it is a build error that names the expression. A truly dynamic source (a CMS, a database) is a hand-written source, not a loader. - Loaders are server-only. They build a content corpus, which must never reach a client bundle. Thus they belong in a
.server.tsfile (orsrc/lib/server/, or a.remote.ts). The server-module guard of Kit then makes the corpus mechanically un-leakable. A loader called inside a.sveltecomponent is a warning, not a silent success. - The runtime builders still exist.
markdown(),folder(),json()are what the macros rewrite to. They are the escape hatch, for when you must feed a source builder something other than a literal glob. But for static files, the macro is the path.
See content collections for the full read surface (refs, get), schemas, relations, and how a source composes with withRemotes() to cross the wire.