Skip to main content
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';
});
// 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);
});
// 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>
---
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;
}

Performance Tips

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);

Build docs developers (and LLMs) love