Load Goodreads data for books in shelves/lists, user updates, and author blogs into Astro.
[!NOTE]
astro-loader-goodreads
uses the Astro Content Loader API to fetch data from Goodreads RSS feeds.See the Usage section below for instructions on how to use the package to load the fetched Goodreads data into Astro Content Collections.
- Load Bookshelves: Import your Goodreads shelves to showcase your reading list, books you've read, or want to read.
- User Updates: Display your latest Goodreads activity including reading status updates, reviews, and likes.
- Author Blogs: Fetch author blogs from Goodreads to display updates from your favorite authors.
- Astro Content Collections: Seamlessly integrates with Astro's content collection system for type-safe data access.
Below are some examples of websites that use astro-loader-goodreads
. If you wish to add your site to this list, open a pull request!
Site | Page | Description | Source |
---|---|---|---|
sadman.ca | sadman.ca/about | Books I'm currently reading and have recently finished. | → |
npm add astro-loader-goodreads
Property | Description | Required | Default |
---|---|---|---|
url |
The URL of your Goodreads shelf, user, or author. | ✅ | - |
refreshIntervalDays |
Number of days to cache data before fetching again from Goodreads. | ❌ | 0 |
When refreshIntervalDays
is set (e.g., to 7
for weekly updates), the loader will only fetch new data from Goodreads when that many days have passed since the last fetch.
feat: Add optional loader option refreshIntervalDays
. If not specified, no caching is done between builds (Astro's default data caching between page loads still applies).
astro-loader-goodreads
supports loading Goodreads data from 3 types of urls:
- Shelves: Load books from a Goodreads shelf.
- User Updates: Load a Goodreads user's updates feed.
- Author Blogs: Load a Goodreads author's blog.
In your content.config.ts
or src/content/config.ts
file, you can define your content collections using each type of URL with the goodreadsLoader
function.
[!NOTE] For the full list of fields available for Astro content collections created using
astro-loader-goodreads
, see the Data Schema section below.
To load data for books from a Goodreads shelf, use the shelf's URL (e.g. https://www.goodreads.com/review/list/152185079-sadman-hossain?shelf=currently-reading). astro-loader-goodreads
will convert it to the correct RSS feed URL automatically.
[!IMPORTANT] The RSS feed for a Goodreads shelf only includes the last 100 books added to that shelf. This means that if there are more than 100 books in a shelf,
astro-loader-goodreads
will not be able to retrieve them all.You can, however, create multiple shelves (e.g. read-2025, read-2026, etc.) and then create a content collection for each shelf to get around this limitation.
// src/content/config.ts
import { defineCollection } from "astro:content";
import { goodreadsLoader } from "astro-loader-goodreads";
const currentlyReading = defineCollection({
loader: goodreadsLoader({
url: "https://www.goodreads.com/review/list/152185079-sadman-hossain?shelf=currently-reading",
refreshIntervalDays: 7, // optional parameter; set to only fetch new data once per week
}),
});
export const collections = { currentlyReading };
---
// src/pages/reading.astro
import { getCollection } from "astro:content";
const books = await getCollection("currentlyReading");
---
<h1>Books I'm Currently Reading</h1>
<div class="book-grid">
{books.map((book) => (
<div class="book-card">
<img src={book.data.book_large_image_url} alt={`Cover of ${book.data.title}`} />
<h2>{book.data.title}</h2>
<p class="author">by {book.data.author_name}</p>
{book.data.user_rating > 0 && (
<p class="rating">My rating: {book.data.user_rating}/5</p>
)}
<a href={book.data.link} target="_blank" rel="noopener noreferrer">
View on Goodreads
</a>
</div>
))}
</div>
<style>
.book-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 2rem;
}
.book-card {
border: 1px solid #eee;
border-radius: 0.5rem;
padding: 1rem;
text-align: center;
}
.book-card img {
max-width: 100%;
height: auto;
}
</style>
To load a Goodreads user's updates feed, use the user's profile URL (e.g. https://www.goodreads.com/user/show/152185079-sadman-hossain). astro-loader-goodreads
will convert it to the correct RSS feed URL automatically.
[!IMPORTANT] The RSS feed for a Goodreads user's updates only includes the last 10 updates by that user. This means that
astro-loader-goodreads
cannot retrieve more than 10 updates for any single user.
// src/content/config.ts
import { defineCollection } from "astro:content";
import { goodreadsLoader } from "astro-loader-goodreads";
const userUpdates = defineCollection({
loader: goodreadsLoader({
url: "https://www.goodreads.com/user/show/152185079-sadman-hossain",
}),
});
export const collections = { userUpdates };
---
// src/pages/activity.astro
import { getCollection } from "astro:content";
const updates = await getCollection("userUpdates");
// Sort updates by publication date (newest first)
const sortedUpdates = updates.sort((a, b) =>
new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
);
---
<h1>My Goodreads Activity</h1>
<div class="activity-feed">
{sortedUpdates.map((update) => {
const itemData = update.data.itemData;
return (
<div class="activity-item">
<p class="date">{new Date(update.data.pubDate).toLocaleDateString()}</p>
{itemData?.type === "ReadStatus" && (
<div class="read-status">
<img
src={itemData.bookImgUrl}
alt={`Cover of ${itemData.bookTitle}`}
width="50"
/>
<div>
<p>
<strong>{itemData.readingStatus}</strong>
<a href={itemData.bookUrl}>{itemData.bookTitle}</a>
by {itemData.bookAuthor}
</p>
</div>
</div>
)}
{itemData?.type === "Review" && (
<div class="review">
<img
src={itemData.bookImgUrl}
alt={`Cover of ${itemData.bookTitle}`}
width="50"
/>
<div>
<p>
Rated <strong>{itemData.rating} stars</strong> for
<a href={itemData.bookUrl}>{itemData.bookTitle}</a>
by {itemData.bookAuthor}
</p>
</div>
</div>
)}
{itemData?.type === "CommentReview" && (
<div class="comment-review">
<div>
<p>
Commented on <a href={itemData.reviewUrl}>{itemData.reviewUser}'s review</a> of
<a href={itemData.bookUrl}>{itemData.bookTitle}</a>:
</p>
<blockquote>"{itemData.comment}"</blockquote>
</div>
</div>
)}
{/* Add additional item types as needed */}
</div>
);
})}
</div>
To load Goodreads author blog posts, use the author's URL (e.g. https://www.goodreads.com/author/show/3389.Stephen_King). astro-loader-goodreads
will append the necessary parameters to fetch the blog RSS feed automatically.
// src/content/config.ts
import { defineCollection } from "astro:content";
import { goodreadsLoader } from "astro-loader-goodreads";
const authorBlog = defineCollection({
loader: goodreadsLoader({
url: "https://www.goodreads.com/author/show/3389.Stephen_King",
}),
});
export const collections = { authorBlog };
---
// src/pages/author-updates.astro
import { getCollection } from "astro:content";
const posts = await getCollection("authorBlog");
---
<h1>Latest Updates from Stephen Kingn</h1>
<div class="blog-posts">
{posts.map((post) => (
<article class="blog-post">
<h2>{post.data.title}</h2>
<p class="date">Published: {new Date(post.data.pubDate).toLocaleDateString()}</p>
{post.data.content && (
<div class="content" set:html={post.data.content} />
)}
<a href={post.data.link}>Read on Goodreads</a>
</article>
))}
</div>
The astro-loader-goodreads package provides three main schemas:
-
BookSchema
- For books from Goodreads shelves -
UserUpdateSchema
- For user updates (with various activity types) -
AuthorBlogSchema
- For author blog posts
This schema is used when loading data from a Goodreads shelf.
export const BookSchema = z.object({
id: z.coerce.string(),
title: z.coerce.string(),
guid: z.string(),
pubDate: z.string(),
link: z.string(),
book_id: z.coerce.string(),
book_image_url: z.string(),
book_small_image_url: z.string(),
book_medium_image_url: z.string(),
book_large_image_url: z.string(),
book_description: z.string(),
num_pages: z.string().optional(),
author_name: z.string(),
isbn: z.coerce.string(),
user_name: z.string(),
user_rating: z.number(),
user_read_at: z.string(),
user_date_added: z.string(),
user_date_created: z.string(),
user_shelves: z.string().optional(),
user_review: z.string().optional(),
average_rating: z.number(),
book_published: z.coerce.string(),
});
Field | Description |
---|---|
id |
Unique identifier for the book |
title |
Book title |
guid |
Global unique identifier for this entry |
pubDate |
Publication date of this entry in the feed |
link |
URL to the book's Goodreads page |
book_id |
Goodreads ID for the book |
book_image_url |
URL to the book cover image |
book_small_image_url |
URL to small version of book cover (50×75 px) |
book_medium_image_url |
URL to medium version of book cover (65×98 px) |
book_large_image_url |
URL to large version of book cover (316×475 px) |
book_description |
Description/synopsis of the book |
num_pages |
Number of pages in the book (optional) |
author_name |
Name of the book's author |
isbn |
International Standard Book Number |
user_name |
Username of who added the book to their shelf |
user_rating |
Rating given by the user (0-5) |
user_read_at |
Date when the user finished reading the book |
user_date_added |
Date when the book was added to the user's shelf |
user_date_created |
Date when this entry was created |
user_shelves |
List of shelves the user assigned to this book (optional) |
user_review |
User's review of the book (optional) |
average_rating |
Average rating on Goodreads |
book_published |
Book's original publication date |
This schema is used when loading data from a Goodreads user's updates feed.
export const UserUpdateSchema = z.object({
id: z.string(),
title: z.string(),
link: z.string().optional(),
description: z.string().optional(),
pubDate: z.string(),
itemType: z.string().optional(),
itemData: ItemDataSchema.optional()
});
The itemData
field contains a discriminated union based on the type
field:
When a user follows an author:
{
type: "AuthorFollowing",
followId: string,
userUrl: string,
authorId: string
}
When a user reports progress on a book:
{
type: "UserStatus",
userUrl: string,
percentRead: string,
bookUrl: string,
bookTitle: string,
bookAuthor: string,
bookImgUrl: string
}
When a user changes their reading status:
{
type: "ReadStatus",
userUrl: string,
readingStatus: string, // 'started reading', 'wants to read', or 'finished reading'
bookUrl: string,
bookTitle: string,
bookAuthor: string,
bookImgUrl: string
}
When a user posts a review:
{
type: "Review",
userUrl: string,
rating: number,
bookUrl: string,
bookTitle: string,
bookAuthor: string,
bookImgUrl: string
}
When a user likes someone's review:
{
type: "LikeReview",
userUrl: string,
reviewUrl: string,
reviewUser: string,
bookUrl: string,
bookTitle: string,
bookImgUrl: string
}
When a user likes someone's read status:
{
type: "LikeReadStatus",
userUrl: string,
readStatusUser: string,
readStatusUserImgUrl: string,
readStatus: string,
bookUrl: string,
bookTitle: string
}
When a user comments on a status:
{
type: "CommentStatus",
userUrl: string,
statusUrl: string,
statusUser: string,
comment: string
}
When a user comments on a review:
{
type: "CommentReview",
userUrl: string,
reviewUrl: string,
reviewUser: string,
bookUrl: string,
bookTitle: string,
bookAuthor: string,
comment: string
}
This schema is used when loading data from a Goodreads author's blog.
export const AuthorBlogSchema = z.object({
id: z.string(),
title: z.string(),
link: z.string(),
description: z.string(),
pubDate: z.string(),
author: z.string().optional(),
content: z.string().optional(),
});
Field | Description |
---|---|
id |
Unique identifier for the blog post |
title |
Blog post title |
link |
URL to the blog post |
description |
Raw HTML description of the blog post |
pubDate |
Publication date |
author |
Author's name (if available) |
content |
Main content of the blog post (if available) |
astro-loader-goodreads
is MIT licensed.
Built with ♥ by @sadmanca!