To integrate MarbleCMS in your Astro application, you first need an Astro site. If you don’t have one, you can check out the Astro documentation to get started, or use our Astro blog template for a ready-to-go solution. Astro’s Content Collections are the best way to manage content in your project. We’ll use a Content Loader to fetch your posts from MarbleCMS and make them available in your Astro site with full type-safety. You can learn more about this in the Astro Content Collections documentation.
1

Set up environment variables

First, add your MarbleCMS API URL and Workspace Key to your environment variables. Create a .env file in the root of your Astro project and add the following:
.env
MARBLE_API_URL="https://api.marblecms.com/v1"
MARBLE_WORKSPACE_KEY="your_workspace_key_here"
Important: Never expose your MARBLE_WORKSPACE_KEY in client-side code. Your environment variables are loaded into the server context during the build process and should not be shared with the client. For client-side updates, use webhooks to trigger rebuilds.
You can find your Workspace Key in your MarbleCMS dashboard under your workspace settings.
2

Configure your content collection

Next, define your content collection. This tells Astro how to fetch and validate your data from MarbleCMS. Create a file named src/content/config.ts and add the following code.This configuration defines a posts collection, specifies a schema for your post data using Zod for validation, and includes a loader function that fetches all your posts from the MarbleCMS API.
src/content.config.ts
import { z, defineCollection } from "astro:content";

const key = import.meta.env.MARBLE_WORKSPACE_KEY;
const url = import.meta.env.MARBLE_API_URL;

const postSchema = z.object({
  id: z.string(),
  slug: z.string(),
  title: z.string(),
  content: z.string(),
  description: z.string(),
  coverImage: z.string().url(),
  publishedAt: z.coerce.date(),
  authors: z.array(
    z.object({
      id: z.string(),
      name: z.string(),
      image: z.string().url(),
    })
  ),
  category: z.object({
    id: z.string(),
    name: z.string(),
    slug: z.string(),
  }),
  tags: z.array(
    z.object({
      id: z.string(),
      name: z.string(),
      slug: z.string(),
    })
  ),
  attribution: z
    .object({
      author: z.string(),
      url: z.string().url(),
    })
    .nullable(),
});

type Post = z.infer<typeof postSchema>;

const postsCollection = defineCollection({
  type: "data",
  schema: postSchema,
  loader: async () => {
    const response = await fetch(`${url}/${key}/posts`);
    const { posts }: { posts: Post[] } = await response.json();
    return Object.fromEntries(posts.map((post) => [post.slug, post]));
  },
});

export const collections = {
  posts: postsCollection,
};
3

Display a list of posts

Now, you can use getCollection() to fetch all your posts and display them on a page. Create a file at src/pages/blog/index.astro to list all your posts.
src/pages/blog/index.astro
---
import Layout from '../../layouts/Layout.astro';
import { getCollection } from 'astro:content';

const posts = await getCollection('posts');
---

<Layout>
  <h1>Blog</h1>
  <ul>
    {posts.map((post) => (
      <li>
        <a href={`/blog/${post.slug}`}>{post.data.title}</a>
      </li>
    ))}
  </ul>
</Layout>
4

Display a single post

To display individual posts, create a dynamic route that fetches a single post based on its slug. Create a file at src/pages/blog/[...slug].astro.This page uses getStaticPaths() to generate a static page for every post at build time.
src/pages/blog/[...slug].astro
---
import Layout from '../../layouts/Layout.astro';
import { getCollection } from 'astro:content';

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

const { entry } = Astro.props;
---

<Layout>
  <article>
    <h1>{entry.data.title}</h1>
    <p>Published on: {new Date(entry.data.publishedAt).toLocaleDateString()}</p>
    <img src={entry.data.coverImage} alt={entry.data.title} width="800" />
    <div set:html={entry.data.content} />
  </article>
</Layout>
Rendering HTML ContentWe use the set:html directive to render the post content. MarbleCMS sanitizes all HTML content on the server, ensuring it’s safe to render directly in your application.
That’s it! You now have a fully functional blog in your Astro project powered by MarbleCMS. For a complete, ready-to-use example, you can clone our Astro blog template.