Chapinismos uses Astro’s Content Collections feature to manage and validate all dictionary words. This guide explains how to query, filter, and sort words in your Astro components.
Collection Structure
The project defines two content collections in src/content/config.ts:
import { defineCollection, z } from "astro:content";
const wordsSchema = z.object({
word: z.string(),
meaning: z.string(),
examples: z.array(z.string()),
category: z.enum([
"sustantivo", "verbo", "adjetivo", "adverbio",
"expresión", "interjección", "modismo",
"noun", "verb", "adjective", "adverb",
"expression", "interjection", "idiom",
]),
region: z.string().optional(),
synonyms: z.array(z.string()).optional(),
relatedWords: z.array(z.string()).optional(),
featured: z.boolean().optional(),
});
const wordsEsCollection = defineCollection({
type: "content",
schema: wordsSchema,
});
const wordsEnCollection = defineCollection({
type: "content",
schema: wordsSchema,
});
export const collections = {
"words-es": wordsEsCollection,
"words-en": wordsEnCollection,
};
Querying Collections
Get All Words
Use getCollection() to retrieve all words from a collection:
import { getCollection } from 'astro:content';
// Get all Spanish words
const wordsEs = await getCollection('words-es');
// Get all English words
const wordsEn = await getCollection('words-en');
Get a Single Word
Use getEntry() to retrieve a specific word by its slug:
import { getEntry } from 'astro:content';
// Get a specific word
const patojo = await getEntry('words-es', 'patojo');
// Access frontmatter data
const word = patojo.data.word; // "Patojo"
const meaning = patojo.data.meaning; // "Niño o joven..."
const examples = patojo.data.examples; // Array of strings
// Render markdown content
const { Content } = await patojo.render();
Collection Entry Structure
Each collection entry has the following structure:
{
id: string, // Filename without extension ("patojo")
slug: string, // URL-friendly identifier ("patojo")
body: string, // Raw markdown content
collection: string, // Collection name ("words-es")
data: { // Validated frontmatter data
word: string,
meaning: string,
examples: string[],
category: string,
region?: string,
synonyms?: string[],
relatedWords?: string[],
featured?: boolean,
},
render: () => Promise<{ // Render function
Content: AstroComponent,
headings: Heading[],
remarkPluginFrontmatter: Record<string, any>,
}>
}
Filtering Collections
Filter by Category
import { getCollection } from 'astro:content';
// Get all nouns
const nouns = await getCollection('words-es', (entry) => {
return entry.data.category === 'sustantivo';
});
// Get all expressions
const expressions = await getCollection('words-es', (entry) => {
return entry.data.category === 'expresión';
});
Filter by Featured Status
// Get only featured words
const featuredWords = await getCollection('words-es', (entry) => {
return entry.data.featured === true;
});
Filter by Region
// Get words from specific region
const guatemalanWords = await getCollection('words-es', (entry) => {
return entry.data.region === 'Guatemala';
});
Complex Filters
// Get featured nouns only
const featuredNouns = await getCollection('words-es', (entry) => {
return entry.data.category === 'sustantivo' &&
entry.data.featured === true;
});
// Get words with synonyms
const wordsWithSynonyms = await getCollection('words-es', (entry) => {
return entry.data.synonyms && entry.data.synonyms.length > 0;
});
Sorting Collections
Alphabetical Sort
import { getCollection } from 'astro:content';
const words = await getCollection('words-es');
// Sort alphabetically by word
const sortedWords = words.sort((a, b) => {
return a.data.word.localeCompare(b.data.word);
});
Sort by Slug
// Sort by filename/slug
const sortedBySlug = words.sort((a, b) => {
return a.slug.localeCompare(b.slug);
});
Sort by Category
// Group by category
const sortedByCategory = words.sort((a, b) => {
return a.data.category.localeCompare(b.data.category);
});
Featured First
// Show featured words first
const featuredFirst = words.sort((a, b) => {
if (a.data.featured && !b.data.featured) return -1;
if (!a.data.featured && b.data.featured) return 1;
return a.data.word.localeCompare(b.data.word);
});
Using in Astro Components
Display All Words
---
import { getCollection } from 'astro:content';
const words = await getCollection('words-es');
const sortedWords = words.sort((a, b) =>
a.data.word.localeCompare(b.data.word)
);
---
<ul>
{sortedWords.map((word) => (
<li>
<a href={`/words/${word.slug}`}>
<strong>{word.data.word}</strong> - {word.data.meaning}
</a>
</li>
))}
</ul>
Display Featured Words
---
import { getCollection } from 'astro:content';
const featuredWords = await getCollection('words-es', (entry) =>
entry.data.featured === true
);
---
<section>
<h2>Featured Chapinismos</h2>
<div class="grid">
{featuredWords.map((word) => (
<article>
<h3>{word.data.word}</h3>
<p>{word.data.meaning}</p>
<ul>
{word.data.examples.map((example) => (
<li>{example}</li>
))}
</ul>
</article>
))}
</div>
</section>
Single Word Page
---
import { getEntry } from 'astro:content';
const { slug } = Astro.params;
const entry = await getEntry('words-es', slug);
if (!entry) {
return Astro.redirect('/404');
}
const { Content } = await entry.render();
const { word, meaning, examples, category, synonyms, relatedWords } = entry.data;
---
<article>
<h1>{word}</h1>
<div class="category">
<span>{category}</span>
</div>
<section>
<h2>Meaning</h2>
<p>{meaning}</p>
</section>
<section>
<h2>Examples</h2>
<ul>
{examples.map((example) => (
<li>{example}</li>
))}
</ul>
</section>
{synonyms && synonyms.length > 0 && (
<section>
<h2>Synonyms</h2>
<ul>
{synonyms.map((synonym) => (
<li>{synonym}</li>
))}
</ul>
</section>
)}
{relatedWords && relatedWords.length > 0 && (
<section>
<h2>Related Words</h2>
<ul>
{relatedWords.map((related) => (
<li>{related}</li>
))}
</ul>
</section>
)}
<section>
<Content />
</section>
</article>
Generate Static Paths
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const words = await getCollection('words-es');
return words.map((word) => ({
params: { slug: word.slug },
props: { word },
}));
}
const { word } = Astro.props;
const { Content } = await word.render();
---
<h1>{word.data.word}</h1>
<p>{word.data.meaning}</p>
<Content />
Grouping and Aggregation
Group by Category
import { getCollection } from 'astro:content';
const words = await getCollection('words-es');
const grouped = words.reduce((acc, word) => {
const category = word.data.category;
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(word);
return acc;
}, {} as Record<string, typeof words>);
// Usage:
// grouped['sustantivo'] - all nouns
// grouped['expresión'] - all expressions
Group Alphabetically
const words = await getCollection('words-es');
const alphabetical = words.reduce((acc, word) => {
const firstLetter = word.data.word[0].toUpperCase();
if (!acc[firstLetter]) {
acc[firstLetter] = [];
}
acc[firstLetter].push(word);
return acc;
}, {} as Record<string, typeof words>);
// Usage:
// alphabetical['A'] - words starting with A
// alphabetical['P'] - words starting with P
Type Safety
Infer Types from Schema
import type { CollectionEntry } from 'astro:content';
// Type for a single word entry
type WordEntry = CollectionEntry<'words-es'>;
// Type for word data (frontmatter)
type WordData = WordEntry['data'];
// Use in functions
function formatWord(word: WordEntry): string {
return `${word.data.word}: ${word.data.meaning}`;
}
function getCategoryLabel(category: WordData['category']): string {
// TypeScript knows valid category values
return category;
}
Collection queries run at build time, not runtime. This makes them extremely fast since data is pre-rendered.
- Filter early: Use the filter callback in
getCollection() instead of filtering arrays afterwards
- Cache results: Store filtered/sorted collections in variables
- Avoid redundant queries: Query once and reuse the result
- Use
getEntry() for single items instead of filtering collections
// ✅ Good - filter during query
const featured = await getCollection('words-es', (entry) => entry.data.featured);
// ❌ Less efficient - filter after query
const all = await getCollection('words-es');
const featured = all.filter((entry) => entry.data.featured);