Building my first Raycast Extension

and it was easier than I expected

As a follow up from my previous post about how to use Raycast to get my most recent blog posts, I took it upon myself to investigate how to create a custom Raycast extension for the same purpose.

I did not expect the development process to be so easy.

The end result -

I created the extension Get Blog Post, which now lives in my favorites when I open up Raycast.

My Raycast home panel, with my new Get Blog Posts

When I press Enter on the extension, it opens and makes a fetch call to my website for all of of posts.

And when I select a post, its url copies to my clipboard.

The setup

I'm happy to report that, as a React developer, this couldn't have been easier to set up.

Raycast has its own node library with a slim library of UI elements and React hooks and much more.

For more specifics, here are the Raycast docs.

Below is the entirety of my code -

1import { useEffect, useState } from "react"; 2import { Action, ActionPanel, List } from "@raycast/api"; 3import { useFetch } from "@raycast/utils"; 4 5interface BlogPost { 6 title: string; 7 description?: string; 8 slug: string; 9} 10 11export default function Command() { 12 const [searchText, setSearchText] = useState<string>(""); 13 const [filteredList, filterList] = useState<BlogPost[]>([]); 14 15 const BASE_URL = "https://natespilman.com"; 16 const API_ENDPOINT = `${BASE_URL}/posts`; 17 const BLOG_URL = `${BASE_URL}/blog`; 18 19 const { data, isLoading } = useFetch<BlogPost[]>(API_ENDPOINT); 20 console.log({filteredList}) 21 22 useEffect(() => { 23 if (data) { 24 filterList( 25 data.filter((item) => { 26 const searchLower = searchText.toLowerCase(); 27 return ( 28 (item.title?.toLowerCase() || "").includes(searchLower) || 29 (item.description?.toLowerCase() || "").includes(searchLower) 30 ); 31 }) 32 ); 33 } 34 }, [searchText, data]); 35 36 return ( 37 <> 38 <List 39 filtering={false} 40 onSearchTextChange={setSearchText} 41 navigationTitle="Search Posts" 42 searchBarPlaceholder="Search by title or description" 43 isLoading={isLoading} 44 > 45 {filteredList?.map((item) => ( 46 <List.Item 47 key={item.slug} 48 title={item.title} 49 subtitle={item.description} 50 actions={ 51 <ActionPanel> 52 <Action.CopyToClipboard 53 title="Select" 54 content={`${BLOG_URL}/${item.slug}`} 55 /> 56 </ActionPanel> 57 } 58 /> 59 ))} 60 </List> 61 </> 62 ); 63}
tsx