npx skills add https://github.com/johnlindquist/claude --skill raycast-extensionSKILL.md
Raycast Extension Development
Quick Start
- Create project structure
- Write package.json with extension config
- Implement command in src/
- Run
npm install && npm run dev
Project Structure
my-extension/
├── package.json # Extension manifest + dependencies
├── tsconfig.json # TypeScript config
├── .eslintrc.json # ESLint config
├── raycast-env.d.ts # Type definitions (auto-generated)
├── assets/
│ └── extension-icon.png # 512x512 PNG icon
└── src/
└── command-name.tsx # Command implementation
package.json Template
{
"name": "extension-name",
"title": "Extension Title",
"description": "What this extension does",
"icon": "extension-icon.png",
"author": "author-name",
"categories": ["Productivity", "Developer Tools"],
"license": "MIT",
"commands": [
{
"name": "command-name",
"title": "Command Title",
"description": "What this command does",
"mode": "view",
"keywords": ["keyword1", "keyword2"]
}
],
"dependencies": {
"@raycast/api": "^1.83.1",
"@raycast/utils": "^1.17.0"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.11",
"@types/node": "22.5.4",
"@types/react": "18.3.3",
"eslint": "^8.57.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4"
},
"scripts": {
"build": "ray build --skip-types -e dist -o dist",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint"
}
}
Command Modes
| Mode | Use Case |
|---|---|
view | Show UI with Detail, List, Form, Grid |
no-view | Background task, clipboard, notifications only |
menu-bar | Menu bar icon with dropdown |
Hotkey Configuration
Add to command in package.json:
"hotkey": {
"modifiers": ["opt"],
"key": "m"
}
Modifiers: cmd, opt, ctrl, shift
Note: Hotkeys in package.json are suggestions. Users set them in Raycast Preferences → Extensions.
tsconfig.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "react-jsx",
"lib": ["ES2022"],
"module": "ES2022",
"moduleResolution": "bundler",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
},
"include": ["src/**/*", "raycast-env.d.ts"]
}
.eslintrc.json
{
"root": true,
"extends": ["@raycast"]
}
Command Patterns
No-View Command (Background Task)
import { showHUD, Clipboard, showToast, Toast } from "@raycast/api";
export default async function Command() {
const toast = await showToast({
style: Toast.Style.Animated,
title: "Working...",
});
try {
// Do work
const result = await doSomething();
await Clipboard.copy(result);
await showHUD("✅ Done!");
} catch (error) {
toast.style = Toast.Style.Failure;
toast.title = "Failed";
toast.message = error instanceof Error ? error.message : "Unknown error";
}
}
View Command (List)
import { List, ActionPanel, Action } from "@raycast/api";
export default function Command() {
return (
<List>
<List.Item
title="Item"
actions={
<ActionPanel>
<Action.CopyToClipboard content="text" />
</ActionPanel>
}
/>
</List>
);
}
View Command (Detail)
import { Detail } from "@raycast/api";
export default function Command() {
const markdown = `# Hello World`;
return <Detail markdown={markdown} />;
}
Performance & Caching
Instant Load Pattern (No Empty Flash)
Use synchronous cache read + async refresh for instant perceived load:
import { List, Cache } from "@raycast/api";
import { useCachedPromise, withCache } from "@raycast/utils";
const cache = new Cache();
const CACHE_KEY = "myData";
// Read cache synchronously at module load (before React renders)
function getInitialData(): MyData[] {
const cached = cache.get(CACHE_KEY);
if (cached) {
try {
return JSON.parse(cached);
} catch {
return [];
}
}
return [];
}
// Expensive async operation wrapped with withCache (5 min TTL)
const fetchExpensiveData = withCache(
async () => {
// Your expensive operation here
return await someSlowOperation();
},
{ maxAge: 5 * 60 * 1000 }
);
async function fetchAllData(): Promise<MyData[]> {
const data = await fetchExpensiveData();
// Update cache for next launch
cache.set(CACHE_KEY, JSON.stringify(data));
return data;
}
export default function Command() {
const { data, isLoading } = useCachedPromise(fetchAllData, [], {
initialData: getInitialData(), // Sync read - instant render!
keepPreviousData: true,
});
return (
<List isLoading={isLoading && !data?.lengt
...
Repository
johnlindquist/claudeParent repository
Repository Stats
Stars12
Forks2