Macros
loader
The content loaders markdown, folder, json and git. Each takes a literal directory or glob (or a repo spec) and becomes a source for content({ loader }), and the macro writes the import.meta.glob plumbing so you don't have to.
On this page
import.meta.og.loader.* is how you point a content collection at static files. You give it a literal path (or, for git, a repo spec), and the macro writes the import.meta.glob(…) wrapper for you and hands content() a finished source.
import { content } from '@ogygia/content';
export const docs = content({
loader: import.meta.og.loader.markdown('./content'),
schema
});The whole loader is a directory, with no glob, no import.meta.glob, no { eager } and no wrapper import. Each loader has a built-in file pattern under the directory you point it at (for markdown, every .svx / .md, recursively). Compared with the longhand markdown(import.meta.glob('./content/**/*.svx', { eager: true })), you can see what the macro takes off your hands.
The four loaders
Each loader takes a directory and returns a Source, and they differ only in what they parse. Each derives its own file set under that directory, so in the conventional case you never write a glob.
// 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 that contains glob syntax (*, {}, ?), or names a single file, is passed through unchanged, so markdown('./docs/**/*.svx') still means exactly that. This is the escape hatch for a corpus that the convention does not fit; the directory form is the default.
markdown(glob)loads a markdown /.svxcollection. Bodies compile through the markdown pipeline, and each entry’smeta.headingsis filled in for on-page tables of contents.folder(glob)is the filesystem-convention preset. Its default glob picks up+doc.svxpages and+meta.jsonsection files, soNN-prefixes become the order and each+meta.jsonbecomes a section’s label. This is what a docs site uses.json(glob)loads a data collection with one entry per JSON file.git(spec)sources a collection directly from another repository, withspecin the formowner/repo[@ref][:path]. The plugin makes a shallow checkout at build time (cached undernode_modules/.ogygia, so a warm CI run clones nothing) and points the glob at it. This lets you build a docs site on someone else’s source without a committed copy or a sync script.
Each loader accepts the same second opts argument as its underlying builder. folder and git pass folder options (page, meta, convention) through, so you control ordering and labels.
One option belongs to the macro itself and is consumed at compile time rather than passed on: preset, a literal name from content({ presets }). It opts the whole collection into a markdown variant (different overrides, themes or transformers) without changing the app’s default:
loader: import.meta.og.loader.folder('../content/blog', { preset: 'plain' })Each opted-in file compiles as its own module variant, so another collection that globs the same file under a different preset, or none, renders independently. The name must be a literal because it selects config at build time, and an unknown name is a build error that lists the configured names.
Lazy by default
The macro emits a lazy glob, so each matched file becomes its own chunk that loads when get(id) reads it, rather than everything being 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 whole corpus, but that fan-out is paid once and cached.
The rules
- The glob (or spec) is a literal string. A template literal with
${…}interpolation is a runtime value the build cannot resolve, so it is a build error that names the expression. A truly dynamic source, such as a CMS or a database, is a hand-written source rather than a loader. - Loaders are server-only. They build a content corpus, so they belong in a
.server.tsfile (or undersrc/lib/server/, or in a.remote.ts) for the reasons given in Content collections. A loader called inside a.sveltecomponent produces a warning rather than silently working. - The runtime builders still exist.
markdown(),folder()andjson()are what the macros rewrite to, and they remain the escape hatch when you need to feed a source builder something other than a literal glob. For static files, use the macro.
Content collections covers the full read surface (refs, get), schemas, relations, and how a source works with withRemotes() to cross the wire.