Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Type-safe content management with schema validation, multiple data sources, and runtime querying.
function defineCollection<S extends BaseSchema>(config: CollectionConfig<S>): CollectionConfig<S>;
interface CollectionConfig<S> {
type?: 'content' | 'data' | 'content_layer'; // default: 'content'
schema?: S | ((context: SchemaContext) => S);
loader?: Loader | (() => Array<DataType> | Promise<Array<DataType>>);
}
interface SchemaContext {
image: ImageFunction;
}import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
pubDate: z.date(),
tags: z.array(z.string())
})
});
export const collections = { blog };function glob(options: GlobOptions): Loader;
interface GlobOptions {
pattern: string | string[];
base?: string | URL;
generateId?: (options: { entry: string; base: URL; data: Record<string, unknown>; }) => string;
}import { glob } from 'astro:content';
const docs = defineCollection({
loader: glob({ pattern: '**/*.md', base: './docs' }),
schema: z.object({ title: z.string() })
});function file(fileName: string, options?: { parser?: (text: string) => any }): Loader;import { file } from 'astro:content';
const products = defineCollection({
loader: file('products.json'),
schema: z.object({ name: z.string(), price: z.number() })
});interface LoaderContext {
collection: string;
store: DataStore;
logger: AstroIntegrationLogger;
config: AstroConfig;
parseData<T>(props: { id: string; data: T; filePath?: string; }): Promise<T>;
renderMarkdown(content: string): Promise<{ html: string; metadata: { headings: MarkdownHeading[]; imagePaths: string[]; }; }>;
generateDigest(data: Record<string, unknown> | string): string;
}function getCollection<C>(collection: C, filter?: (entry: Collections[C]) => boolean): Promise<Array<Collections[C]>>;import { getCollection } from 'astro:content';
const allPosts = await getCollection('blog');
const published = await getCollection('blog', (p) => !p.data.draft);
const tagged = await getCollection('blog', (p) => p.data.tags.includes('tutorial'));function getEntry<C>(collection: C, id: string): Promise<Collections[C] | undefined>;
function getEntry(lookup: { collection: string; id?: string; slug?: string; }): Promise<Entry | undefined>;const post = await getEntry('blog', 'my-post');
const page = await getEntry({ collection: 'docs', slug: 'getting-started' });function getEntries(entries: Array<{ collection: string; id?: string; slug?: string; }>): Promise<Array<Entry>>;const related = await getEntries([
{ collection: 'blog', id: 'post-1' },
{ collection: 'blog', id: 'post-2' }
]);function render(entry: ContentEntry): Promise<{ Content: AstroComponent; headings: MarkdownHeading[]; remarkPluginFrontmatter: Record<string, any>; }>;const post = await getEntry('blog', 'my-post');
const { Content, headings } = await render(post);function defineLiveCollection<L, S>(config: { type?: 'live'; schema?: S; loader: L; }): LiveCollectionConfig<L, S>;
interface LiveLoader {
loadCollection(collection: string): Promise<Array<Entry>>;
loadEntry(collection: string, id: string): Promise<Entry | undefined>;
}// src/live.config.ts
import { defineLiveCollection } from 'astro:content';
export const collections = {
posts: defineLiveCollection({
loader: myLiveLoader,
schema: z.object({ title: z.string() })
})
};function getLiveCollection(collection: string, filter?: Record<string, any>): Promise<{ data: LiveDataEntry[]; cacheHint?: CacheHint; }>;
function getLiveEntry(collection: string, lookup: string | Record<string, any>): Promise<{ data: LiveDataEntry; cacheHint?: CacheHint; }>;class LiveCollectionError extends Error {
readonly collection: string;
static is(error: unknown): error is LiveCollectionError;
}
class LiveEntryNotFoundError extends LiveCollectionError {
static is(error: unknown): error is LiveEntryNotFoundError;
}
class LiveCollectionValidationError extends LiveCollectionError {
static is(error: unknown): error is LiveCollectionValidationError;
}import { getLiveEntry, LiveEntryNotFoundError } from 'astro:content';
try {
const result = await getLiveEntry('products', { sku: 'ABC-123' });
} catch (error) {
if (LiveEntryNotFoundError.is(error)) {
console.error(`Entry not found in ${error.collection}`);
}
}function reference(collection: string): ZodType;const authors = defineCollection({
type: 'data',
schema: z.object({ name: z.string() })
});
const blog = defineCollection({
schema: z.object({
title: z.string(),
author: reference('authors'),
related: z.array(reference('blog'))
})
});const schema = ({ image }) => z.object({
title: z.string(),
coverImage: image()
});interface ContentEntry<T = any> {
id: string; slug: string; body: string; collection: string;
data: T;
render(): Promise<RenderResult>;
}
interface DataEntry<T = any> {
id: string; collection: string; data: T;
}// src/content/config.ts
import { defineCollection, z } from 'astro:content';
export const collections = {
blog: defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.date(),
tags: z.array(z.string())
})
})
};import { getCollection, render } from 'astro:content';
const posts = await getCollection('blog');
const { Content, headings } = await render(posts[0]);const blog = defineCollection({
schema: z.object({
author: reference('authors'),
relatedPosts: z.array(reference('blog'))
})
});z.optional() for optional fieldssrc/live.config.ts, not src/content/config.tstype: 'content_layer' with custom loaders