Load Readwise highlights for Astro's content loader.
In this example, we will create a tagged highlight from the following article:
chriscoyier.net/2022/06/27/there-is-no-bar


Go to readwise.io/access_token:

You can save your access token in a private .env
file.
Add astro-loader-readwise to your project:
npm install astro-loader-readwise
Use it in your collection config file:
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { readwiseLoader } from 'astro-loader-readwise';
const readwiseHighlights = defineCollection({
loader: readwiseLoader({
READWISE_ACCESS_TOKEN: import.meta.env.READWISE_ACCESS_TOKEN
filterTags: [
'astro-loader-example'
]
})
});
export const collections = { readwiseHighlights };
Create an endpoint to display the content collection:
// src/pages/astro-loader-readwise.json.ts
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
const highlightsCollection = await getCollection('readwiseHighlights');
const highlightsJSON = JSON.stringify(highlightsCollection);
export const GET: APIRoute = () => {
return new Response(highlightsJSON, {
headers: { 'Content-Type': 'application/json' }
});
};
Run the development server:
-
npx astro dev --port 4321
-
From the browser, go to localhost:4321/astro-loader-readwise.json
While some fields have been shortened to keep things concise,
you can expect results comparable to the following:
[{
"id": "987654321",
"data": {
"text":
"I’d *like* to write better individual blog posts, but something has always compelled me to punt out a thought early rather than wait until I have some perfect way to present it. And for the record, I don’t mind reading your posts like that either. We’re not shootin’ for the Pulitzer over here mmkay.",
"id": 987654321,
"is_deleted": false,
"note": "",
"location": 4567,
"location_type": "offset",
"highlighted_at": "2025-02-08T12:00:00.000Z",
"created_at": "2025-02-08T11:59:30.000Z",
"updated_at": "2025-02-08T12:00:00.020Z",
"external_id": "01abc000000000000000000000",
"end_location": null,
"url": "https://read.readwise.io/read/01abc000000000000000000000",
"tags": [
{
"id": 191919191,
"name": "astro-loader-example"
}
],
"is_favorite": false,
"is_discard": false,
"readwise_url": "https://readwise.io/open/987654321",
"color": "",
"book": {
"title": "There Is No Bar",
"readable_title": "There Is No Bar",
"author": "Chris Coyier",
"source_url": "https://chriscoyier.net/2022/06/27/there-is-no-bar/",
"asin": null,
"book_tags": [],
"category": "articles",
"cover_image_url": "https://i0.wp.com/.../Frame-1.png?fit=1200%2C1200&ssl=1",
"document_note": null,
"is_deleted": false,
"readwise_url": "https://readwise.io/bookreview/12345678",
"source": "reader",
"summary": "There is no standard for the quality of a blog post; ...",
"unique_url": "https://read.readwise.io/read/02def000000000000000000000",
"user_book_id": 12345678
},
"book_id": 12345678
},
"collection": "readwiseHighlights"
}]
Required. Your Readwise API access token.
readwiseLoader({
READWISE_ACCESS_TOKEN: import.meta.env.READWISE_ACCESS_TOKEN // Required
})
Array of tags to filter highlights by.
Only highlights containing at least one of these tags will be loaded.
readwiseLoader({
filterTags: ['astro-loader'] // Default: ['astro-loader']
})
Array of keys to omit from highlight objects.
Useful for server-rendered routes if you don't need certain fields.
readwiseLoader({
omitHighlightKeys: [
'created_at',
'updated_at',
'is_deleted'
'note'
'location'
'location_type'
'created_at'
'updated_at'
'external_id'
'end_location'
'url'
'is_favorite'
'is_discard'
'readwise_url'
'color'
'book_id'
]
// Default: []
})
Array of keys to omit from book objects.
Defaults to [ 'highlights' ] to prevent duplicate data.
readwiseLoader({
omitBookKeys: ['highlights', 'summary'] // Default: ['highlights']
})
-
Use a dedicated highlight tag for all highlights you intend to fetch with the loader
-
If you have several pages using Readwise highlights of varying tags, filter them with flatMap:
// src/pages/quotes/frontend.json.ts import type { APIRoute } from 'astro'; import { getCollection } from 'astro:content'; const filter = [ // We want to keep highlights that match any of these tags, // with the prerequisite that they also contain the tags // specified in our loader's `filterTags`. 'css', 'html', 'javascript', ]; export const GET: APIRoute = async () => { const highlights = await getCollection('readwiseHighlights'); const filtered = highlights.flatMap(entry => { const hasFilterTags = entry.data.tags?.some(tag => filter.includes(tag.name)); return hasFilterTags ? [entry] // Keep : []; // Remove }); return new Response(JSON.stringify(filtered), { headers: { 'Content-Type': 'application/json' } }); };
- [ ] Identify nullable fields from Readwise API
- [ ] Provide subset schema for common use-cases
- [ ] Minimal demo on GitHub pages
- [ ] Test equality for pre-rendered json endpoint with expected output