Starter KitAstraCMS
Integrations

Astro Loader

Use AstraCMS as a content source for Astro.js Content Collections

The @astracms/astro-loader package provides Astro Content Collection loaders for seamlessly integrating AstraCMS content into your Astro.js projects.

Features

  • 🚀 4 Content Loaders - Posts, Categories, Tags, and Authors
  • 🔐 Dual API Support - Works with both v1 (workspaceId) and v2 (API key) authentication
  • 📝 Markdown Rendering - Full support for Astro's <Content /> component
  • 🔄 Incremental Builds - Content digests for efficient rebuilds
  • 📘 TypeScript - Full type safety with Zod schema validation
  • 🎯 Filtering - Filter posts by categories, tags, and search queries

Installation

npm install @astracms/astro-loader
pnpm add @astracms/astro-loader
yarn add @astracms/astro-loader
bun add @astracms/astro-loader

Quick Start

Configure Environment Variables

Create a .env file in your Astro project:

.env
ASTRACMS_API_URL=https://api.astracms.com
ASTRACMS_API_KEY=your_api_key_here

Set Up Content Collections

Create or update your content configuration file:

src/content.config.ts
import { defineCollection } from 'astro:content';
import {
  postsLoader,
  categoriesLoader,
  tagsLoader,
  authorsLoader,
} from '@astracms/astro-loader';

const config = {
  apiUrl: import.meta.env.ASTRACMS_API_URL,
  apiKey: import.meta.env.ASTRACMS_API_KEY,
};

const posts = defineCollection({
  loader: postsLoader({
    ...config,
    format: 'markdown', // or 'html'
  }),
});

const categories = defineCollection({
  loader: categoriesLoader(config),
});

const tags = defineCollection({
  loader: tagsLoader(config),
});

const authors = defineCollection({
  loader: authorsLoader(config),
});

export const collections = { posts, categories, tags, authors };

Use Content in Your Pages

src/pages/blog/[slug].astro
---
import { getCollection, render } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('posts');
  return posts.map((post) => ({
    params: { slug: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await render(post);
---

<article>
  <h1>{post.data.title}</h1>
  <p>{post.data.description}</p>
  
  {post.data.coverImage && (
    <img src={post.data.coverImage} alt={post.data.title} />
  )}
  
  <div class="meta">
    <span>Category: {post.data.category.name}</span>
    <span>Published: {post.data.publishedAt.toLocaleDateString()}</span>
  </div>
  
  <div class="tags">
    {post.data.tags.map((tag) => (
      <a href={`/tags/${tag.slug}`}>{tag.name}</a>
    ))}
  </div>
  
  <Content />
  
  <div class="authors">
    {post.data.authors.map((author) => (
      <div class="author">
        {author.image && <img src={author.image} alt={author.name} />}
        <span>{author.name}</span>
        {author.role && <span>{author.role}</span>}
      </div>
    ))}
  </div>
</article>

Loaders Reference

postsLoader

Loads blog posts from AstraCMS with full support for content rendering.

postsLoader({
  apiUrl: string,           // Required: AstraCMS API base URL
  apiKey?: string,          // API key (recommended for v2 API)
  workspaceId?: string,     // Workspace ID (required for v1 API)
  format?: 'html' | 'markdown', // Content format (default: 'markdown')
  categories?: string[],    // Filter by category slugs
  excludeCategories?: string[], // Exclude category slugs
  tags?: string[],          // Filter by tag slugs
  excludeTags?: string[],   // Exclude tag slugs
  query?: string,           // Search query
})

Post Data Schema:

FieldTypeDescription
titlestringPost title
descriptionstringPost description/excerpt
slugstringURL slug
coverImagestring | nullCover image URL
featuredbooleanFeatured post flag
publishedAtDatePublication date
updatedAtDateLast update date
categoryCategoryPost category
tagsTag[]Post tags
authorsAuthor[]Post authors
attributionstring | nullContent attribution

categoriesLoader

Loads categories from AstraCMS.

categoriesLoader({
  apiUrl: string,
  apiKey?: string,
  workspaceId?: string,
  query?: string,  // Search query
})

Category Data Schema:

FieldTypeDescription
namestringCategory name
slugstringURL slug
descriptionstring | nullCategory description

tagsLoader

Loads tags from AstraCMS.

tagsLoader({
  apiUrl: string,
  apiKey?: string,
  workspaceId?: string,
  query?: string,  // Search query
})

Tag Data Schema:

FieldTypeDescription
namestringTag name
slugstringURL slug
descriptionstring | nullTag description

authorsLoader

Loads author profiles from AstraCMS.

authorsLoader({
  apiUrl: string,
  apiKey?: string,
  workspaceId?: string,
  query?: string,  // Search query
})

Author Data Schema:

FieldTypeDescription
namestringAuthor name
slugstringURL slug
biostring | nullAuthor biography
imagestring | nullProfile image URL
rolestring | nullAuthor role/title
socialsAuthorSocial[]Social media links

Authentication

The loader supports two authentication methods:

postsLoader({
  apiUrl: 'https://api.astracms.com',
  apiKey: import.meta.env.ASTRACMS_API_KEY,
})

Workspace ID (v1 API)

postsLoader({
  apiUrl: 'https://api.astracms.com',
  workspaceId: 'your-workspace-id',
})

We recommend using API key authentication (v2 API) as it provides better security and doesn't expose your workspace ID in the URL.

Advanced Usage

Filter Posts by Category

const posts = defineCollection({
  loader: postsLoader({
    ...config,
    categories: ['technology', 'tutorials'],
  }),
});

Exclude Certain Tags

const posts = defineCollection({
  loader: postsLoader({
    ...config,
    excludeTags: ['internal', 'draft'],
  }),
});

Use HTML Format

If you prefer to use pre-rendered HTML instead of markdown:

const posts = defineCollection({
  loader: postsLoader({
    ...config,
    format: 'html',
  }),
});

Extend Schemas

You can import and extend the default schemas:

import { z } from 'astro:content';
import { postSchema } from '@astracms/astro-loader';

const extendedPostSchema = postSchema.extend({
  customField: z.string().optional(),
});

Requirements

  • Astro 5.0 or later
  • AstraCMS account with API access

Next Steps

Last updated on

On this page