# Developers 2 > ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-25a03b5271959426230e724a733f30e7597dd1bf%2Faction-panel.webp?alt=me --- # Source: https://developers.raycast.com/api-reference/user-interface/action-panel.md # Action Panel ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-25a03b5271959426230e724a733f30e7597dd1bf%2Faction-panel.webp?alt=media) ## API Reference ### ActionPanel Exposes a list of [actions](https://developers.raycast.com/api-reference/user-interface/actions) that can be performed by the user. Often items are context-aware, e.g., based on the selected list item. Actions can be grouped into semantic\ sections and can have keyboard shortcuts assigned. The first and second action become the primary and secondary action. They automatically get the default keyboard shortcuts assigned.\ In [List](https://developers.raycast.com/api-reference/user-interface/list), [Grid](https://developers.raycast.com/api-reference/user-interface/grid), and [Detail](https://developers.raycast.com/api-reference/user-interface/detail), this is `↵` for the primary and `⌘` `↵` for the secondary action. In [Form](https://developers.raycast.com/api-reference/user-interface/form) it's `⌘` `↵` for the primary and `⌘` `⇧` `↵` for the secondary.\ Keep in mind that while you can specify an alternative shortcut for the primary and secondary actions, it won't be displayed in the Action Panel. #### Example ```typescript import { ActionPanel, Action, List } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | -------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------- | | children | Sections or Actions. If Action elements are specified, a default section is automatically created. | [`ActionPanel.Children`](#actionpanel.children) | - | | title | The title displayed at the top of the panel | `string` | - | ### ActionPanel.Section A group of visually separated items. Use sections when the [ActionPanel](#actionpanel) contains a lot of actions to help guide the user to related actions.\ For example, create a section for all copy actions. #### Example ```typescript import { ActionPanel, Action, List } from "@raycast/api"; export default function Command() { return ( console.log("Close PR #1")} /> } /> ); } ``` #### Props | Prop | Description | Type | Default | | -------- | --------------------------------- | --------------------------------------------------------------- | ------- | | children | The item elements of the section. | [`ActionPanel.Section.Children`](#actionpanel.section.children) | - | | title | Title displayed above the section | `string` | - | ### ActionPanel.Submenu A very specific action that replaces the current [ActionPanel](#actionpanel) with its children when selected. This is handy when an action needs to select from a range of options. For example, to add a label to a GitHub pull request or an assignee to a todo. #### Example ```typescript import { Action, ActionPanel, Color, Icon, List } from "@raycast/api"; export default function Command() { return ( console.log("Add bug label")} /> console.log("Add enhancement label")} /> console.log("Add help wanted label")} /> } /> ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | title\* | The title displayed for submenu. | `string` | - | | autoFocus | Indicates whether the ActionPanel.Submenu should be focused automatically when the parent ActionPanel (or Actionpanel.Submenu) opens. | `boolean` | - | | children | Items of the submenu. | [`ActionPanel.Submenu.Children`](#actionpanel.submenu.children) | - | | filtering | Toggles Raycast filtering. When `true`, Raycast will use the query in the search bar to filter the items. When `false`, the extension needs to take care of the filtering. You can further define how native filtering orders sections by setting an object with a `keepSectionOrder` property: When `true`, ensures that Raycast filtering maintains the section order as defined in the extension. When `false`, filtering may change the section order depending on the ranking values of items. | `boolean` or `{ keepSectionOrder: boolean }` | - | | icon | The icon displayed for the submenu. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | isLoading | Indicates whether a loading indicator should be shown or hidden next to the search bar | `boolean` | - | | onOpen | Callback that is triggered when the Submenu is opened. This callback can be used to fetch its content lazily: `js function LazySubmenu() { const [content, setContent] = useState(null) return ( fetchSubmenuContent().then(setContent)}> {content} ) }` | `() => void` | - | | onSearchTextChange | Callback triggered when the search bar text changes. | `(text: string) => void` | - | | shortcut | The keyboard shortcut for the submenu. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | throttle | Defines whether the `onSearchTextChange` handler will be triggered on every keyboard press or with a delay for throttling the events. Recommended to set to `true` when using custom filtering logic with asynchronous operations (e.g. network requests). | `boolean` | - | ## Types ### ActionPanel.Children ```typescript ActionPanel.Children: ActionPanel.Section | ActionPanel.Section[] | ActionPanel.Section.Children | null ``` Supported children for the [ActionPanel](#actionpanel) component. ### ActionPanel.Section.Children ```typescript ActionPanel.Section.Children: Action | Action[] | ReactElement | ReactElement[] | null ``` Supported children for the [ActionPanel.Section](#actionpanel.section) component. ### ActionPanel.Submenu.Children ```typescript ActionPanel.Children: ActionPanel.Section | ActionPanel.Section[] | ActionPanel.Section.Children | null ``` Supported children for the [ActionPanel.Submenu](#actionpanel.submenu) component. --- # Source: https://developers.raycast.com/api-reference/user-interface/actions.md # Actions Our API includes a few built-in actions that can be used for common interactions, such as opening a link or copying some content to the clipboard. By using them, you make sure to follow our human interface guidelines. If you need something custom, use the [`Action`](#action) component. All built-in actions are just abstractions on top of it. ## API Reference ### Action A context-specific action that can be performed by the user. Assign keyboard shortcuts to items to make it easier for users to perform frequently used actions. #### Example ```typescript import { ActionPanel, Action, List } from "@raycast/api"; export default function Command() { return ( console.log("Close PR #1")} /> } /> ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ------- | | title\* | The title displayed for the Action. | `string` | - | | autoFocus | Indicates whether the Action should be focused automatically when the parent ActionPanel (or Actionpanel.Submenu) opens. | `boolean` | - | | icon | The icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onAction | Callback that is triggered when the Action is selected. | `() => void` | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | style | Defines the visual style of the Action. | [`Alert.ActionStyle`](https://developers.raycast.com/feedback/alert#alert.actionstyle) | - | ### Action.CopyToClipboard Action that copies the content to the clipboard. The main window is closed, and a HUD is shown after the content was copied to the clipboard. #### Example ```typescript import { ActionPanel, Action, Detail } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------- | | content\* | The contents that will be copied to the clipboard. | `string` or `number` or [`Clipboard.Content`](https://developers.raycast.com/clipboard#clipboard.content) | - | | concealed | Indicates whether the content be treated as confidential. If `true`, it will not be recorded in the Clipboard History. | `boolean` | - | | icon | A optional icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onCopy | Callback when the content was copied to clipboard. | `(content: string \| number \|` [`Clipboard.Content`](https://developers.raycast.com/clipboard#clipboard.content)`) => void` | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | An optional title for the Action. | `string` | - | ### Action.Open An action to open a file or folder with a specific application, just as if you had double-clicked the file's icon. The main window is closed after the file is opened. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ---------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | target\* | The file, folder or URL to open. | `string` | - | | title\* | The title for the Action. | `string` | - | | application | The application name to use for opening the file. | `string` or [`Application`](https://developers.raycast.com/utilities#application) | - | | icon | The icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onOpen | Callback when the file or folder was opened. | `(target: string) => void` | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | ### Action.OpenInBrowser Action that opens a URL in the default browser. The main window is closed after the URL is opened in the browser. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ------- | | url\* | The URL to open. | `string` | - | | icon | The icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onOpen | Callback when the URL was opened in the browser. | `(url: string) => void` | - | | shortcut | The optional keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | An optional title for the Action. | `string` | - | ### Action.OpenWith Action that opens a file or URL with a specific application. The action opens a sub-menu with all applications that can open the file or URL. The main window is closed after the item is opened in the specified application. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; import { homedir } from "os"; const DESKTOP_DIR = `${homedir()}/Desktop`; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | -------------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | path\* | The path to open. | `string` | - | | icon | The icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onOpen | Callback when the file or folder was opened. | `(path: string) => void` | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | The title for the Action. | `string` | - | ### Action.Paste Action that pastes the content to the front-most applications. The main window is closed after the content is pasted to the front-most application. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ----------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------- | | content\* | The contents that will be pasted to the frontmost application. | `string` or `number` or [`Clipboard.Content`](https://developers.raycast.com/clipboard#clipboard.content) | - | | icon | The icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onPaste | Callback when the content was pasted into the front-most application. | `(content: string \| number \|` [`Clipboard.Content`](https://developers.raycast.com/clipboard#clipboard.content)`) => void` | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | An optional title for the Action. | `string` | - | ### Action.Push Action that pushes a new view to the navigation stack. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; function Ping() { return ( } /> } /> ); } function Pong() { return ; } export default function Command() { return ; } ``` #### Props | Prop | Description | Type | Default | | ---------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ------- | | target\* | The target view that will be pushed to the navigation stack. | `React.ReactNode` | - | | title\* | The title displayed for the Action. | `string` | - | | icon | The icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onPop | Callback when the target view will be popped. | `() => void` | - | | onPush | Callback when the target view was pushed. | `() => void` | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | ### Action.ShowInFinder Action that shows a file or folder in the Finder. The main window is closed after the file or folder is revealed in the Finder. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; import { homedir } from "os"; const DOWNLOADS_DIR = `${homedir()}/Downloads`; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | -------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | path\* | The path to open. | `"fs".PathLike` | - | | icon | A optional icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onShow | Callback when the file or folder was shown in the Finder. | `(path: "fs".PathLike) => void` | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | An optional title for the Action. | `string` | - | ### Action.SubmitForm Action that adds a submit handler for capturing form values. #### Example ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` #### Props | Prop | Description | Type | Default | | -------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------- | | icon | The icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onSubmit | Callback when the Form was submitted. The handler receives a the values object containing the user input. | `(input:` [`Form.Values`](https://developers.raycast.com/api-reference/form#form.values)`) => boolean \| void \| Promise` | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | style | Defines the visual style of the Action. | [`Alert.ActionStyle`](https://developers.raycast.com/feedback/alert#alert.actionstyle) | - | | title | The title displayed for the Action. | `string` | - | ### Action.Trash Action that moves a file or folder to the Trash. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; import { homedir } from "os"; const FILE = `${homedir()}/Downloads/get-rid-of-me.txt`; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ------- | | paths\* | The item or items to move to the trash. | `"fs".PathLike` or `"fs".PathLike[]` | - | | icon | A optional icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onTrash | Callback when all items were moved to the trash. | `(paths: "fs".PathLike \| "fs".PathLike[]) => void` | - | | shortcut | The optional keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | An optional title for the Action. | `string` | - | ### Action.CreateSnippet Action that navigates to the the Create Snippet command with some or all of the fields prefilled. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ----------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | snippet\* | | [`Snippet`](#snippet) | - | | icon | A optional icon displayed for the Action. See Image.ImageLike for the supported formats and types. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | An optional title for the Action. | `string` | - | ### Action.CreateQuicklink Action that navigates to the the Create Quicklink command with some or all of the fields prefilled. #### Example ```typescript import { ActionPanel, Detail, Action } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | quicklink\* | The Quicklink to create. | [`Quicklink`](#quicklink) | - | | icon | A optional icon displayed for the Action. See Image.ImageLike for the supported formats and types. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | An optional title for the Action. | `string` | - | ### Action.ToggleQuickLook Action that toggles the Quick Look to preview a file. #### Example ```typescript import { ActionPanel, List, Action } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | -------- | ------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | icon | The icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | title | The title for the Action. | `string` | - | ### Action.PickDate Action to pick a date. #### Example ```typescript import { ActionPanel, List, Action } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------- | | onChange\* | Callback when the Date was picked | `(date:` [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)`) => void` | - | | title\* | A title for the Action. | `string` | - | | icon | A optional icon displayed for the Action. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | max | The maximum date (inclusive) allowed for selection. - If the PickDate type is `Type.Date`, only the full day date will be considered for comparison, ignoring the time components of the Date object. - If the PickDate type is `Type.DateTime`, both date and time components will be considered for comparison. The date should be a JavaScript Date object. | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) | - | | min | The minimum date (inclusive) allowed for selection. - If the PickDate type is `Type.Date`, only the full day date will be considered for comparison, ignoring the time components of the Date object. - If the PickDate type is `Type.DateTime`, both date and time components will be considered for comparison. The date should be a JavaScript Date object. | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) | - | | shortcut | The keyboard shortcut for the Action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | type | Indicates what types of date components can be picked Defaults to Action.PickDate.Type.DateTime | [`Action.PickDate.Type`](#action.pickdate.type) | - | ## Types ### Snippet #### Properties | Property | Description | Type | | -------------------------------------- | ----------------------------------- | -------- | | text\* | The snippet contents. | `string` | | keyword | The keyword to trigger the snippet. | `string` | | name | The snippet name. | `string` | ### Quicklink #### Properties | Property | Description | Type | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | link\* | The URL or file path, optionally including placeholders such as in "" | `string` | | application | The application that the quicklink should be opened in. | `string` or [`Application`](https://developers.raycast.com/utilities#application) | | icon | The icon to display for the quicklink. | [`Icon`](https://developers.raycast.com/api-reference/icons-and-images#icon) | | name | The quicklink name | `string` | ### Action.Style Defines the visual style of the Action. Use [Action.Style.Regular](#action.style) for displaying a regular actions. Use [Action.Style.Destructive](#action.style) when your action has something that user should be careful about. Use the confirmation [Alert](https://developers.raycast.com/api-reference/feedback/alert) if the action is doing something that user cannot revert. ### Action.PickDate.Type The types of date components the user can pick with an `Action.PickDate`. #### Enumeration members | Name | Description | | -------- | ---------------------------------------------------------------- | | DateTime | Hour and second can be picked in addition to year, month and day | | Date | Only year, month, and day can be picked | ### Action.PickDate.isFullDay A method that determines if a given date represents a full day or a specific time. ```tsx import { ActionPanel, List, Action } from "@raycast/api"; export default function Command() { return ( { if (Action.PickDate.isFullDay(values.reminderDate)) { // the event is for a full day } else { // the event is at a specific time } }} /> } /> ); } ``` --- # Source: https://developers.raycast.com/api-reference/ai.md # AI The AI API provides developers with seamless access to AI functionality without requiring API keys, configuration, or extra dependencies. {% hint style="info" %} Some users might not have access to this API. If a user doesn't have access to Raycast Pro, they will be asked if they want to get access when your extension calls the AI API. If the user doesn't wish to get access, the API call will throw an error. You can check if a user has access to the API using [`environment.canAccess(AI)`](https://developers.raycast.com/api-reference/environment). {% endhint %} ## API Reference ### AI.ask Ask AI anything you want. Use this in “no-view” Commands, effects, or callbacks. In a React component, you might want to use the [useAI util hook](https://developers.raycast.com/utilities/react-hooks/useai) instead. #### Signature ```typescript async function ask(prompt: string, options?: AskOptions): Promise & EventEmitter; ``` #### Example {% tabs %} {% tab title="Basic Usage" %} ```typescript import { AI, Clipboard } from "@raycast/api"; export default async function command() { const answer = await AI.ask("Suggest 5 jazz songs"); await Clipboard.copy(answer); } ``` {% endtab %} {% tab title="Error handling" %} ```typescript import { AI, showToast, Toast } from "@raycast/api"; export default async function command() { try { await AI.ask("Suggest 5 jazz songs"); } catch (error) { // Handle error here, eg: by showing a Toast await showToast({ style: Toast.Style.Failure, title: "Failed to generate answer", }); } } ``` {% endtab %} {% tab title="Stream answer" %} ```typescript import { AI, getSelectedFinderItems, showHUD } from "@raycast/api"; import fs from "fs"; export default async function main() { let allData = ""; const [file] = await getSelectedFinderItems(); const answer = AI.ask("Suggest 5 jazz songs"); // Listen to "data" event to stream the answer answer.on("data", async (data) => { allData += data; await fs.promises.writeFile(`${file.path}`, allData.trim(), "utf-8"); }); await answer; await showHUD("Done!"); } ``` {% endtab %} {% tab title="User Feedback" %} ```typescript import { AI, getSelectedFinderItems, showHUD } from "@raycast/api"; import fs from "fs"; export default async function main() { let allData = ""; const [file] = await getSelectedFinderItems(); // If you're doing something that happens in the background // Consider showing a HUD or a Toast as the first step // To give users feedback about what's happening await showHUD("Generating answer..."); const answer = await AI.ask("Suggest 5 jazz songs"); await fs.promises.writeFile(`${file.path}`, allData.trim(), "utf-8"); // Then, when everythig is done, notify the user again await showHUD("Done!"); } ``` {% endtab %} {% tab title="Check for access" %} ```typescript import { AI, getSelectedFinderItems, showHUD, environment } from "@raycast/api"; import fs from "fs"; export default async function main() { if (environment.canAccess(AI)) { const answer = await AI.ask("Suggest 5 jazz songs"); await Clipboard.copy(answer); } else { await showHUD("You don't have access :("); } } ``` {% endtab %} {% endtabs %} #### Parameters | Name | Description | Type | | ---------------------------------------- | ------------------------------------------------------------ | --------------------------------- | | prompt\* | The prompt to ask the AI. | `string` | | options | Options to control which and how the AI model should behave. | [`AI.AskOptions`](#ai.askoptions) | #### Return A Promise that resolves with a prompt completion. ## Types ### AI.Creativity Concrete tasks, such as fixing grammar, require less creativity while open-ended questions, such as generating ideas, require more. ```typescript type Creativity = "none" | "low" | "medium" | "high" | "maximum" | number; ``` If a number is passed, it needs to be in the range 0-2. For larger values, 2 will be used. For lower values, 0 will be used. ### AI.Model The AI model to use to answer to the prompt. Defaults to `AI.Model["OpenAI_GPT-4o_mini"]`. | Model | Description | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | OpenAI\_GPT-5\_mini | OpenAI's latest model, great for well-defined tasks and precise prompts. | | OpenAI\_GPT-5\_nano | OpenAI's latest model, great for summarization and classification tasks. | | OpenAI\_GPT-4.1 | OpenAI's flagship model optimized for complex problem solving. | | OpenAI\_GPT-4.1\_mini | Balanced GPT-4.1 variant optimized for speed and cost efficiency. | | OpenAI\_GPT-4.1\_nano | Fastest and most cost-effective GPT-4.1 variant. | | OpenAI\_GPT-4 | Previous generation GPT-4 model with broad knowledge and complex instruction handling. | | OpenAI\_GPT-4\_Turbo | Previous generation GPT-4 with expanded context window. | | OpenAI\_GPT-4o | Advanced OpenAI model optimized for speed and complex problem solving. | | OpenAI\_GPT-4o\_mini | Fast and intelligent model for everyday tasks. | | OpenAI\_GPT-5 | OpenAI's latest model, great for coding and agentic tasks across domains. | | OpenAI\_GPT-5\_Codex | OpenAI's model optimized for agentic coding tasks in Codex and similar environments. | | OpenAI\_GPT-5.1 | OpenAI's model with adaptive reasoning, great for coding and agentic tasks across domains. | | OpenAI\_GPT-5.1\_Codex | A version of GPT-5.1 optimized for agentic coding tasks in Codex or similar environments. | | OpenAI\_GPT-5.1\_Instant | OpenAI's fastest GPT-5.1 model with adaptive reasoning, optimized for speed and efficiency. | | OpenAI\_GPT-5.2 | OpenAI's most capable model for professional work and long-running agents with state-of-the-art tool-calling. | | OpenAI\_GPT-5.2\_Instant | OpenAI's fast, capable model for everyday work with improved info-seeking, how-tos, and technical writing. | | OpenAI\_o3 | Advanced model excelling in math, science, coding, and visual tasks. | | OpenAI\_o4-mini | Fast, efficient model optimized for coding and visual tasks. | | OpenAI\_o1 | Advanced reasoning model for complex STEM problems. | | OpenAI\_o3-mini | Fast reasoning model optimized for STEM tasks. | | Groq\_GPT-OSS\_20b | OpenAI's first open-source model, 20b variant. | | Groq\_GPT-OSS\_120b | OpenAI's first open-source model, 120b variant. | | Anthropic\_Claude\_4.5\_Haiku | Anthropic's offering focusing on being the best combination of performance and speed. | | Anthropic\_Claude\_4\_Sonnet | Anthropic's most intelligent model. | | Anthropic\_Claude\_4.5\_Sonnet | Anthropic's most intelligent model with the highest intelligence across most tasks. | | Anthropic\_Claude\_4\_Opus | Anthropic's model for complex tasks with exceptional fluency. | | Anthropic\_Claude\_4.1\_Opus | Anthropic's model for complex tasks with exceptional fluency. | | Anthropic\_Claude\_4.5\_Opus | Anthropic's model for complex tasks with exceptional fluency. | | Perplexity\_Sonar | Fast Perplexity model with integrated search capabilities. | | Perplexity\_Sonar\_Pro | Advanced Perplexity model for complex queries with search integration. | | Groq\_Llama\_4\_Scout | Advanced 17B parameter multimodal model with 16 experts. | | Groq\_Llama\_3.3\_70B | Meta's state-of-the-art model for reasoning and general knowledge. | | Groq\_Llama\_3.1\_8B | Fast, instruction-optimized open-source model. | | Together\_AI\_Llama\_3.1\_405B | Meta's flagship model with advanced capabilities across multiple domains. | | Mistral\_Nemo | Small, Apache-licensed model built with NVIDIA. | | Mistral\_Large | Top-tier reasoning model with strong multilingual support. | | Mistral\_Medium | A powerful, cost-effective, frontier-class multimodal model. | | Mistral\_Small\_3 | Latest enterprise-grade small model with improved reasoning. | | Mistral\_Codestral | Specialized model for code-related tasks and testing. | | Groq\_Kimi\_K2\_Instruct | Kimi K2 is a powerful and versatile AI model designed for a wide range of tasks. | | Groq\_Qwen3-32B | The latest generation of large language models in the Qwen series. | | Google\_Gemini\_3\_Flash | Fast thinking model with strong balance of speed, performance, and value. | | Google\_Gemini\_3\_Pro | Advanced thinking model for complex problem solving. | | Google\_Gemini\_2.5\_Pro | Advanced thinking model for complex problem solving. | | Google\_Gemini\_2.5\_Flash | Fast, well-rounded thinking model. | | Google\_Gemini\_2.5\_Flash\_Lite | Fast model optimized for large-scale text output. | | Together\_AI\_Qwen3-235B-A22B-Instruct-2507-tput | A varied model with enhanced reasoning. | | Together\_AI\_DeepSeek-R1 | Open-source model matching OpenAI-o1 performance. | | Together\_AI\_DeepSeek-V3 | Advanced Mixture-of-Experts model. | | xAI\_Grok-4.1\_Fast | xAI's best agentic tool calling model that shines in real-world use cases like customer support and deep research. | | xAI\_Grok-4 | Advanced language model with enhanced reasoning and tool capabilities. | | xAI\_Grok-4\_Fast | xAI's latest advancement in cost-efficient reasoning models. | | xAI\_Grok\_Code\_Fast\_1 | Grok Code Fast 1 is xAI's Coding Agent focused model | | xAI\_Grok-3\_Beta | Enterprise-focused model for data, coding, and summarization tasks. | | xAI\_Grok-3\_Mini\_Beta | Fast, lightweight model for logic-based tasks. | If a model isn't available to the user (or has been disabled by the user), Raycast will fallback to a similar one. ### AI.AskOptions #### Properties | Property | Description | Type | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | creativity | Concrete tasks, such as fixing grammar, require less creativity while open-ended questions, such as generating ideas, require more. If a number is passed, it needs to be in the range 0-2. For larger values, 2 will be used. For lower values, 0 will be used. | [`AI.Creativity`](#ai.creativity) | | model | The AI model to use to answer to the prompt. | [`AI.Model`](#ai.model) | | signal | Abort signal to cancel the request. | [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) | --- # Source: https://developers.raycast.com/api-reference/feedback/alert.md # Alert When the user takes an important action (for example when irreversibly deleting something), you can ask for confirmation by using `confirmAlert`. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-18a8eeb2446fdc8e95157b412aa2c84fd22ffbfa%2Falert.webp?alt=media) ## API Reference ### confirmAlert Creates and shows a confirmation Alert with the given [options](#alert.options). #### Signature ```typescript async function confirmAlert(options: Alert.Options): Promise; ``` #### Example ```typescript import { confirmAlert } from "@raycast/api"; export default async function Command() { if (await confirmAlert({ title: "Are you sure?" })) { console.log("confirmed"); // do something } else { console.log("canceled"); } } ``` #### Parameters | Name | Description | Type | | ----------------------------------------- | ------------------------------------- | --------------------------------- | | options\* | The options used to create the Alert. | [`Alert.Options`](#alert.options) | #### Return A Promise that resolves to a boolean when the user triggers one of the actions.\ It will be `true` for the primary Action, `false` for the dismiss Action. ## Types ### Alert.Options The options to create an Alert. #### Example ```typescript import { Alert, confirmAlert } from "@raycast/api"; export default async function Command() { const options: Alert.Options = { title: "Finished cooking", message: "Delicious pasta for lunch", primaryAction: { title: "Do something", onAction: () => { // while you can register a handler for an action, it's more elegant // to use the `if (await confirmAlert(...)) { ... }` pattern console.log("The alert action has been triggered"); }, }, }; await confirmAlert(options); } ``` #### Properties | Property | Description | Type | | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | title\* | The title of an alert. Displayed below the icon. | `string` | | dismissAction | The Action to dismiss the alert. There usually shouldn't be any side effects when the user takes this action. | [`Alert.ActionOptions`](#alert.actionoptions) | | icon | The icon of an alert to illustrate the action. Displayed on the top. | [`Image.ImageLike`](https://developers.raycast.com/user-interface/icons-and-images#image.imagelike) | | message | An additional message for an Alert. Useful to show more information, e.g. a confirmation message for a destructive action. | `string` | | primaryAction | The primary Action the user can take. | [`Alert.ActionOptions`](#alert.actionoptions) | | rememberUserChoice | If set to true, the Alert will also display a `Do not show this message again` checkbox. When checked, the answer is persisted and directly returned to the extension the next time the alert should be shown, without the user actually seeing the alert. | `boolean` | ### Alert.ActionOptions The options to create an Alert Action. #### Properties | Property | Description | Type | | --------------------------------------- | ----------------------------------------------- | ----------------------------------------- | | title\* | The title of the action. | `string` | | onAction | A callback called when the action is triggered. | `() => void` | | style | The style of the action. | [`Alert.ActionStyle`](#alert.actionstyle) | ### Alert.ActionStyle Defines the visual style of an Action of the Alert. Use [Alert.ActionStyle.Default](#alert.actionstyle) for confirmations of a positive action.\ Use [Alert.ActionStyle.Destructive](#alert.actionstyle) for confirmations of a destructive action (eg. deleting a file). #### Enumeration members | Name | Value | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Default | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-cf694515bf72da488eea228c3511ea5667cacfe2%2Falert-action-default.webp?alt=media) | | Destructive | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-3529cca2309b77669ede9d8cc0bdff210a9b6f00%2Falert-action-destructive.webp?alt=media) | | Cancel | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-e73a37408b953e1a0d6f9751d5e8f001c0f2556f%2Falert-action-cancel.webp?alt=media) | --- # Source: https://developers.raycast.com/information/lifecycle/arguments.md # Arguments Raycast supports arguments for your commands so that users can enter values right from Root Search before opening the command. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-5820d335065ed715429f3d7c0ab5edff00c9182a%2Farguments.webp?alt=media) Arguments are configured in the [manifest](https://developers.raycast.com/manifest#argument-properties) per command. {% hint style="info" %} * **Maximum number of arguments:** 3 (if you have a use case that requires more, please let us know via feedback or in the [Slack community](https://www.raycast.com/community)) * The order of the arguments specified in the manifest is important and is reflected by the fields shown in Root Search. To provide a better UX, put the required arguments before the optional ones. {% endhint %} ## Example Let's say we want a command with three arguments. Its `package.json` will look like this: ```json { "name": "arguments", "title": "API Arguments", "description": "Example of Arguments usage in the API", "icon": "command-icon.png", "author": "raycast", "license": "MIT", "commands": [ { "name": "my-command", "title": "Arguments", "subtitle": "API Examples", "description": "Demonstrates usage of arguments", "mode": "view", "arguments": [ { "name": "title", "placeholder": "Title", "type": "text", "required": true }, { "name": "subtitle", "placeholder": "Secret Subtitle", "type": "password" }, { "name": "favoriteColor", "type": "dropdown", "placeholder": "Favorite Color", "required": true, "data": [ { "title": "Red", "value": "red" }, { "title": "Green", "value": "green" }, { "title": "Blue", "value": "blue" } ] } ] } ], "dependencies": { "@raycast/api": "1.38.0" }, "scripts": { "dev": "ray develop", "build": "ray build -e dist", "lint": "ray lint" } } ``` The command itself will receive the arguments' values via the `arguments` prop: ```typescript import { Form, LaunchProps } from "@raycast/api"; export default function Todoist(props: LaunchProps<{ arguments: Arguments.MyCommand }>) { const { title, subtitle } = props.arguments; console.log(`title: ${title}, subtitle: ${subtitle}`); return (
); } ``` ## Types ### Arguments A command receives the values of its arguments via a top-level prop named `arguments`. It is an object with the arguments' `name` as keys and their values as the property's values. Depending on the `type` of the argument, the type of its value will be different. | Argument type | Value type | | ------------- | ---------- | | `text` | `string` | | `password` | `string` | | `dropdown` | `string` | {% hint style="info" %} Raycast provides a global TypeScript namespace called `Arguments` which contains the types of the arguments of all the commands of the extension. For example, if a command named `show-todos` accepts arguments, its `LaunchProps` can be described as `LaunchProps<{ arguments: Arguments.ShowTodos }>`. This will make sure that the types used in the command stay in sync with the manifest. {% endhint %} --- # Source: https://developers.raycast.com/information/lifecycle/background-refresh.md # Background Refresh Commands of an extension can be configured to be automatically run in the background, without the user manually opening them. Background refresh can be useful for: * dynamically updating the subtitle of a command in Raycast root search * refreshing menu bar commands * other supporting functionality for your main commands This guide helps you understand when and how to use background refresh and learn about the constraints. ## Scheduling Commands Raycast supports scheduling commands with mode `no-view` and `menu-bar` at a configured interval. ### Manifest Add a new property `interval` to a command in the [manifest](https://developers.raycast.com/manifest#command-properties) Example: ```json { "name": "unread-notifications", "title": "Show Unread Notifications", "description": "Shows the number of unread notifications in root search", "mode": "no-view", "interval": "10m" }, ``` The interval specifies that the command should be launched in the background every X seconds (s), minutes (m), hours (h) or days (d). Examples: `10m`, `12h`, `1d`. The minimum value is 10 seconds (`10s`), which should be used cautiously, also see the section on best practices. Note that the actual scheduling is not exact and might vary within a tolerance level. macOS determines the best time for running the command in order to optimize energy consumption, and scheduling times can also vary when running on battery. To prevent overlapping background launches of the same command, commands are terminated after a timeout that is dynamically adjusted to the interval. ## Running in the background The entry point of your command stays the same when launched from the background. For no-view commands, a command will run until the Promise of the main async function resolves. Menu bar commands render a React component and run until the `isLoading` property is set to `false`. You can use the global `environment.launchType` in your command to determine whether the command has been launched by the user (`LaunchType.UserInitiated`) or via background refresh (`LaunchType.Background`). ```typescript import { environment, updateCommandMetadata } from "@raycast/api"; async function fetchUnreadNotificationCount() { return 10; } export default async function Command() { console.log("launchType", environment.launchType); const count = await fetchUnreadNotificationCount(); await updateCommandMetadata({ subtitle: `Unread Notifications: ${count}` }); } ``` Raycast auto-terminates the command if it exceeds its maximum execution time. If your command saves some state that is shared with other commands, make sure to use defensive programming, i.e. add handling for errors and data races if the stored state is incomplete or inaccessible. ## Development and Debugging For local commands under development, errors are shown as usual via the console. Two developer actions in root search help you to run and debug scheduled commands: * Run in Background: this immediately runs the command with `environment.launchType` set to `LaunchType.Background`. * Show Error: if the command could not be loaded or an uncaught runtime exception was thrown, the full error can be shown in the Raycast error overlay for development. This action is also shown to users of the installed Store command and provides actions to copy and report the error on the production error overlay. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-f56fdf8a10d8451837247c60012963ab7db7c947%2Fbackground-refresh-error.webp?alt=media) When the background run leads to an error, users will also see a warning icon on the root search command and a tooltip with a hint to show the error via the Action Panel. The tooltip over the subtitle of a command shows the last run time. You can launch the built-in root search command "Extension Diagnostics" to see which of your commands run in background and when they last ran. ## Preferences For scheduled commands, Raycast automatically adds command preferences that give users the options to enable and disable background refresh. Preferences also show the last run time of the command. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-475411d88fef4886f8a3f30521b5dedf3f8b4afc%2Fbackground-refresh-preferences.webp?alt=media) When a user installs the command via the Store, background refresh is initially *disabled* and is activated either when the user opens the command for the first time or enables background refresh in preferences. (This is to avoid automatically running commands in the background without the user being aware of it.) ## Best Practices * Make sure the command is useful both when manually launched by the user or when launched in the background * Choose the interval value as high as possible - low values mean the command will run more often and consume more energy * If your command performs network requests, check the rate limits of the service and handle errors appropriately (e.g. automatically retry later) * Make sure the command finishes as quickly as possible; for menu bar commands, ensure `isLoading` is set to false as early as possible * Use defensive programming if state is shared between commands of an extension and handle potential data races and inaccessible data --- # Source: https://developers.raycast.com/information/best-practices.md # Best Practices ## General ### Handle errors Network requests can fail, permissions to files can be missing… More generally, errors happen. By default, we handle every unhandled exception or unresolved Promise and show error screens. However, you should handle the "expected" error cases for your command. You should aim not to disrupt the user's flow just because something went wrong. For example, if a network request fails but you can read the cache, show the cache. A user might not need the fresh data straight away. In most cases, it's best to show a `Toast` with information about the error. Here is an example of how to show a toast for an error: ```typescript import { Detail, showToast, Toast } from "@raycast/api"; import { useEffect, useState } from "react"; export default function Command() { const [error, setError] = useState(); useEffect(() => { setTimeout(() => { setError(new Error("Booom 💥")); }, 1000); }, []); useEffect(() => { if (error) { showToast({ style: Toast.Style.Failure, title: "Something went wrong", message: error.message, }); } }, [error]); return ; } ``` ### Handle runtime dependencies Ideally, your extension doesn't depend on any runtime dependencies. In reality, sometimes locally installed apps or CLIs are required to perform functionality. Here are a few tips to guarantee a good user experience: * If a command requires a runtime dependency to run (e.g. an app that needs to be installed by the user), show a helpful message. * If your extension is tightly coupled to an app, f.e. searching tabs in Safari or using AppleScript to control Spotify, checks don't always have to be strict because users most likely don't install the extension without having the dependency installed locally. * If only some functionality of your extension requires the runtime dependency, consider making this functionality only available if the dependency is installed. Typically, this is the best case for [actions](https://developers.raycast.com/terminology#action), e.g. to open a URL in the desktop app instead of the browser. ### Show loading indicator When commands need to load big data sets, it's best to inform the user about this. To keep your command snappy, it's important to render a React component as quickly as possible. You can start with an empty list or a static form and then load the data to fill the view. To make the user aware of the loading process, you can use the `isLoading` prop on all top-level components, e.g. [``](https://developers.raycast.com/api-reference/user-interface/detail), [`
`](https://developers.raycast.com/api-reference/user-interface/form), [``](https://developers.raycast.com/api-reference/user-interface/grid), or [``](https://developers.raycast.com/api-reference/user-interface/list). Here is an example to show the loading indicator in a list: ```typescript import { List } from "@raycast/api"; import { useEffect, useState } from "react"; export default function Command() { const [items, setItems] = useState(); useEffect(() => { setTimeout(() => { setItems(["Item 1", "Item 2", "Item 3"]); }, 1000); }, []); return ( {items?.map((item, index) => ( ))} ); } ``` *** ## Forms ### Use Forms Validation Before submitting data, it is important to ensure all required form controls are filled out, in the correct format. In Raycast, validation can be fully controlled from the API. To keep the same behavior as we have natively, the proper way of usage is to validate a `value` in the `onBlur` callback, update the `error` of the item and keep track of updates with the `onChange` callback to drop the `error` value. The [useForm](https://developers.raycast.com/utilities/react-hooks/useform) utils hook nicely wraps this behavior and is the recommended way to do deal with validations. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-06326f9b2b6de5d39b9c8aa37e677b23c122f26e%2Fform-validation.webp?alt=media) {% hint style="info" %} Keep in mind that if the Form has any errors, the [`Action.SubmitForm`](https://developers.raycast.com/api-reference/user-interface/actions#action.submitform) `onSubmit` callback won't be triggered. {% endhint %} {% tabs %} {% tab title="FormValidationWithUtils.tsx" %} ```tsx import { Action, ActionPanel, Form, showToast, Toast } from "@raycast/api"; import { useForm, FormValidation } from "@raycast/utils"; interface SignUpFormValues { name: string; password: string; } export default function Command() { const { handleSubmit, itemProps } = useForm({ onSubmit(values) { showToast({ style: Toast.Style.Success, title: "Yay!", message: `${values.name} account created`, }); }, validation: { name: FormValidation.Required, password: (value) => { if (value && value.length < 8) { return "Password must be at least 8 symbols"; } else if (!value) { return "The item is required"; } }, }, }); return ( } > ); } ``` {% endtab %} {% tab title="FormValidationWithoutUtils.tsx" %} ```typescript import { Form } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [nameError, setNameError] = useState(); const [passwordError, setPasswordError] = useState(); function dropNameErrorIfNeeded() { if (nameError && nameError.length > 0) { setNameError(undefined); } } function dropPasswordErrorIfNeeded() { if (passwordError && passwordError.length > 0) { setPasswordError(undefined); } } return (
{ if (event.target.value?.length == 0) { setNameError("The field should't be empty!"); } else { dropNameErrorIfNeeded(); } }} /> { const value = event.target.value; if (value && value.length > 0) { if (!validatePassword(value)) { setPasswordError("Password should be at least 8 characters!"); } else { dropPasswordErrorIfNeeded(); } } else { setPasswordError("The field should't be empty!"); } }} /> ); } function validatePassword(value: string): boolean { return value.length >= 8; } ``` {% endtab %} {% endtabs %} --- # Source: https://developers.raycast.com/api-reference/browser-extension.md # Browser Extension The Browser Extension API provides developers with deeper integration into the user's Browser *via* a [Browser Extension](https://raycast.com/browser-extension). {% hint style="info" %} Some users might not have installed the Browser Extension. If a user doesn't have the Browser Extension installed, they will be asked if they want to install it when your extension calls the Browser Extension API. If the user doesn't wish to install it, the API call will throw an error. You can check if a user has the Browser Extension installed using [`environment.canAccess(BrowserExtension)`](https://developers.raycast.com/api-reference/environment). The API is not accessible on Windows for now. {% endhint %} ## API Reference ### BrowserExtension.getContent Get the content of an opened browser tab. #### Signature ```typescript async function getContent(options?: { cssSelector?: string; tabId?: number; format?: "html" | "text" | "markdown"; }): Promise; ``` #### Example {% tabs %} {% tab title="Basic Usage" %} ```typescript import { BrowserExtension, Clipboard } from "@raycast/api"; export default async function command() { const markdown = await BrowserExtension.getContent({ format: "markdown" }); await Clipboard.copy(markdown); } ``` {% endtab %} {% tab title="CSS Selector" %} ```typescript import { BrowserExtension, Clipboard } from "@raycast/api"; export default async function command() { const title = await BrowserExtension.getContent({ format: "text", cssSelector: "title" }); await Clipboard.copy(title); } ``` {% endtab %} {% endtabs %} #### Parameters | Name | Description | Type | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | options | Options to control which content to get. | `Object` | | options.cssSelector | Only returns the content of the element that matches the [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors). If the selector matches multiple elements, only the first one is returned. If the selector doesn't match any element, an empty string is returned. When using a CSS selector, the `format` option can not be `markdown`. | `string` | | options.format | The format of the content. - `html`: `document.documentElement.outerHTML` - `text`: `document.body.innerText` - `markdown`: A heuristic to get the "content" of the document and convert it to markdown. Think of it as the "reader mode" of a browser. | `"html"` or `"text"` or `"markdown"` | | options.tabId | The ID of the tab to get the content from. If not specified, the content of the active tab of the focused window is returned. | `number` | #### Return A Promise that resolves with the content of the tab. ### BrowserExtension.getTabs Get the list of open browser tabs. #### Signature ```typescript async function getTabs(): Promise; ``` #### Example ```typescript import { BrowserExtension } from "@raycast/api"; export default async function command() { const tabs = await BrowserExtension.getTabs(); console.log(tabs); } ``` #### Return A Promise that resolves with the list of [tabs](#browserextension.tab). ## Types ### BrowserExtension.Tab #### Properties | Property | Description | Type | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | | active\* | Whether the tab is active in its window. There can only be one active tab per window but if there are multiple browser windows, there can be multiple active tabs. | `boolean` | | id\* | The ID of the tab. Tab IDs are unique within a browser session. | `number` | | url\* | The URL the tab is displaying. | `string` | | favicon | The URL of the tab's [favicon](https://developer.mozilla.org/en-US/docs/Glossary/Favicon). It may also be `undefined` if the tab is loading. | `string` | | title | The title of the tab. It may also be `undefined` if the tab is loading. | `string` | --- # Source: https://developers.raycast.com/api-reference/cache.md # Cache Caching abstraction that stores data on disk and supports LRU (least recently used) access. Since extensions can only consume up to a max. heap memory size, the cache only maintains a lightweight index in memory and stores the actual data in separate files on disk in the extension's support directory. ## API Reference ### Cache The `Cache` class provides CRUD-style methods (get, set, remove) to update and retrieve data synchronously based on a key. The data must be a string and it is up to the client to decide which serialization format to use.\ A typical use case would be to use `JSON.stringify` and `JSON.parse`. By default, the cache is shared between the commands of an extension. Use [Cache.Options](#cache.options) to configure a `namespace` per command if needed (for example, set it to [`environment.commandName`](https://developers.raycast.com/api-reference/environment)). #### Signature ```typescript constructor(options: Cache.Options): Cache ``` #### Example ```typescript import { List, Cache } from "@raycast/api"; type Item = { id: string; title: string }; const cache = new Cache(); cache.set("items", JSON.stringify([{ id: "1", title: "Item 1" }])); export default function Command() { const cached = cache.get("items"); const items: Item[] = cached ? JSON.parse(cached) : []; return ( {items.map((item) => ( ))} ); } ``` #### Properties | Property | Description | Type | | ----------------------------------------- | -------------------------------------------------------- | --------- | | isEmpty\* | Returns `true` if the cache is empty, `false` otherwise. | `boolean` | #### Methods | Method | | --------------------------------------------------------------------------------- | | [`get(key: string): string \| undefined`](#cache-get) | | [`has(key: string): boolean`](#cache-has) | | [`set(key: string, data: string): void`](#cache-set) | | [`remove(key: string): boolean`](#cache-remove) | | [`clear(options = { notifySubscribers: true }): void`](#cache-clear) | | [`subscribe(subscriber: Cache.Subscriber): Cache.Subscription`](#cache-subscribe) | ### Cache#get Returns the data for the given key. If there is no data for the key, `undefined` is returned.\ If you want to just check for the existence of a key, use [has](#cache-has). #### Signature ```typescript get(key: string): string | undefined ``` #### Parameters | Name | Description | Type | | ------------------------------------- | --------------------------- | -------- | | key\* | The key of the Cache entry. | `string` | ### Cache#has Returns `true` if data for the key exists, `false` otherwise.\ You can use this method to check for entries without affecting the LRU access. #### Signature ```typescript has(key: string): boolean ``` #### Parameters | Name | Description | Type | | ------------------------------------- | --------------------------- | -------- | | key\* | The key of the Cache entry. | `string` | ### Cache#set Sets the data for the given key.\ If the data exceeds the configured `capacity`, the least recently used entries are removed.\ This also notifies registered subscribers (see [subscribe](#cache-subscribe)). #### Signature ```typescript set(key: string, data: string) ``` #### Parameters | Name | Description | Type | | -------------------------------------- | ---------------------------------------- | -------- | | key\* | The key of the Cache entry. | `string` | | data\* | The stringified data of the Cache entry. | `string` | ### Cache#remove Removes the data for the given key.\ This also notifies registered subscribers (see [subscribe](#cache-subscribe)).\ Returns `true` if data for the key was removed, `false` otherwise. #### Signature ```typescript remove(key: string): boolean ``` ### Cache#clear Clears all stored data.\ This also notifies registered subscribers (see [subscribe](#cache-subscribe)) unless the `notifySubscribers` option is set to `false`. #### Signature ```typescript clear((options = { notifySubscribers: true })); ``` #### Parameters | Name | Description | Type | | ------- | -------------------------------------------------------------------------------------------------------------------------- | -------- | | options | Options with a `notifySubscribers` property. The default is `true`; set to `false` to disable notification of subscribers. | `object` | ### Cache#subscribe Registers a new subscriber that gets notified when cache data is set or removed.\ Returns a function that can be called to remove the subscriber. #### Signature ```typescript subscribe(subscriber: Cache.Subscriber): Cache.Subscription ``` #### Parameters | Name | Description | Type | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | subscriber | A function that is called when the Cache is updated. The function receives two values: the `key` of the Cache entry that was updated or `undefined` when the Cache is cleared, and the associated `data`. | [`Cache.Subscriber`](#cache.subscriber) | ## Types ### Cache.Options The options for creating a new Cache. #### Properties | Property | Description | Type | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | capacity | The capacity in bytes. If the stored data exceeds the capacity, the least recently used data is removed. The default capacity is 10 MB. | `number` | | namespace | If set, the Cache will be namespaced via a subdirectory. This can be useful to separate the caches for individual commands of an extension. By default, the cache is shared between the commands of an extension. | `string` | ### Cache.Subscriber Function type used as parameter for [subscribe](#cache-subscribe). ```typescript type Subscriber = (key: string | undefined, data: string | undefined) => void; ``` ### Cache.Subscription Function type returned from [subscribe](#cache-subscribe). ```typescript type Subscription = () => void; ``` --- # Source: https://developers.raycast.com/misc/changelog.md # Changelog ## 1.103.0 - 2025-09-15 Over the past few releases, we've made some additions to the API to better support it: * there's a new `platforms` field in the manifest. We’ve intentionally chosen to release only extensions that we are tested on Windows. This is the field that allow extensions to be available on Windows. By default, if not specified, the field's value is `["macOS"]`. If you want to make an extension available on Windows, you can set it to `["macOS", "Windows"]` * Keyboard Shortcuts are tricky. If you use the `Common` shortcuts, then they will be cross-platform. However, if you use shortcuts and specify a modifier like `cmd`, the shortcut will be ignored on Windows (and vice-versa, if you specify a modifier like `windows`, it won't be available on macOS). So we've added a new syntax where you can nest shortcuts per platform: ```js { macOS: { modifiers: ["cmd", "shift"], key: "c" }, Windows: { modifiers: ["ctrl", "shift"], key: "c" }, } ``` * Sometimes, you might want to change preferences depending on the platform (for example if it's a path). You can now specify the default as a object as well: ```json "default": { "macOS": "foo", "Windows": "bar" } ``` * We've also updated the `@raycast/utils` to make it cross platform and added a `runPowerShellScript` function. ## 1.98.0 - 2025-05-08 ### ✨ New * New `Action.InstallMCPServer` to push a MCP installation form ### 💎 Improvements * **Shortcuts**: It’s now possible to provide platform-specific shortcuts. ### 🐞 Fixes * Fixed an issue that caused extensions containing Swift code to not compile. ## 1.94.0 - 2025-03-19 ### ✨ New * The extensions now run on Nodejs 22 and react 19. Among other benefits, this makes `fetch` globally available. There shouldn’t be any breaking change - but if you find some, please let us know! Additionally, new extensions will be bootstrapped with ESLint 9 * **Tools**: Tools can now specify some preferences, the same way Commands can ### 💎 Improvements * **CLI**: When a tool or a command (when running in the background) times out, an error message will be printed in the terminal * **CLI**: When publishing an extension, the PR to the extensions repository will be created as draft so you can fill the description up before submitting it ## 1.93.0 - 2025-02-26 ### ✨ New * **Tools**: We are introducing a new type of entry points for extensions: Tools. They turn a regular extension into an AI Extension. As opposed to a command, they don’t show up in the root search and the user can’t directly interact with them. Instead, they are functionalities that the AI can use to interact with an extension. ## 1.91.0 - 2025-02-05 ### ✨ New * **AI**: The models added in [Raycast 1.90.0](https://www.raycast.com/changelog/1-90-0) are now also part of the API * DeepSeek R1² reasoning model (powered by Together AI) and its distilled version¹ (powered by Groq) * OpenAI o1-mini² and o1-preview² reasoning models * OpenAI o3-mini¹ * Google Gemini 1.5 Flash¹ and Gemini 1.5 Pro², Gemini 2.0 Flash¹ and Gemini 2.0 Flash Thinking¹ models * xAI Grok-2² model * Perplexity Sonar¹, Sonar Pro² and Sonar Reasoning¹ models ¹ available with Raycast Pro ² available with Raycast Pro + Advanced AI ### 🐞 Fixes * **Window Management**: Added missing types for `getActiveWindow`. ## 1.89.0 - 2025-01-15 ### 💎 Improvements * **Cache**: Clearing the cache will now delete all the files in the cache folder instead of the entire folder. ## 1.88.0 - 2024-12-16 ### 🐞 Fixes * **Markdown**: Fixed a crash when trying to print invalid surrogate code points * **Types**: Fixed an issue when generating the TypeScript definition for the preferences when one of their descriptions contained `*/` ## 1.87.0 - 2024-12-04 ### ✨ New * **Docs**: You can now find a few txt files containing all the docs that you can feed to LLMs: * → All the docs * → The API docs * → The utils docs ### 🐞 Fixes * **CLI**: Fix a couple of issues when trying to publish an extension or pull contributions ## 1.86.0 - 2024-11-20 ### 💎 Improvements * **CLI**: The CLI that comes with `@raycast/api` does not use a platform/architecture-specific binary anymore. This should fix some issues that people encountered when trying to install the API. ## 1.84.0 - 2024-10-09 ### 💎 Improvements * When running a no-view command with arguments, only clear the argument inputs instead of clearing the entire search bar (which brings the behaviour in line with other no-view commands) ### 🐞 Fixes * Fixed a regression where `selectedItemId` wouldn’t be respected * Fixed a typo in the extension template’s build script ## 1.81.0 - 2024-08-13 ### ✨ New * **Detail:** You can now render LaTeX in the Detail views. We support the following delimiters: * Inline math: `\(...\)` and `\begin{math}...\end{math}` * Display math: `\[...\]`, `$$...$$` and `\begin{equation}...\end{equation}` ### 💎 Improvements * You can now pick a different command template for each command that you add in the `Create Extension` command’s form. * Added a new `Add Command` action for local extensions in the `Manage Extensions` command. ## 1.80.0 - 2024-07-31 ### ✨ New * **AI:** OpenAI GPT-4o Mini can now be used in the API. * **Quicklinks:** `CreateQuickLink` now accepts an `icon` prop that allows you to customize the icon of your Quicklink. ### 💎 Improvements * **Menu Bar Commands** now show a confirmation toast when activated or refreshed. ## 1.79.0 - 2024-07-17 ### ✨ New * **Navigation**: Added a second argument to `useNavigation().push` to specify a callback called when the pushed view will be popped. You can use it to update the current view when it will become active again. There’s also a new `onPop` prop on `Action.Push` to do the same thing. ### 💎 Improvements * When creating or forking an extension, an alert will be shown if you specify an existing folder (and thus avoid overwriting files without warning) ## 1.78.0 - 2024-07-03 ### ✨ New * In addition to the new Custom Window Management commands, we are introducing a `WindowManagement` API to give you total control to move your windows depending on any kind of logic you can imagine. * You can now access the `ownerOrAuthorName` in the `environment`, useful for re-usable libraries. ### 🐞 Fixes * **Pagination**: Fixed the TypeScript definition of the `onLoadMore` callback. ## 1.77.0 - 2024-06-19 ### ✨ New * Updated React version to 18.3.1 to prepare for the next major version of React. This shouldn't impact any extensions but let us know if you find any unexpected behaviour. ### 🐞 Fixes * **Menu Bar Extra**: fixed an issue where `Submenu` icons changed appearance based on Raycast's appearance, instead of the system's. ## 1.76.0 - 2024-06-05 ### 💎 Improvements * Some companies requires all package.json’s names to be name-spaced (eg. `@foo/bar`). However, Raycast only understands names that *aren’t* name-spaced. This prevented some people from creating internal extensions. In order to workaround this issue, you can now use the `@workaround` namespace in extension names (eg. `@workaround/bar`). ### 🐞 Fixes * **Clipboard**: Fixed an issue where 2 items were added to the pasteboard when copying a file (one with the file name, and one with the file url). It now correctly adds 1 item with 2 representations. ## 1.74.0 - 2024-05-15 ### ✨ New * **AI:** The models available in the API now matches the ones available in the app (eg. GPt-4o, Llama-3, etc.). As part of this, the models are now part of an enum `AI.Model` which will make it easier to add and deprecate them as time goes on. * **Utils:** we’ve added a new React hook called `useLocalStorage`. This hook simplifies managing a value in `LocalStorage`. Take a look at the [developer docs](https://developers.raycast.com/utilities/react-hooks/uselocalstorage) to learn more. ### 💎 Improvements * **DX**: Improved the precision of warning messages when trying to add children to a react component that can’t accept them. ## 1.72.0 - 2024-04-24 ### ✨ New * **Browser Extension**: You can now access the context of the focused browser via the Raycast Browser Extension. You can get the list of open tabs as well as the content of a tab. ### 🐞 Fixes * **Grid**: Fixed a bug that caused the selected Grid item to be brought into focus when paginating. ## 1.71.0 - 2024-04-10 ### ✨ New * **Developer Hub:** you can now programmatically send error reports using the new `captureException` function. * **Utils**: we’ve added a new React hook, `useStreamJSON`. The new hook simplifies the process of streaming through large JSON data sources, which normally would not fit in the extension’s memory. Take a look at the [developer docs](https://developers.raycast.com/utilities/react-hooks/usestreamjson) to learn more. * **AI**: All the new models are also available in the API. ### 💎 Improvements * `getApplications`, `getDefaultApplication`, and `Action.OpenWith` now support remote URLs and will return the installed Applications that can open remote URLs (usually browsers) ### 🐞 Fixes * **Pagination**: Fixed a bug that could cause pagination to not work when `filtering` was set to true. * **CLI**: Fixed the cursor being kept hidden when interrupting a command ## 1.70.0 - 2024-03-20 ### 💎 Improvements * **Grid & List:** The placeholders shown while waiting for the next page to load are now animated * **Application info:** Application object now returns the localized name if the application is running ### 🐞 Fixes * **Forms:** Fixed an issue which made it impossible to select a value of a controlled Dropdown after changing its value programmatically * **Grid:** Fixed an issue where pagination would not work when scrolling to the bottom while `isLoading` is initially false * **List:** Fixed an issue where pagination would not work if there was an empty section at the end * Fixed a rare case where, when an extension throws an error, a different error saying “Could not communicate with command worker” would be thrown instead ## 1.69.0 - 2024-03-07 ### ✨ New * `List` and `Grid` now have native pagination support! 🎉 If you want to update your extension to support pagination, head over to the [docs](https://developers.raycast.com/api-reference/user-interface/list#pagination) for instructions on how to get your extension to use pagination. * Markdown: Added support for specifying a tint color in the url of a markdown image by adding a `raycast-tint-color` query string ### 💎 Improvements * Lint: The eslint plugin and `ray` CLI has been updated to have the same algorithm to check if a string is in Title Case (using the definition from Apple) * `getApplications` (and `Action.OpenWith`) will now show `Terminal` when using a path to a directory ### 🐞 Fixes * Fixed an issue where, when the user would change the selection in a List or Grid and rapidly trigger an action, the action of the previously selected item would execute instead ## 1.67.0 - 2024-02-07 ### 🐞 Fixes * Fix a crash that could happen when exporting a function that would return another function. * **Menu Bar Extra:** Fixed a bug that caused the text in text-only extras to be offset. ## 1.66.0 - 2024-01-24 ### 💎 Improvements * Improved some error messages in the `ray` CLI. ### 🐞 Fixes * **Form**: Fixed the display of full-day dates in the Date Picker. ## 1.65.0 - 2024-01-10 ### ✨ New * **Developer Tools**: we've introduced a new developer option, `Use file logging instead of OSLog`, to work around an OS issue that causes some users to not see any extension logs in the terminal during development. ### 💎 Improvements * **Form's Date Picker:** Future dates will be prioritised when parsing the date, f.e. if you type "8am" and itrs already "10am", then the parsed date will be "tomorrow 8am". ### 🐞 Fixes * Fixed an issue where the `ray` CLI could not communicate with the app. * Fixed an issue where an OAuth authorization session triggered by a menu bar command would not be able to complete if a `background` launch was triggered between authorization starting and completing. * Fixed an issue on multi-monitor setups, where sometimes MenuBarExtra icons would not appear dimmed on inactive displays. ## 1.64.0 - 2023-12-13 ### ✨ New * **Form**: Introduced a new component `Form.LinkAccessory` to render a link displayed in the right-hand side of the search bar. * **Arguments**: Introduced a new Argument type: `dropdown`. You can now [specify a list of options](https://developers.raycast.com/information/manifest#argument-properties) for the user choose from. * **Developer Hub**: User preferences are now included in error reports. Password and text preferences will be replaced with `[REDACTED]`, file/directory/appPicker preferences will be scrubbed of PII, and dropdown/checkbox preferences will be sent as-is. ### 💎 Improvements * **Window Capture**: Added a warning when trying to take a screenshot of Raycast if that screenshot won't match the requirement for the Store's extensions guidelines (eg. if Raycast is too close to an edge or if the screen doesn't have a high enough resolution). ### 🐞 Fixes * **Types generation**: Fixed the type of a required `appPicker` preference (even if it is `required`, the app might be undefined because it is missing). * **Empty View**: Fixed an issue where the Empty View might not be showing in a certain case. * **Menu Bar Extra**: \*\*\*\*icons tinted with `Color.PrimaryText` and `Color.SecondaryText` should now change based on the menu bar's appearance. * **List Metadata:** `Link`s should be properly aligned again. ## 1.63.0 - 2023-11-29 ### 💎 Improvements * Improved runtime error handling when using a Swift project ### 🐞 Fixes * **Lists**: Fixed a race condition where the selected item would not be the first one after a list items update * **MenuBarExtra:** `alternate` are no longer supported on pre-Sonoma versions of macOS, as they would often appear alongside their parent items. ## 1.62.0 - 2023-11-15 ### ✨ New * **Menu Bar:** `MenuBarExtra.Item`s have a new prop, `alternate`. If an `alternate` is defined, it will replace its parent `MenuBarExtra.Item` when the user presses the ⌥ (option) key. * The Node runtime has been updated to [Node 20](https://nodejs.org/en/blog/announcements/v20-release-announce/), the [current](https://github.com/nodejs/Release#release-schedule) Long-term Support (LTS) release. * **AI**: You can now use the `gpt-4` model with `AI.ask`. If a user does not have access to this model, it will gracefully fall back to an available model. You can check if a user has access using `environment.canAccess('gpt-4')`. ### 💎 Improvements * **Error Handling:** `Could not communicate with command worker` errors should not be reported anymore. ### 🐞 Fixes * **Toast:** Fixed an issue that caused toast actions to not work after a toast was updated. * **Error Handling:** Fixed an edge case that could cause an out-of-memory error while an uncaught exception was processed, obfuscating the original error. * **Performance**: Fixed an issue where some keyboard events would be dropped while an extension was loading. * **Markdown**: Fixed a regression where HTML comments would show up in the rendered Markdown. ## 1.61.0 - 2023-11-02 ### 💎 Improvements * **Date Picker**: When specifying a min and/or max date, the suggestion will now always be within those bounds ### 🐞 Fixes * Fixed a bug that previously could cause a `no-view` command to display an error icon in the root search, with no means of removing the error. ## 1.60.0 - 2023-10-18 ## Introducing the Extension Issues Dashboard ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-e3c7a6de170fa071b3e0caf1ac8f93d283255a91%2Fextension-issues.webp?alt=media) The new Extension Issues Dashboard is designed to help you quickly troubleshoot and resolve issues in any of your extensions by providing real-time visibility into errors encountered by users. You can access it at , or by using the new `View Issues` action. ### ✨ New * It is now possible to write extensions [using ESM](https://developers.raycast.com/faq) instead of CommonJS ### 💎 Improvements * Updated NodeJS runtime to 18.18.2 * When copying a deeplink with some arguments in the root search, copy the deeplink with those arguments ### 🐞 Fixes * Fixed an issue where animated toasts would hang around after the command was unloaded. ## 1.59.0 - 2023-09-21 ### ✨ New * **PickDate**: Similar to `Form.DatePicker`, you can also check whether the user picked a full day or a specific time with `Action.PickDate.isFullDay(date)`. ### 💎 Improvements * **Clipboard**: The `transient` option is renamed to `concealed`. ### 🐞 Fixes * **MenuBarExtra:** Right-clicking `MenuBarExtra.Item`s should now work in macOS Sonoma. ## 1.58.0 - 2023-09-06 ### ✨ New * **Alert**: Add a new option `rememberUserChoice` to show a checkbox to remember the user choice the next time the same Alert would be shown. * **DatePicker**: You can know check whether the user picked a full day or a specific time with `Form.DatePicker.isFullDay(date)`. ### 💎 Improvements * The "Fork Extension" action is now also available in the Store for installed extensions. * All the APIs that accepts a file path will now resolve `~` if necessary. ### 🐞 Fixes * Fix an issue where some Toasts would not disappear after the command was terminated. * Fix an issue where List Item's accessories with an icon could have their text cut off. * Fix `getFrontmostApplication` failing for some applications. * The "Fork Extension" will now be more robust dealing with unexpected `package.json` formats. * Fixed an issue where newly created Extensions would not use the correct username after it had been updated. * Fix an issue where it was possible to set a multiline `searchText` ## 1.57.0 - 2023-08-09 ### 🐞 Fixes * **Metadata**: Fixed various rendering issues with `TagList`. * **Menu Bar Extra**: Fixed a bug that caused section titles to be unreadable on macOS Sonoma. * **Menu Bar Extra**: Fixed a bug that could cause a menu bar command to be unloaded while its menu is open. * **Form**: Fixed stale suggestions in the DatePicker when changing its type. * **Icon**: Fixed the `AppWindowGrid2x2` icon only showing a square. ## 1.56.0 - 2023-07-26 ### ✨ New * **Clipboard**: `Clipboard.read()` now supports an `offset` option to access the Clipboard History (limited to the last 5) * **Grid:** Grid items can now have an icon accessory * **Shortcuts:** Providing a consistent user experience should now be easier thanks to the new `Keyboard.Shortcut.Common` export. ### 💎 Improvements * `getSelectedText` is now more reliable * **Trash**: Improved behaviour of `trash` and `Action.Trash` to better handle missing files. * **HUD**: `showHUD` now supports the same options as `closeMainWindow` * **Command Launching:** Improved logic for deciding which version of a command gets launched when a user has both a production and a development version of an extension installed. * **Tags:** Icon-only tags should now center the icon. ### 🐞 Fixes * **Form**: When working on a draft, updating a `Form.Checkbox` will update the draft. * **Error Reports:** Improved error messages when an extension crashes during a background launch. * **Shortcuts:** Previously, the API permitted the creation of shortcuts using keys reserved by Raycast (⌘+K, ⌘+W, ⌘+Esc, etc.), resulting in unexpected behavior. Raycast now ignores these and, during development mode, they will trigger a runtime warning. ## 1.55.0 - 2023-07-06 ### 💎 Improvements * **Fallback Commands**: Local commands will now have an indicator so that it's possible to differentiate them from the commands installed from the Store * The NodeJS process used for Raycast extensions will now be named `Raycast Helper (Extensions)` * Active menu bar commands will now be displayed in `Extension Diagnostics`. ### 🐞 Fixes * Fix an issue where Metadata's Tag items would sometimes not be updated * Fix a bug where renamed commands appear in the root search with both the original and the updated name after an extension update. ## 1.54.0 - 2023-06-21 ### 💎 Improvements * Add an action to clear the local storage when an unexpected error occurs * When using `showToast` while the Raycast window is closed (for example if a command is launched with a hotkey), a `HUD` will be shown instead * Improve the error messages when a command fails to load * The NodeJS inspector will now use a random free port instead of using the default 9229 port (which you can use for other NodeJS scripts) ### 🐞 Fixes * Fix a performance issue on the first render of Lists and Grids * Fix an issue where required arguments wouldn't be required when launching a command right after installing it * Fix a regression where the deprecated `render` method would not work anymore * Fix an edge case where some Form items would not be updated if some items would be added at the same time ## 1.53.0 - 2023-06-07 ### ✨ New * **Metadata**: `List.Item.Detail.Metadata.TagList.Item` and `Detail.Metadata.TagList.Item` now accepts an action handler via the `onAction` prop! * Added [LaunchContext](https://developers.raycast.com/api-reference/command#launchcontext) support to `Create Quicklink` and `Create Snippet:` * `launchCommand({ ownerOrAuthorName: "raycast", extensionName: "raycast", name: "create-quicklink", type: LaunchType.UserInitiated, context: { name: "context name", application: "Xcode", }});` * `launchCommand({ ownerOrAuthorName: "raycast", extensionName: "snippets", name: "create-snippet", type: LaunchType.UserInitiated, context: { name: "context name", text: "context text", keyword: "context keyword" }})` * **Date Pickers:** You can now add a minimum and maximum date to `Form.DatePicker` and `Action.PickDate` using the `min` and `max` props to limit the suggestions shown when entering a date. ### 💎 Improvements * Updated NodeJS to 18.16.0 * Improve the "Fork Extension" action to avoid modifying the manifest as much as possible. ### 🐞 Fixes * Fixed a bug that sometimes caused `no-view` commands to not display errors. * Fixed a bug that caused OAuth not to work if the `client.authorize(authorizationRequest)` was executed more than once. * Fixed a problem where commands with background execution would not display the OAuth sign-in screen. * **SVG**: Properly handle `currentColor` * **List/Grid**: Fixed `selectedItemId` being sometimes ignored on the first render. * **Form**: Fixed triggering `onChange` on the TextArea when using a markdown keyboard shortcut. ## 1.52.0 - 2023-05-24 ### ✨ New * **SVG**: You can now use the Raycast `Color` in an SVG. ### 💎 Improvements * Improve the error message when a required property is missing on a component ### 🐞 Fixes * Fixed an edge case where the keyboard events triggered while an extension is loading would not be passed down to the extension once loaded * Fixed an issue where the fallback of an image would show while it is being loaded ## 1.51.0 - 2023-05-10 ### ✨ New * **AI**: Introduced a new `AI` Pro API. Use `AI.ask` to seamlessly ask any prompt and enhance your extensions with artificial intelligence. * **Pro APIs:** You can now check whether a user can access a certain API using `environment.canAccess(AI)`. ### 💎 Improvements * **Custom Theme**: Deprecated `Color.Brown` as it is not part of the Raycast colors anymore. * **Custom Theme:** Renamed `environment.theme` to `environment.appearance`. * Improve the error message when an API is called with arguments of the wrong type. ### 🐞 Fixes * **Forms**: Fixed an issue where drafts would not save the value of a File Picker. * **Forms**: Fixed an issue where `onChange` would not be triggered in certain cases for a File Picker. * **Lists**: Fixed an issue that caused a List's section to re-render whenever an action panel's submenu was updated. * **Colors:** Fixed a crash that could sometimes occur when using `adjustContrast` on a dynamic color. ## 1.50.0 - 2023-04-27 ### ✨ New * Raycast now provides 2 global TypeScript namespaces called `**Preferences**` and `**Arguments**` which respectively contain the types of the preferences and the types of the arguments of all the commands of the extensions. For example, if a command named `show-todos` has some preferences, its `getPreferenceValues`'s return type can be specified with `getPreferenceValues()`. This will make sure that the types used in the command stay in sync with the manifest. * It is now possible to add commands that are disabled by default. A user will have to enable it manually before it shows up in Raycast's root search. This can be useful to provide commands for specific workflows without overwhelming everybody's root search. * **Markdown Tables** are now properly supported. * **Markdown** code blocks now support syntax highlighting. To enable it, make sure you specify the programming language at the start of the block. ### 💎 Improvements * **Colors**: To improve accessibility, dynamic adjustment for raw colors (`HEX`, `rgb` etc) used in extensions has been switched from opt-in to opt-out. If your extension relies on accurate color reproduction, check the [documentation](https://developers.raycast.com/api-reference/user-interface/colors) for instructions on how to opt-out. * **Images**: You can now suffix your local assets with `@dark` to automatically provide a dark theme option, eg: `icon.png` and `icon@dark.png`. ### 🐞 Fixes * **CLI**: Fix an issue where the CLI wouldn't want to bundle files named `foo.node.js`. ## 1.49.0 - 2023-03-29 ### ✨ New * It is now possible to drag and drop items from Grids. Lists are also supported if their items have as `quickLook` properties. ### 💎 Improvements * Extend `launchCommand` to allow inter-extension launches * Extend `launchCommand` to allow to pass a `fallbackText` ### 🐞 Fixes * **SVG**: Ignore doctype and HTML comments * Fix a flicker happening when there was a fallback text passed to a command * Fix a rendering issue with multi-line `tag` text. ## 1.48.0 - 2023-02-22 ### ✨ New * **Clipboard**: Added `transient` option to `Clipboard.copy` method. * **Actions**: Added `type` prop to `Action.PickDate` to control the date components to be picked. ### 💎 Improvements * Improve the time to interaction when launching a command that always renders the same view type. ### 🐞 Fixes * Changed `Deactivate Command` action shortcut to `⌘ ⌥ ⇧ D`, so it doesn't clash with `Copy Deeplink` * Fixed an issue where restarting Raycast would not properly restore menu bar commands that sometimes didn't put anything in the menu bar. * Locale: Respect the hourCycle, calendar, and numbering system locale. ## 1.47.0 - 2023-02-01 ### ✨ New * **Clipboard**: Add a new `Clipboard.read()` method that reads the clipboard content as plain text, file path, or HTML. ### 💎 Improvements * **List Accessories**: Tags can now use any color (we made some improvements to ensure that any color would have enough contrast to be readable) ### 🐞 Fixes * Fixed a bug where reloading menu bar commands in development mode would not respect certain manifest property updates (e.g. interval). * Fixed a bug that caused `Metadata.Link`'s `title` to be cut off unnecessarily when using the large text size. * Fixed a bug where `clearSearchBar` wouldn't clear the search bar when rendering a Grid. * Fixed a bug where `ray lint` would fail if there were a .DS\_Store file in the `src` folder. ## 1.46.0 - 2023-01-18 ⚠️️ **Global Fetch Deprecation**: We've removed the experimental support for global fetch in Node 18. The reason is that the feature is not stable yet (hence the warning on it being "experimental" in the dev console) and is not compatible with our new proxy feature in Raycast. We've scanned the public repository for extensions that make use of global fetch and replaced it with the *cross-fetch* dependency via separate PRs. If we missed an extension, let us know - in most cases, it should be a straightforward replacement. ### ✨ New * **Source maps** for production errors: source maps are now also enabled for production builds of an extension. When an exception occurs, you get cleaner stack traces with proper source locations in the TypeScript sources files (vs. the minified and unusable JavaScript locations). *Note*: An extension must be re-published to enable production source maps. * **Action.PickDate**: We are introducing a new Action to allow users to set a Date directly from the action panel. ### 💎 Improvements * **Dev Tools**: the "Start Development" command under "Manage Extensions" now starts development in iTerm if installed as the default terminal. * In order to ensure that date formatting & other internationalization functions work as expected, the NodeJS process is now started with the `LC_ALL` environment variable set to the user's current locale. ### 🐞 Fixes * Fixed an issue where the first exec/spawn call for running a subprocess could be slower than subsequent calls. * Fixed menu bar icon padding when there's no text. * Fixed a problem where menu bar commands updated with a new required preference would not display the required preference screen. * Fixed a rare bug with menu bar commands that could lead to Raycast hanging. * Fixed an issue where menu bar commands launching view commands would cause stacking in the navigation hierarchy. * Fixed an issue where fallback images in lists would flicker. * Dev Tools: Fixed a bug when zip archiving extensions with special characters in file names. ## 1.45.0 - 2022-12-14 ### ✨ New * **Fallback commands**: All commands (except menu-bar commands and commands with more than one required argument) can now be used as [fallback commands](https://manual.raycast.com/fallback-commands)! They should all work out of the box (e.g. a command that renders a List will receive `onSearchTextChange` with the fallback text on its first render, etc.) but you can customize the user experience with a new top-level prop `fallbackText`. * **List Accessories:** `date` and `text` accessories can now be colored. * **List Accessories:** We've added a new accessory type: `tag`. * **Metadata:** Label text can now also be colored. * **Proxy Support**: Extensions using popular networking libraries such as node-fetch/cross-fetch, got, Axios, or our useFetch hook are compatible with proxies if the user has turned on the new proxy preference in Raycast. ### 💎 Improvements * **Background refresh**: when a command misses a required preference, instead of showing the error screen, the user is directed to the preference onboarding screen again. ### 🐞 Fixes * Fixed a bug where entered characters could be "swallowed" in controlled form components or the controlled search bar. * Fixed the `launchContext` not being propagated to menu-bar and background launches when using the `launchCommand` API. * Fixed a multi-monitor [bug](https://github.com/raycast/extensions/issues/2975) where menu bar extra text would be unreadable on the inactive screen. * Fixed a bug where menu bar extra icon tinting would change based on Raycast's appearance instead of the system's. * Fixed some memory leaks when using Form components ## 1.44.0 - 2022-11-23 ### ✨ New * **Async Submenus and Dropdown**: Dropdowns and ActionPanel Submenus now also support the properties `onSearchTextChange, isLoading, throttle, filtering` - same as for List and Grid where you can perform custom logic when the user changes the search text. * **Application:** You can now get the current frontmost Application of the system with the top-level `getFrontmostApplication` method. * **File and Directory Preferences**: We've added two new preference types `"directory"` and `"file"`, supported via the manifest. Both types show a file picker component and let the user select directory or file paths. * **Environment:** You can now get the user's text size via `environment.textSize`. ### 💎 Improvements * **Pop To Root Behavior**: `closeMainWindow` accepts a new parameter `popToRootType` that lets you control when Raycast pops back to root: the default is as-is and respects the user's "Pop to Root Search" preference in Raycast. `PopToRootType.Immediate` closes the window *and* immediately pops back to root, regardless of the user's setting (so you can get rid of an additional `popToRoot()` call). The new mode `PopToRootType.Suspended` temporarily prevents Raycast from automatically popping back to root; this is useful for situations where a command needs to interact with an external system 00ity and then return the user back to the launching command. * **Clipboard:** We added new options to copy and paste HTML content, which is useful for sharing formatted text, e.g. a link to a Notion page in Slack. * **Markdown**: Markdown in a `Detail` component now supports convenience image references for icons and asset folder files such as: `![built-in icon](${Icon.AddPerson})` or `![local-assets-image](example.png)` (absolute URLs and user folder paths via `~` are also supported) * **OAuth**: The client's `providerIcon` is now optional (extension icon as default) and accepts an `Image.ImageLike` type. * **List and Detail Metadata**: Now show tooltips when labels get truncated. * **Action.ToggleQuickLook**: Now also expands paths starting with `~`. ### 🐞 Fixes * **Dropdown**: Fixed triggering a dropdown component's `onChange` handler when navigating. * **Dropdown**: Fixed the missing `placeholder` property in the search bar dropdown. * **Forms**: Fixed submitting a form with marked text. ## 1.43.0 - 2022-11-09 ### ✨ New * **Actions**: You can now specify an action to focus when opening the ActionPanel (and an ActionPanel.Submenu) by setting the `autoFocus` prop. * **Forms**: Introducing a new Form Item `Form.FilePicker` to select one or multiple files (or directories) ### 💎 Improvements * **DX**: A warning will now be shown in the console when using async entry points for view and menu-bar commands. * **List/Grid**: Improved the keyword search algorithm to match intersecting keywords (for example, the search term "black cat" matches keywords \["black", "cat"]). * **Grid**: The grid supports a new property for configuring how sections are ordered. Setting `filtering={{ keepSectionOrder: true }}` ensures that the sections' order is not changed based on items' ranking values; this can be useful for use cases where a small number of fixed sections should always appear in the same order when the user filters the grid. We are deprecating the `enableFiltering` property. ### 🐞 Fixes * Fixed the Grid or List's selection sometimes not being preserved when native filtering is disabled. * The `Image.Mask.RoundedRectangle` mask will be more consistent regardless of the size of the image. * Fixed an issue where the specified `searchText` property would not always be respected. ## 1.42.0 - 2022-10-26 ### ✨ New * The Node runtime has been updated to [Node 18](https://nodejs.org/en/blog/announcements/v18-release-announce/), the [current](https://github.com/nodejs/Release#release-schedule) Long-term Support (LTS) release. * Commands can now launch other commands! Using the new `launchCommand` method, you can now trigger a background refresh of another command in the same extension - or even open another command. Some use cases are updating a menu bar command from a view command or, vice versa, launching a companion view command from the menu bar. (Note that for now we only support launches of other commands within the same extension.) ### 💎 Improvements * **Grid** now supports two new aspect ratios: 4/3 and 3/4. * **Menu Bar** icon tinting is now theme-aware. * **Background Refresh:** The shortest interval available is now 10s instead of 1m (use cautiously and also see our [best practices guide](https://developers.raycast.com/information/background-refresh#best-practices)). * **Grid**: The grid supports a new property for configuring how sections are ordered. Setting `filtering={{ keepSectionOrder: true }}` ensures that the section order is not changed based on items' ranking values; this can be useful for use cases where a small number of fix sections should always appear in the same order when the user filters the list. We are deprecating the `enableFiltering` property. ### 🐞 Fixes * **List Item Metadata Link and Detail Metadata Link** styling should now be consistent with their respective **List Item Metadata Label** and **Detail Metadata Label** respectively. * Fixed a bug where `List.Item`'s accessories might not be aligned. * Fixed a bug where the last API call or log in a no-view command would not run before the command gets unloaded. ## 1.41.0 - 2022-10-12 ### New * **Grid**: the `Grid` component accepts three new props that should give extension authors more flexibility: `columns`, `fit` and `aspectRatio`. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-274bdfb26a191e298c4248a6d7031d08d725f484%2Fgrid-styled-sections.webp?alt=media) * **Grid Sections** don't all have to look the same anymore! The grid `Section` component now *also* accepts the `columns`, `fit` and `aspectRatio` props. When specified, they will override the value of the parent `Grid` component's prop. * **List**: The list supports a new property for configuring how sections are ordered. Setting `filtering={{ keepSectionOrder: true }}` ensures that the section order is not changed based on items' ranking values; this can be useful for use cases where a small number of fix sections should always appear in the same order when the user filters the list. We are deprecating the `enableFiltering` property. * **Menu Bar Extra:** added a new `Section` component, which can be used to better group related `Item`s and/or `Submenu`s. The component has an optional title for the section. At the same time, we are deprecating the `Separator` component. * **Menu Bar Extra**: The `Item` component now accepts an optional `subtitle` prop. * **Clipboard:** `Clipboard.copy()` and `Clipboard.paste()` methods now accept file paths as a parameter. ### 💎 Improvements * Improved dark/light mode detection for **Menu Bar Extra** icons. * If a **Menu Bar Extra**'s `title` spans multiple lines\*\*,\*\* only the first one will be displayed. ### 🐞 Fixes * Fixed certain error stack traces causing CPU spikes of the Node process. * Fixed an issue with **macOS Ventura Beta** where **Menu Bar Extra**s would sometimes become unresponsive. * Fixed the type of the List and Grid's `onSelectionChange`. It always used to return `null` when no items were selected but the type was `string | undefined`. It is now properly `string | null`. Note that this might trigger some TypeScript error when you upgrade but it should help you fix some bugs. ## 1.40.0 - 2022-09-28 ### ✨ New * **Menu Bar Extras** can now be deactivated without disabling the menu bar command! To deactivate a menu bar command, run the `Deactivate Command` action from the command's Action Panel - or drag the menu bar extra out of the menu bar while holding down ⌘. * Commands with **Background Refresh** also now have a `Deactivate Command` action! * **Menu Bar Extras** now support both a primary and secondary action type (right click or control click). * **Dropdown**'s items can now specify `keywords` to match more search terms. * **Extension Diagnostics** command can now be used to help finding the cause behind any issues with extensions. It displays all `Loaded Commands`, commands with `Background Refresh` enabled and latest `Events` triggered. ### 💎 Improvements * **Menu Bar Extra** action handlers will now either wait or force a render after finishing execution, to ensure any state updates performed in the action handler have had a chance to render. * **Menu Bar** commands now automatically refresh when their or their parent extension's preferences change. * **OAuth**: Path-based redirect URLs are now officially supported. * **OAuth**: ⚠️️ API methods for OAuth request creation now throw an error when used from a background command - you can check the launch type of a command to see whether authorization can be performed * **Types**: Node and React types have been added back as optional API peer dependencies and dev dependencies to the templates, so that VS Code autocompletion works. * **Templates**: Updated to include the utils package. * **DevX**: Added warnings when specifying a `value` without `onChange` or when changing a Form item from controlled to uncontrolled. * **DevX**: For starting development, the CLI does not depend on metadata attributes any more ### 🐞 Fixes * **Forms**: The type of the `DatePicker`'s value is now `Date | null` (`null` happens when selecting `No Date`). ⚠️ This might cause some TypeScript errors but it will now reflect what is really happening, preventing bugs at runtime. * Fixed an issue where `List.Item.Detail.Metadata` titles sometimes being cropped despite there being enough room. * **Menu Bar Extra** `Item` and `Submenu` icons now change based on the system's dark / light mode, not Raycast's. * **Forms**: Fixed a bug where the initial value for a controlled TextArea could not be deleted. * **Forms**: Fixed the info icon and message not coming back after clearing an error on form items. * **Forms**: Fixed updating the placeholder of the TagPicker item. * **Empty View**: Fix an issue where an Empty View's actions would be rendered even thought the Empty View isn't. * **OAuth**: Fixed a bug where multiple overlays could stack upon each other when OAuth was initiated from a menu bar or background launched command ## 1.39.2 - 2022-09-01 ### ✨ New * **Bundler**: You can now import wasm files and they will bundle in the extension ### 💎 Improvements * **SVG**: Accept a percentage for rect corner radius attributes * **Actions**: `Action.Trash` is now a Destructive Action (meaning it will show up in red) ### 🐞 Fixes * **Metadata**: Fixes an issue where List Metadata would sometimes render Tags in the wrong position ## 1.39.0 - 2022-08-18 ### ✨ New * **List.Item.Detail.Metadata**: We've added support for new `Link` and `TagList` item types. * **Environment**: You can now check the `mode` of the current command *(as defined in the manifest)* *via* `environment.commandMode`. ### 💎 Improvements * **CLI**: The ray CLI is now code-signed * **CLI**: We've updated esbuild to v0.14.52 * **NPM size:** is now 0.5MB instead of 25MB *(binary files for ray CLI have been moved out of the NPM package)* ### 🐞 Fixes * **Navigation**: Top-level components can now dynamically return a different view type when used inside a navigation stack * **Background Refresh**: Fixed an edge case where commands would run into a timeout that prevented further refreshing * **Menu Bar Commands**: Fixed a bug where the error screen of menu bar commands would repeatedly be shown in the root search * **Actions:** Triggering actions by *numeric shortcut / double-clicking* could trigger wrong actions or didn't work entirely * **Form:** `TextArea` placeholder now won't highlight markdowns if it has `enabledMarkdown` ## 1.38.3 - 2022-08-03 ### 💎 Improvements * Added debug actions to all local development commands in root search * Menu bar commands now show an activation button in preferences ### 🐞 Fixes * **Menu Bar Commands**: Fixed issues around hot reloading, unloading, and inconsistent action handler behavior * **No-view Commands:** Fixed returning top-level props for commands that doesn't have arguments or drafts ## 1.38.1 - 2022-07-21 ### ✨ New * 🍫 **Menu Bar Commands (Beta)**: For a long time, Commands could only live in the Raycast window. From today, Commands can put glanceable information in the Menu Bar 💪. Check out our [new docs section](https://developers.raycast.com/api-reference/menu-bar-commands) on how to develop your first native macOS menu bar command with hot reloading 🔥. * 🔄 **Background Refresh (Beta)**: To keep Menu Bar Commands up-to-date, we ported Background Refresh from [Script Commands](https://github.com/raycast/script-commands) to Extensions. Background Refresh is configured with a new interval option in the Extension's [manifest](https://developers.raycast.com/information/manifest) and also works for "no-view" mode commands. Read more about it in a [new docs guide](https://developers.raycast.com/information/background-refresh). * 🪝 **Utils**: We've released new React hooks to make it faster to build extensions that follow best practices. To do this, we looked at the Extension's repository for use cases and how we can improve them. Most Extensions connect to APIs: they make network requests, show a toast to handle errors, and add caching and optimistic updates to speed up interactions. Utils are available via a [new public npm package](https://www.npmjs.com/package/@raycast/utils). * 🤬 **Arguments**: We also ported more functionality from Script Commands. Extensions can now define arguments, which enable simple forms that live in the root search of Raycast. Arguments can be defined via the [manifest](https://developers.raycast.com/information/manifest), and their entered values are passed to the main function of a command. * ✍️ **Subtitle Updates**: We've added a new method `updateCommandMetadata` that allows you to update the subtitle of a command in root search. Combined with Background Refresh, this is another way to present information to the user as dashboard-like items in the root search. ## 1.38.0 - 2022-07-19 ### ✨ New * **Redesign**: Along side the app's redesign, we are introducing a whole set of [new icons](https://developers.raycast.com/api-reference/user-interface/icons-and-images#icon) for you to pick to illustrate the actions in your extensions. * **New Destructive Action:** You can now specify the `style` of an `Action` to highlight it in the Action Panel as destructive. Use it for actions where an end-user should be cautious with proceeding. ### 💎 Improvements * **DevTools**: Turning on the "Use Node production environment" in the Advanced Preferences will also hide the debug actions. Previously it was only hiding them when there was no Action Panel specified. * **DevTools**: The "Clear Local Storage" debug action has been renamed to "Clear Local Storage & Cache" and will clear the [Cache](https://developers.raycast.com/api-reference/cache) along side the [Local Storage](https://developers.raycast.com/api-reference/storage). * **Dev Tools**: The "Start Development" action now quotes the extension folder path. * **Dev Tools**: Added a new development advanced preference to keep the Raycast window always visible during development. * **Dev Tools**: Added a new build status tooltip to the accessory icon of a development command in root search. * **Dev Tools**: Improved the error handling for failed extension updates after invalid manifest changes; improved the error messages for general rendering errors. ### 🐞 Fixes * `require('os').tmpdir()` will now properly return the path to a temp directory. * Fixed a rarely occurring crash happening when using some SVGs with a path that contains an arc where the ending point is the same as the starting point. * Forms: Fixed a bug where stored form values could get cleared after extension updates. * Forms: Fixed inconsistent behaviour of the `onBlur` handler that could get triggered for the `Form.TextField` when the form screen is popped in a navigation stack. * List: Fixed the updating of tooltips for list accessory items. ## 1.37.0 - 2022-06-29 ### ✨ New * **React 18**: React Suspense, `useSyncExternalStore`, etc.. A whole bunch of new features are available with the newest version of React. See the [migration guide](https://developers.raycast.com/migration/v1.37.0) for more information. * **Quick Look:** Use the new `` action to show additional information with a Quick Look preview. * **Forms:** Use the new validation feature to check if entered data is correctly formatted and show failure messages with a nice UX * **Forms:** Drafts support - use the feature if you want Raycast to preserve non-submitted data, to provide the best experience for users * **DevX:** Check out the new screenshot tool that takes a photo of Raycast from the best possible angle ### 💎 Improvements * **List Accessories**: You can now pass `{date: Date}` as an accessory and it will be rendered nicely by Raycast. * **Detail View:** Add support for `- [ ] task` and `- [x] task` in markdown views. * **Action Panel**: Add a new `onOpen` callback on `ActionPanel.Submenu`. It can, for example, be used to lazily fetch the content of the Submenu. * **Grid**: Add support for `ColorLike` as Grid.Item's content. * **Forms:** New callbacks `onFocus` and `onBlur` for all the items * **Forms:** Markdown highlighting for the `Form.TextArea` ### 🐞 Fixes * **Misc:** Fixed a crash when using `{response?.website && }` and `website` is an empty string ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 1845](https://github.com/raycast/extensions/issues/1845)). * **Dev Tools**: Fixed uninstalling of local development extensions via the Action Panel * **Markdown**: Fixed rendering of transparent animated gifs in markdown * **Forms:** Fixed an issue when entering characters with IME ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 739](https://github.com/raycast/extensions/issues/739)) in controlled text inputs * **List Accessories:** Fixed the tooltip for grouped accessories; now the tooltip will be shown for the group instead of separately for the items ## 1.36.0 - 2022-06-01 ### ✨ New The `` component's made its way to our API. It's perfect to layout media-heavy information, such as icons, images or colors. The component allows you to layout differently sized items. We designed [its API](https://developers.raycast.com/api-reference/user-interface/list) close to the `` component for smooth adoption. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-4bb3d7e88613cf9ccba01c798f5d2aa62edfaeac%2Fgrid.webp?alt=media) ### 🐞 Fixes * Fixed the controlled mode for `Form.DatePicker` * Fixed the dynamic appearance of form item's `info` accessory * Fixed the OAuth logout preference not being shown for single-command extensions * Fixed a bug where components that are pushed with the same properties values would not be updated ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 1843](https://github.com/raycast/extensions/issues/1843)) * Fixed a bug where updated list metadata items would cause unnecessary list reloading * Fixed an issue with tinted, resized icons appearing blurred in some cases (e.g. Alerts) ## 1.35.0 - 2022-05-18 ### ✨ New * **List Item Metadata**: we've added a new `metadata` property to the `List.Item.Detail` component, allowing you to add structured metadata. The `metadata` property can be used together with `markdown`, in which case the detail view will be split horizontally, with the markdown being displayed in the top half and the metadata displayed in the bottom half (similar to the `File Search`, `Clipboard History` or `Search Contacts` commands). Alternatively, it can be used by itself, in which case the metadata will take up the entire height of the detail view. * **Preferences**: We've added two new top-level methods `openExtensionPreferences` and `openCommandPreferences` that allow you to open both extension and command preferences, for example, via an Action ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 179](https://github.com/raycast/extensions/issues/179)) ### 💎 Improvements * Added a new development action to clear the local storage of an extension ### 🐞 Fixes * Fixed a bug where the wrong form element onChange handler would be called initially while the form was being updated ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 1633](https://github.com/raycast/extensions/issues/1633)) * Fixed a bug where form elements would not be re-rendered correctly ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 1663](https://github.com/raycast/extensions/issues/1663)) * Fixed a bug where a fully controlled form TextField/PasswordField behaves as stateful ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 1093](https://github.com/raycast/extensions/issues/1093)) * Fixed `EmptyView` not being displayed when it would be reused in a navigation stack ## 1.34.0 - 2022-05-04 ### 💎 Improvements * OAuth: TokenSets are now included in the encrypted Raycast export (Raycast Preferences > Advanced > Export) * OAuth: The convenience method `TokenSet.isExpired()` now includes some buffer time to reduce the risk of performing requests with expired access tokens ### 🐞 Fixes * Fixed an issue where updating the search bar accessory would result in the search bar text being selected * Forms: We've fixed some inconsistencies around form item properties and added new warnings (e.g. when `defaultValue` and `value` are set at the same time); this also fixes [![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 1104](https://github.com/raycast/extensions/issues/1104) * Forms: Fixed an issue where updating form items would lead to unwanted scrolling; fixed the `autoFocus` property not scrolling to the focused item * Fixed an issue with `Action.OpenWith` trying to perform a state update without checking whether it's still mounted ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue 1495](https://github.com/raycast/extensions/issues/1495)). * Fixed an issue where `adjustContrast` would not be respected for colored TagPicker items. ## 1.33.0 - 2022-04-20 ### ✨ New * **OAuth**: we've added a new API that enables you to authorize extensions through OAuth providers such as Google, Twitter, Dropbox or Spotify ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #178](https://github.com/raycast/extensions/issues/178)). The docs contain a [new detailed guide](https://developers.raycast.com/api-reference/oauth) and we've added some integration examples to the extensions repository. (Note that we currently only support OAuth 2.0 with PKCE, more on that in the [guide](https://developers.raycast.com/api-reference/oauth).) * **Form Focus**: use the new imperative form API to programmatically focus form items. Want to make sure a particular input is focused on mount? Form items now accept an `autoFocus` prop! ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #66](https://github.com/raycast/extensions/issues/66)) * **Form Reset**: use the new imperative form API to reset form items' values to their initial values. * **Form Info:** Use the new `info` prop on form items to show additional information about an item, e.g. to explain what this field is used for. * The Raycast window opens automatically when you start a development session for an extension. You can turn the behavior off in the Advanced preferences tab. ### 💎 Improvements * Improved detection of default editor when you open extensions from Raycast * Improved templates for list, form and detail * Removed `react-devtools` from `devDependencies` for newly created extensions (so that you don't have to download a big dependency that you might not use) ### 🐞 Fixes * Fixed an issue where animated gifs would be incorrectly scaled when size attributes are specified in markdown. * Form Checkbox now returns proper boolean values on submit ## 1.32.0 - 2022-04-06 ### ✨ New * **List Tooltips**: List items now support tooltips for the title, subtitle, icon, and each accessory item. For titles, you can use the new type `{ value: string, tooltip: string }`, for icons `{ value: Image.ImageLike, tooltip: string }`, and for accessories you just add the new property `tooltip`. * **Animated Gifs**: the `Detail` component now renders animated gifs defined in markdown! 🎭 ### 💎 Improvements * Improved recovering the Node process after a crash and logging the error to the CLI output * Added support for running CLI commands through `npx @raycast/api ` * Improved the `Create Extension` command to add `README.md` and `CHANGELOG.md` files ### 🐞 Fixes * **Detail Metadata**: Fixed toggling (showing/hiding) * **Detail Metadata**: Fixed only one separator item getting rendered * **Detail Metadata**: Fixed a crash when using primary or secondary colors for tag items * **List Accessories**: Fixed rendering when using `undefined` for accessory values * **List EmptyView**: Fixed an issue where passing a `List.EmptyView` child to a `List.Section` would treat it as a `List.Item` * **SVG**: Fixed rendering base64 encoded SVG images * Fixed loading when a new command is launched by hotkey while another command is open ## 1.31.0 - 2022-03-23 ### ✨ New * **Detail Metadata**: we've added a new property `metadata` to the `Detail` component; this allows you to add structured metadata that is displayed on the right side in a detail view (similar to the Linear, Asana or Jira extensions). We support types such as labels, coloured tags, links, and separators. ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #219](https://github.com/raycast/extensions/issues/219)) * **List Accessories**: list components can now show multiple accessory items through the new `accessories` property. (Previously you could only configure one `accessoryTitle` and `accesoryIcon`, both of which continue to work but have been marked deprecated.) Each item can be configured as text-only, icon-only, or icon + text. ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #72](https://github.com/raycast/extensions/issues/72)) * **List Empty View**: list components can define a new `EmptyView` that gives you control over the icon, title, description and optional actions to use when there are no items in a list. (Previously we would default to a "No results" view.) You can use the component to show a custom image and text when the search does not return results or the user is required to first perform some setup. ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #447](https://github.com/raycast/extensions/issues/447)) ### 💎 Improvements * **Environment**: the current theme (`"dark" | "light"`) configured via Raycast appearance preferences is now globally accessible through `environment.theme` * **SVG**: You can now specify width and height attributes for images in markdown (`` tag). * **Dev Tools:** the "Create Extension" command lets you add categories to your extension; the categories are displayed alongside the new metadata on our revamped details page in the store. * **Dev Tools**: added a new development action to clear the local assets cache, e.g. to render an updated list icon without having to restart Raycast. ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #1095](https://github.com/raycast/extensions/issues/1095)) * **Preferences**: the `required` property in manifest preferences is now optional. ### 🐞 Fixes * Fixed the extension icon not being updated during development. * Fixed an extension's cached icon not being cleared when updated from the store. (Note that other dynamically loaded images in the assets folder may still be cached, so if you want to enforce an update for end users you need to rename them.) * Fixed an edge case where some search bar characters would be wrongly passed to pushed lists in a navigation stack. ## 1.30.2 - 2022-03-11 ### 🐞 Fixes * Fixed updating the list `isShowingDetail` property * Fixed unnecessarily reloading the list detail view on search term changes ## 1.30.0 - 2022-03-09 ### ✨ New * We've added the highly requested **search bar dropdown** 🎉 ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #72](https://github.com/raycast/extensions/issues/72)): you can now add a dropdown component as an accessory to the search bar; the dropdown shows up in the top-right corner and can be used for filtering lists and toggling list states. (So it's a good time to remove any workarounds with actions or navigation for showing a different set of items in the list.) * The **search bar text** 🔎 can now be programmatically updated ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #281](https://github.com/raycast/extensions/issues/281)) while you can still opt into built-in filtering at the same time * **List-detail views**: list views now support a `detail` property that allows you to display a detail view on the right-hand side of list items ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #83](https://github.com/raycast/extensions/issues/83)) 👯‍♂️; you can use the feature to display additional content side-by-side as users scroll through the list * Support for rendering **SVG files** 🖼️ where images are accepted ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #77](https://github.com/raycast/extensions/issues/77)), including in the `Detail` view's markdown * New method `Clipboard.readText()` to read the last copied text from the system's clipboard 📋 * Added a new prop `type` to `Form.DatePicker` 📅 to control the date components asked from the user ### 💎 Improvements * **Toast action handlers** 🍞 can now still be called if the toast outlives a dismissed extension * Support for multiple actions of type `Action.SubmitForm` in a form's Action Panel ### 🐞 Fixes * Fixed some flickering that could happen when using `React.memo` * Fixed a few edge cases around Action Panels * Fixed duplicated shortcut shown in the Action Panel's tooltip when setting the default shortcut explicitly on the primary action * Fixed updating a `Form.Description` component ## 1.29.0 - 2022-02-23 ### ✨ New * Add 2 new Actions: `Action.CreateSnippet` and `Action.CreateQuicklink`. Use them in your extensions to provide users an option to integrate deeper with Raycast, for example, by creating a Quicklink from a frequently visited website. ### 💎 Improvements * Various documentation fixes and improvements such as new media for UI components. * Synchronous React state update calls are now batched, leading to less re-rendering. * Markdown comments will now be hidden in the `Detail` view ### 🐞 Fixes * Fixed a crash that could happen when switching between a development and store version of an extension or restarting the Node connection. * Fixed an issue with React Developer Tools sometimes not getting opened. * Limit the width that the `ActionPanel` can take. ## 1.28.0 - 2022-02-09 ### 💎 Improvements * Completely **revised (backwards-compatible) API** - new namespaces, better organisation, more consistency, updated templates, revamped docs. Check out the full [migration guide](https://developers.raycast.com/migration/v1.28.0) and get rid of those deprecation warnings. (At the same time, don't worry, your extension is going to work as before, even if you don't take immediate action.) * We've **prettified the CLI output** 💅: all output is now more colourful, cleaner and easier to parse. Update the npm package to v1.28.0 to get the latest CLI for development. * **Fallback images**: You can now specify local asset files or built-in icons that are displayed when image loading fails, for example when a remote file is missing (![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)[Issue #108](https://github.com/raycast/extensions/issues/108)); [see the docs](https://developers.raycast.com/api-reference/user-interface/icons-and-images) * **Toasts** are now passed as argument to their action callback, so you can directly act on them in the handler function (for example, hiding them) * **Extensions feedback:** We've added **better bug report and feature request actions** both to the store details page of an extension and to the error screen; the actions prefill some data already in the templates so that reporting issues and feature requests becomes easier for end users. ### 🐞 Bugfixes * Fixed tag picker images and emojis not being properly displayed (![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)[Issue #493](https://github.com/raycast/extensions/issues/493)) ## 1.27.1 - 2022-01-28 ### 💎 Improvements * **Preferences:** Added a new app picker preference type - useful if you want to let users customize their apps to use for opening files, folders and URLs [![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #98](https://github.com/raycast/extensions/issues/98) * **Forms:** Added new `Form.PasswordField` that allows you to show secure text fields ([Issue #319](https://github.com/raycast/extensions/issues/319) and [![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #44](https://github.com/raycast/extensions/issues/44)) * **Forms:** Added new `Form.Description` component that allows you to show a simple label * Added a new top-level `open` method that gives you more flexibility for opening files, folders, and URLs with default apps or specified apps, often making using an external npm package unnecessary (the built-in open action use our method under the hood) * **Node:** added security enhancements for the managed Node runtime such as verification of the executable, configuring executable permissions, and removing unnecessary files * **CLI:** Added more error info output to build errors * **CLI:** Added a new `—fix` flag to the `lint` command (applies ESLint and prettier fixes) * **Create Extension Command:** Updated the templates to include a `fix-lint` script; added prettier to devDependencies ### 🐞 Bugfixes * **Forms:** Fixed `onChange` callback behaviour to be consistent across all components * **Forms:** Fixed generic updates of titles for all components ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #687](https://github.com/raycast/extensions/issues/687)) * **Preferences:** Fixed a bug in dropdown preferences returning the defined default value, even if the default is not part of the list values * **Preferences:** Fixed the `data` property not being treated as required for the dropdown * **Preferences:** Fixed defined initial values not being ignored (use default only) * **List:** Fixed same-rank items with identical names being non-deterministically ordered * Fixed a bug with open actions causing double opening via the default and specified app * **CLI:** Removed auto-installation of npm dependencies through the downloaded npm ## 1.27.0 - 2022-01-12 ### 💎 Improvements * **Developer Tools:** Added `Open Support Directory` action to local dev extensions * **Developer Tools**: Removed auto-injecting of globals for enabling React Developer Tools in dev mode * **Developer Tools**: Added `prettier` checks to CLI `lint` command * **Documentation:** Updates and fixes ### 🐞 Bugfixes * **Forms:** Fixed controlled updates for the `Form.TagPicker` * **Navigation**: Fixed a bug where a programmatic pop, followed by a manual pop (e.g. ESC) could lead to wrong state (![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)[Issue #571](https://github.com/raycast/extensions/issues/571)) ## 1.26.3 - 2021-12-16 ### ✨ New * New API for **Alert** views: Alerts are useful for destructive actions or actions that require user confirmation; new methods let you display our beautiful native Alert component\ ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #48](https://github.com/raycast/extensions/issues/48)) * New API for **interactive Toasts**: you can now add buttons to Toasts, e.g. to give the user options for created items, to open the browser, or for any other relevant context ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #438](https://github.com/raycast/extensions/issues/438)) * New API for retrieving the current **Finder selection**: unlocks a couple of use cases for extensions that perform actions on selected files and folders ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #153](https://github.com/raycast/extensions/issues/153)) ### 💎 Improvements * Improved ranking for fuzzy search in lists with sections and keywords * The icon of the `OpenWithAction` can now be customised * The env var NODE\_EXTRA\_CA\_CERTS is now being propagated so that custom certificates can be configured * Improved the CLI error message when an entry point file from the manifest is missing ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #495](https://github.com/raycast/extensions/issues/495)) ### 🐞 Bugfixes * Textfields do not auto-transform certain characters such as dashes any more ([![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #491](https://github.com/raycast/extensions/issues/491) and [![](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fexternal_integrations%2Fgithub-icon.png?width=12\&userId=\&cache=v2)Issue #360](https://github.com/raycast/extensions/issues/360)) ### ⚙️ Build Updates * This CLI of this version contains an update of the build tool with changed (and "more compatible") heuristics around how `default` exports are handled. This means that you should double check whether `import` statements for certain npm packages need to be adjusted.\ **Example**: `import caniuse from "caniuse-api"` has to be changed to `import * as caniuse from "caniuse-api"` because of the missing `default` export of the built `caniuse` library that has to run in a Node environment. ## 1.25.7 - 2021-11-26 ### 💎 Improvements * Keywords added to list items are now matched again by prefixes (exact matches were required previously) * Extensions are now checked for version compatibility before updating and installation * New and updated templates available in the "Create Extension" scaffolding command ### 🐞 Bugfixes * Modifications to list item keywords could result in wrong list filtering * Fixed a regression where the CLI would not automatically install dependencies when building the extension * DatePicker form element now returns the time component when specified * Animated toasts are now automatically dismissed when the extension is unloaded * Forms don't accidentally trigger draft creation mode any more * Extensions which are off by default are now correctly disabled ## 1.25.5 - 2021-11-18 ### 💎 Improvements * Full fuzzy search by default for lists using built-in filtering * Faster list loading times * Better default auto-layout of list item title, subtitle and accessories * Extension support directory does not need to be explicitly created any more * Raycast is no longer automatically brought to the foreground for failure toasts * New default action to open a bug report on production error screens in extensions ### 🐞 Bugfixes * Updated extension icons are now displayed without having to re-install the dev extension * Focus is now kept on the current form element when re-rendering * Caret does not jump to the end of the string in controlled textfields and textareas any more (one edge left that is going to be tackled in one of the next releases) * "Disable pop to root search" developer preference is now only applied for commands that are under active development * Documentation fixes and updates ## 1.25.4 - 2021-11-11 ### 💎 Improvements * Updating of items and submenus while the action panel is open * Supporting all convenience actions with primary shortcut (cmd + enter) on form views * Better error handling when the API cannot be loaded after failed app updates ### 🐞 Bugfixes * Loading indicator in detail views when used in a navigation stack ## 1.25.2 - 2021-10-28 ### 💎 Improvements * Improved ActionPanel updating performance ### 🐞 Bugfixes * `searchBarPlaceholder` updates when using the list in a navigation stack * Wrong action panel actions when popping back in a navigation stack * Empty state flickering when updating the `isLoading` property in lists * Accessory and subtitle label truncation in lists * Icon from assets tinting on dynamic theme changes * Dynamic removal of form elements * Open actions leading to Node env vars being set for the opened application * Some extensions not getting loaded for a particular Node setup * Local storage values being lost when extensions are automatically updated ## 1.25.1 - 2021-10-20 ### 🐞 Bugfixes * Fixed configuring `tintColor` for icons in `ActionPanel` and `Form.Dropdown` * Fixed displaying submenu icons from local assets * Fixed tinting of icons provided from local assets * Fixed a crash with the `getSelectedText` function * Fixed the main window sometimes not shown when an error is thrown from a command * Fixed the `OpenWithAction` not working for some apps * Fixed the list empty state not being shown in certain cases when using custom filtering * Fixed the the topmost item not automatically being selected for custom list filtering * Fixed the line number info in error stack traces sometimes not being correct * Fixed an issue where installing store extension would sometimes fail * Fixed a crash that could be caused by sending invalid codepoints from an extension * Fixed a bug where no error would be shown when the runtime download failed * Fixed reaching the max. call stack size when logging recursive object structures (this could happen when you console logged a React component, for example). ## 1.25.0 - 2021-10-13 ### Hello World It's happening! We're opening up our API and store for public beta. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-6de7d7ba17dddccdb8afa568bde72d292a5e2920%2Fchangelog-hello-world.webp?alt=media) This is a big milestone for our community. We couldn't have pulled it off without our alpha testers. A massive shoutout to everybody who helped us shape the API. Now let's start building. We can't wait to see what you will come up with. --- # Source: https://developers.raycast.com/information/developer-tools/cli.md # CLI The CLI is part of the `@raycast/api` package and is automatically installed in your extension directory during setup. To get a list of the available CLI commands, run the following command inside your extension directory: ```bash npx ray help ``` ## Build `npx ray build` creates an optimized production build of your extension for distribution. This command is used by our CI to publish your extension to the store. You can use `npx ray build -e dist` to validate that your extension builds properly. ## Development `npx ray develop` starts your extension in development mode. The mode includes the following: * Extension shows up at the top of the root search for quick access * Commands get automatically reloaded when you save your changes (you can toggle auto-reloading via Raycast Preferences > Advanced > "Auto-reload on save") * Error overlays include detailed stack traces for faster debugging * Log messages are displayed in the terminal * Status indicator is visible in the navigation title of the command to signal build errors * Imports the extension to Raycast if it wasn't before ## Lint `npx ray lint` runs [ESLint](http://eslint.org) for all files in the `src` directory. ## Migrate `npx ray migrate` [migrates](https://developers.raycast.com/misc/migration) your extension to the latest version of the `@raycast/api`. ## Publish `npx ray publish` verifies, builds, and publishes an extension. If the extension is private (eg. has an `owner` and no public `access`), it will be published to the organization's private store. This command is only available to users that are part of that organization. Learn more about it [here](https://developers.raycast.com/teams/getting-started). --- # Source: https://developers.raycast.com/api-reference/clipboard.md # Clipboard Use the Clipboard APIs to work with content from your clipboard. You can write contents to the clipboard through [`Clipboard.copy`](#clipboard.copy) and clear it through [`Clipboard.clear`](#clipboard.clear). The [`Clipboard.paste`](#clipboard.paste) function inserts text at the current cursor position in your frontmost app. The action [`Action.CopyToClipboard`](https://developers.raycast.com/user-interface/actions#action.copytoclipboard) can be used to copy content of a selected list item to the clipboard and the action [`Action.Paste`](https://developers.raycast.com/user-interface/actions#action.paste) can be used to insert text in your frontmost app. ## API Reference ### Clipboard.copy Copies text or a file to the clipboard. #### Signature ```typescript async function copy(content: string | number | Content, options?: CopyOptions): Promise; ``` #### Example ```typescript import { Clipboard } from "@raycast/api"; export default async function Command() { // copy some text await Clipboard.copy("https://raycast.com"); const textContent: Clipboard.Content = { text: "https://raycast.com", }; await Clipboard.copy(textContent); // copy a file const file = "/path/to/file.pdf"; try { const fileContent: Clipboard.Content = { file }; await Clipboard.copy(fileContent); } catch (error) { console.log(`Could not copy file '${file}'. Reason: ${error}`); } // copy confidential data await Clipboard.copy("my-secret-password", { concealed: true }); } ``` #### Parameters | Name | Description | Type | | ----------------------------------------- | ------------------------------------- | ----------------------------------------------------------------- | | content\* | The content to copy to the clipboard. | `string` or `number` or [`Clipboard.Content`](#clipboard.content) | | options | Options for the copy operation. | [`Clipboard.CopyOptions`](#clipboard.copyoptions) | #### Return A Promise that resolves when the content is copied to the clipboard. ### Clipboard.paste Pastes text or a file to the current selection of the frontmost application. #### Signature ```typescript async function paste(content: string | Content): Promise; ``` #### Example ```typescript import { Clipboard } from "@raycast/api"; export default async function Command() { await Clipboard.paste("I really like Raycast's API"); } ``` #### Parameters | Name | Description | Type | | ----------------------------------------- | ------------------------------------ | ----------------------------------------------------------------- | | content\* | The content to insert at the cursor. | `string` or `number` or [`Clipboard.Content`](#clipboard.content) | #### Return A Promise that resolves when the content is pasted. ### Clipboard.clear Clears the current clipboard contents. #### Signature ```typescript async function clear(): Promise; ``` #### Example ```typescript import { Clipboard } from "@raycast/api"; export default async function Command() { await Clipboard.clear(); } ``` #### Return A Promise that resolves when the clipboard is cleared. ### Clipboard.read Reads the clipboard content as plain text, file name, or HTML. #### Signature ```typescript async function read(options?: { offset?: number }): Promise; ``` #### Example ```typescript import { Clipboard } from "@raycast/api"; export default async () => { const { text, file, html } = await Clipboard.read(); console.log(text); console.log(file); console.log(html); }; ``` #### Parameters | Name | Description | Type | | -------------- | ------------------------------------------------------------------------------------------ | -------- | | options | Options for the read operation. | `Object` | | options.offset | Specify an offset to access the Clipboard History. Minimum value is 0, maximum value is 5. | `number` | #### Return A promise that resolves when the clipboard content was read as plain text, file name, or HTML. ### Clipboard.readText Reads the clipboard as plain text. #### Signature ```typescript async function readText(options?: { offset?: number }): Promise; ``` #### Example ```typescript import { Clipboard } from "@raycast/api"; export default async function Command() { const text = await Clipboard.readText(); console.log(text); } ``` #### Parameters | Name | Description | Type | | -------------- | ------------------------------------------------------------------------------------------ | -------- | | options | Options for the readText operation. | `Object` | | options.offset | Specify an offset to access the Clipboard History. Minimum value is 0, maximum value is 5. | `number` | #### Return A promise that resolves once the clipboard content is read as plain text. ## Types ### Clipboard.Content Type of content that is copied and pasted to and from the Clipboard ```typescript type Content = | { text: string; } | { file: PathLike; } | { html: string; text?: string; // The alternative text representation of the content. }; ``` ### Clipboard.ReadContent Type of content that is read from the Clipboard ```typescript type Content = | { text: string; } | { file?: string; } | { html?: string; }; ``` ### Clipboard.CopyOptions Type of options passed to `Clipboard.copy`. #### Properties | Property | Description | Type | | --------- | ---------------------------------------------------------------------------------------------------------------------- | --------- | | concealed | Indicates whether the content be treated as confidential. If `true`, it will not be recorded in the Clipboard History. | `boolean` | --- # Source: https://developers.raycast.com/teams/collaborate-on-private-extensions.md # Collaborate on Private Extensions Isn't it more fun to work with your colleagues together on your extension? For this, we recommend to share all your extensions in a single repository, similar to how we organize the [public store](https://raycast.com/store). If you follow the [Getting Started guide](https://developers.raycast.com/teams/getting-started), we will set up a local repository for you that is optimized for collaboration. As next steps, create a [new repository](https://github.com/new) and push the existing local repository. Afterwards, your teammates can check out the code and help you improve your extensions and add new ones. --- # Source: https://developers.raycast.com/api-reference/user-interface/colors.md # Colors Anywhere you can pass a color in a component prop, you can pass either: * A standard [Color](#color) * A [Dynamic](#color.dynamic) Color * A [Raw](#color.raw) Color ## API Reference ### Color The standard colors. Use those colors for consistency. The colors automatically adapt to the Raycast theme (light or dark). #### Example ```typescript import { Color, Icon, List } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Enumeration members | Name | Dark Theme | Light Theme | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Blue | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-bb792f0278bbeea127bab32134a0addabe1f471d%2Fcolor-dark-blue.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-b5fd11b78553af9231fec2bbd2065fa5b21daccd%2Fcolor-blue.webp?alt=media) | | Green | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-1f26f61f56df07565cd4749355f553c998856c75%2Fcolor-dark-green.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-0ecfe804320264a9ea87283aaa994930f7cb4d7d%2Fcolor-green.webp?alt=media) | | Magenta | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-460fbc6e16fa66a017a31462d181e1b7db1299f1%2Fcolor-dark-magenta.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-aab0de01b6ee3f98eedd291af31708cc27c1bceb%2Fcolor-magenta.webp?alt=media) | | Orange | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-5c867edbcb01e9fb71ddbfd0351ec7e19cadbec2%2Fcolor-dark-orange.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-a80e366e832757469108afcaddff62f3aeb1d3a0%2Fcolor-orange.webp?alt=media) | | Purple | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-6e5cba871d9c05aa374915445a220b40f3838aeb%2Fcolor-dark-purple.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-eb0b963fade8b81c5680524e646b95ebb223476e%2Fcolor-purple.webp?alt=media) | | Red | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-ffb7f9534f418f2c52ff9983abbb886df9496f9f%2Fcolor-dark-red.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-73815c38fa3cbc20735f01160de3d9172a277cb1%2Fcolor-red.webp?alt=media) | | Yellow | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-970d9768755133d23b1e27ff90b72b78e4753bad%2Fcolor-dark-yellow.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-656146171a1eb17d688bb5bc16eb442f955a6cf3%2Fcolor-yellow.webp?alt=media) | | PrimaryText | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-46bb91f2a4e7d5a5ebe2b6e358560c6f5933ed47%2Fcolor-dark-primary-text.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-a2b368984466f481e372240d508588bb603f88bb%2Fcolor-primary-text.webp?alt=media) | | SecondaryText | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-eb8e7a97fd21d5853b9c40bc47634e4b8033585e%2Fcolor-dark-secondary-text.webp?alt=media) | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-6302cb6111a94d075871bbc02b7d39b124eb70dd%2Fcolor-secondary-text.webp?alt=media) | ## Types ### Color.ColorLike ```typescript ColorLike: Color | Color.Dynamic | Color.Raw; ``` Union type for the supported color types. When using a [Raw Color](#color.raw), it will be adjusted to achieve high contrast with the Raycast user interface. To disable color adjustment, you need to switch to using a [Dynamic Color](#color.dynamic). However, we recommend leaving color adjustment on, unless your extension depends on exact color reproduction. #### Example ```typescript import { Color, Icon, List } from "@raycast/api"; export default function Command() { return ( ); } ``` ### Color.Dynamic A dynamic color applies different colors depending on the active Raycast theme. When using a [Dynamic Color](#color.dynamic), it will be adjusted to achieve high contrast with the Raycast user interface. To disable color adjustment, you can set the `adjustContrast` property to `false`. However, we recommend leaving color adjustment on, unless your extension depends on exact color reproduction. #### Example ```typescript import { Icon, List } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Properties | Property | Description | Type | | --------------------------------------- | ------------------------------------------------------------------- | --------- | | dark\* | The color which is used in dark theme. | `string` | | light\* | The color which is used in light theme. | `string` | | adjustContrast | Enables dynamic contrast adjustment for light and dark theme color. | `boolean` | ### Color.Raw A color can also be a simple string. You can use any of the following color formats: * HEX, e.g `#FF0000` * Short HEX, e.g. `#F00` * RGBA, e.g. `rgb(255, 0, 0)` * RGBA Percentage, e.g. `rgb(255, 0, 0, 1.0)` * HSL, e.g. `hsla(200, 20%, 33%, 0.2)` * Keywords, e.g. `red` --- # Source: https://developers.raycast.com/api-reference/command.md # Command This set of utilities to work with Raycast commands. ## API Reference ### launchCommand Launches another command. If the command does not exist, or if it's not enabled, an error will be thrown.\ If the command is part of another extension, the user will be presented with a permission alert.\ Use this method if your command needs to open another command based on user interaction,\ or when an immediate background refresh should be triggered, for example when a command needs to update an associated menu-bar command. #### Signature ```typescript export async function launchCommand(options: LaunchOptions): Promise; ``` #### Example ```typescript import { launchCommand, LaunchType } from "@raycast/api"; export default async function Command() { await launchCommand({ name: "list", type: LaunchType.UserInitiated, context: { foo: "bar" } }); } ``` #### Parameters | Name | Description | Type | | ----------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------- | | options\* | Options to launch a command within the same extension or in another extension. | [`LaunchOptions`](#launchoptions) | #### Return A Promise that resolves when the command has been launched. (Note that this does not indicate that the launched command has finished executing.) ### updateCommandMetadata Update the values of properties declared in the manifest of the current command. Note that currently only `subtitle` is supported. Pass `null` to clear the custom subtitle. {% hint style="info" %} The actual manifest file is not modified, so the update applies as long as the command remains installed. {% endhint %} #### Signature ```typescript export async function updateCommandMetadata(metadata: { subtitle?: string | null }): Promise; ``` #### Example ```typescript import { updateCommandMetadata } from "@raycast/api"; async function fetchUnreadNotificationCount() { return 10; } export default async function Command() { const count = await fetchUnreadNotificationCount(); await updateCommandMetadata({ subtitle: `Unread Notifications: ${count}` }); } ``` #### Return A Promise that resolves when the command's metadata have been updated. ## Types ### LaunchContext Represents the passed context object of programmatic command launches. ### LaunchOptions A parameter object used to decide which command should be launched and what data (arguments, context) it should receive. #### IntraExtensionLaunchOptions The options that can be used when launching a command from the same extension. | Property | Description | Type | | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | name\* | Command name as defined in the extension's manifest | `string` | | type\* | LaunchType.UserInitiated or LaunchType.Background | [`LaunchType`](https://developers.raycast.com/environment#launchtype) | | arguments | Optional object for the argument properties and values as defined in the extension's manifest, for example: `{ "argument1": "value1" }` | [`Arguments`](https://developers.raycast.com/information/lifecycle/arguments#arguments) or `null` | | context | Arbitrary object for custom data that should be passed to the command and accessible as LaunchProps; the object must be JSON serializable (Dates and Buffers supported) | [`LaunchContext`](#launchcontext) or `null` | | fallbackText | Optional string to send as fallback text to the command | `string` or `null` | #### InterExtensionLaunchOptions The options that can be used when launching a command from a different extension. | Property | Description | Type | | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | extensionName\* | When launching command from a different extension, the extension name (as defined in the extension's manifest) is necessary | `string` | | name\* | Command name as defined in the extension's manifest | `string` | | ownerOrAuthorName\* | When launching command from a different extension, the owner or author (as defined in the extension's manifest) is necessary | `string` | | type\* | LaunchType.UserInitiated or LaunchType.Background | [`LaunchType`](https://developers.raycast.com/environment#launchtype) | | arguments | Optional object for the argument properties and values as defined in the extension's manifest, for example: `{ "argument1": "value1" }` | [`Arguments`](https://developers.raycast.com/information/lifecycle/arguments#arguments) or `null` | | context | Arbitrary object for custom data that should be passed to the command and accessible as LaunchProps; the object must be JSON serializable (Dates and Buffers supported) | [`LaunchContext`](#launchcontext) or `null` | | fallbackText | Optional string to send as fallback text to the command | `string` or `null` | --- # Source: https://developers.raycast.com/basics/contribute-to-an-extension.md # Contribute to an Extension All published extensions are open-source and can be found in [this repository](https://github.com/raycast/extensions). This makes it easy for multiple developers to collaborate. This guide explains how to import an extension in order to fix a bug, add a new feature or otherwise contribute to it. ## Get source code First, you need to find the source code of the extension. The easiest way to do this is to use the `Fork Extension` action in the Raycast's root search. ![Fork an extension](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-d8248b215be20ea329b284134cce26609fb91dde%2Ffork-extension.webp?alt=media) ## Develop the extension After you have the source code locally, open the Terminal and navigate to the extension's folder. Once there, run `npm install && npm run dev` from the extension folder in your Terminal to start developing the extension. ![Open imported extension](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-d93798084581d66e02d4e51c48bb8c6edd66709d%2Fbasics-open-command.webp?alt=media) ![Icon list command](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-64a3aa26124f0f72eab167839b9858c06e41e84b%2Fbasics-icon-list.webp?alt=media) You should see your forked extension at the top of your root search and can open its commands. When you're done editing the extension, make sure to add yourself to the contributors section of its [manifest](https://developers.raycast.com/information/manifest#extension-properties). If you used the `Fork Extension` action, this should have happened automatically. Additionally, ensure the `CHANGELOG.md` file is updated with your changes; create it if it doesn't exist. Use the `{PR_MERGE_DATE}` placeholder for the date – see the [Version History documentation](https://developers.raycast.com/prepare-an-extension-for-store#version-history) for details. Once everything is ready, see [how to publish an extension](https://developers.raycast.com/basics/publish-an-extension) for instructions on validating and publishing the changes. --- # Source: https://developers.raycast.com/ai/create-an-ai-extension.md # Create an AI Extension To turn your regular extension into an AI-powered one, you need to add a set of tools that allow Raycast AI to interact with your extension. ## Add AI Tools The simplest way to add a tool to your extensions is to open the Manage Extensions command, search for your extension and perform the Add New Tool action via the Action Panel (or press `⌥` `⌘` `T`). ![Add New Tool](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-1be2e53c0b44ea6d5b7b3071c72b9747c88ea1f1%2Fadd-new-tool.webp?alt=media) {% hint style="info" %} Alternatively you can edit the [`package.json` file](https://developers.raycast.com/information/manifest) manually and add a new entry to the `tools` array. {% endhint %} Give the tool a name, a description, and pick a template. The name and description will show up in the UI as well as the Store. The description is passed to AI to help it understand how to use the tool. ## Build Your AI Extension Just like with regular extensions, you need to build your AI Extension. After you've added a tool, switch to your terminal and navigate to your extension directory. Run `npm install && npm run dev` to start the extension in development mode. {% hint style="info" %} `npm run dev` starts the extension in development mode with hot reloading, error reporting and [more](https://developers.raycast.com/information/developer-tools/cli#development). {% endhint %} ## Use Your AI Extension Open Raycast, and you'll notice a new list item saying "Ask ..." at the top of the root search. Press `↵` to open it. From there on, you can chat to your AI Extension. ![AI Extension](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-23cf3dba982add079b5ee80f7cad37b49dee0e13%2Fuse-ai-extension.webp?alt=media) Alternatively, you can open Raycast's AI Chat and start chatting to your AI Extension there. Simply type `@` and start typing the name of your extension. ![AI Chat](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-c8184957f84dd38b6ec5234940c1266a9d7519f9%2Fai-chat.webp?alt=media) 🎉 Congratulations! You built your first AI extension. Now you can start adding more tools to your extension to make it more powerful. --- # Source: https://developers.raycast.com/basics/create-your-first-extension.md # Create Your First Extension ## Create a new extension Open the Create Extension command, name your extension "Hello World" and select the "Detail" template. Pick a parent folder in the Location field and press `⌘` `↵` to continue. ![Create Extension command in Raycast](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-3cf0d0478508eca468e31c4f9ee871ea269d71e4%2Fhello-world.webp?alt=media) {% hint style="info" %} To create a private extension, select your organization in the first dropdown. You need to be logged in and part of an organization to see the dropdown. Learn more about Raycast for Teams [here](https://developers.raycast.com/teams/getting-started). {% endhint %} {% hint style="info" %} To kickstart your extensions, Raycast provides various templates for commands and tools. Learn more [here](https://developers.raycast.com/information/developer-tools/templates). {% endhint %} Next, you'll need to follow the on-screen instructions to build the extension. ## Build the extension Open your terminal, navigate to your extension directory and run `npm install && npm run dev`. Open Raycast, and you'll notice your extension at the top of the root search. Press `↵` to open it. ![Your first extension](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-2927c54a56a87fd95fb0340b9acfcd26f48f0f40%2Fhello-world-2.webp?alt=media) ## Develop your extension To make changes to your extension, open the `./src/index.tsx` file in your extension directory, change the `markdown` text and save it. Then, open your command in Raycast again and see your changes. {% hint style="info" %} `npm run dev` starts the extension in development mode with hot reloading, error reporting and [more](https://developers.raycast.com/information/developer-tools/cli#development). {% endhint %} ## Use your extension Now, you can press `⌃` `C` in your terminal to stop `npm run dev`. The extension stays in Raycast, and you can find its commands in the root when searching for the extension name "Hello World" or the command name "Render Markdown". ![Find your extension in the root search](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-2927c54a56a87fd95fb0340b9acfcd26f48f0f40%2Fhello-world-2.webp?alt=media) 🎉 Congratulations! You built your first extension. Off to many more. {% hint style="info" %} Don't forget to run [`npm run dev`](https://developers.raycast.com/information/developer-tools/cli#development) again when you want to change something in your extension. {% endhint %} --- # Source: https://developers.raycast.com/utilities/functions/createdeeplink.md # createDeeplink Function that creates a deeplink for an extension or script command. ## Signature There are three ways to use the function. The first one is for creating a deeplink to a command inside the current extension: ```ts function createDeeplink(options: { type?: DeeplinkType.Extension, command: string, launchType?: LaunchType, arguments?: LaunchProps["arguments"], fallbackText?: string, }): string; ``` The second one is for creating a deeplink to an extension that is not the current extension: ```ts function createDeeplink(options: { type?: DeeplinkType.Extension, ownerOrAuthorName: string, extensionName: string, command: string, launchType?: LaunchType, arguments?: LaunchProps["arguments"], fallbackText?: string, }): string; ``` The third one is for creating a deeplink to a script command: ```ts function createDeeplink(options: { type: DeeplinkType.ScriptCommand, command: string, arguments?: string[], }): string; ``` ### Arguments #### Extension * `type` is the type of the deeplink. It must be `DeeplinkType.Extension`. * `command` is the name of the command to deeplink to. * `launchType` is the type of the launch. * `arguments` is an object that contains the arguments to pass to the command. * `fallbackText` is the text to show if the command is not available. * For intra-extension deeplinks: * `ownerOrAuthorName` is the name of the owner or author of the extension. * `extensionName` is the name of the extension. #### Script command * `type` is the type of the deeplink. It must be `DeeplinkType.ScriptCommand`. * `command` is the name of the script command to deeplink to. * `arguments` is an array of strings to be passed as arguments to the script command. ### Return Returns a string. ## Example ```tsx import { Action, ActionPanel, LaunchProps, List } from "@raycast/api"; import { createDeeplink, DeeplinkType } from "@raycast/utils"; export default function Command(props: LaunchProps<{ launchContext: { message: string } }>) { console.log(props.launchContext?.message); return ( } /> } /> } /> ); } ``` ## Types ### DeeplinkType A type to denote whether the deeplink is for a script command or an extension. ```ts export enum DeeplinkType { /** A script command */ ScriptCommand = "script-command", /** An extension command */ Extension = "extension", } ``` --- # Source: https://developers.raycast.com/basics/debug-an-extension.md # Debug an Extension Bugs are unavoidable. Therefore it's important to have an easy way to discover and fix them. This guide shows you how to find problems in your extensions. ## Console Use the `console` for simple debugging such as logging variables, function calls, or other helpful messages. All logs are shown in the terminal during [development mode](https://developers.raycast.com/information/developer-tools/cli#development). Here are a few examples: ```typescript console.log("Hello World"); // Prints: Hello World const name = "Thomas"; console.debug(`Hello ${name}`); // Prints: Hello Thomas const error = new Error("Boom 💥"); console.error(error); // Prints: Boom 💥 ``` For more, checkout the [Node.js documentation](https://nodejs.org/docs/latest-v22.x/api/console.html). We automatically disable console logging for store extensions. ## Visual Studio Code For more complex debugging you can install the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=tonka3000.raycast) to be able to attach a node.js debugger to the running Raycast session. 1. Activate your extension in dev mode via `npm run dev` or via the VSCode command `Raycast: Start Development Mode` 2. Start the VSCode command `Raycast: Attach Debugger` 3. Set your breakpoint like in any other node.js base project 4. Activate your command ## Unhandled exceptions and Promise rejections All unhandled exceptions and Promise rejections are shown with an error overlay in Raycast. ![Unhandled exception in development mode](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-b5a9bcaeefa6d23c38427b21408ffd98953722ef%2Fbasics-unhandled-exception.webp?alt=media) During development, we show the stack trace and add an action to jump to the error to make it easy to fix it. In production, only the error message is shown. You should [show a toast](https://developers.raycast.com/api-reference/feedback/toast#showtoast) for all expected errors, e.g. a failing network request. ### Extension Issue Dashboard When unhandled exceptions and Promise rejections occur in the production build of a public extension, Raycast tries to redact all potentially sensitive information they may include, and reports them to our error backend. As an extension author, or as the manager of an organisation, you can view and manage error reports for your public extensions by going to , or by finding your extension in Raycast's root, `Store` command, or `Manage Extensions` command, and using the `View Issues` action. The dashboard should give you an overview of what issues occurred, how many times, how many users were affected, and more. Each issue additionally has a detail view, including a stack trace, breadcrumbs (typically the actions performed before the crash), extension release date, Raycast version, macOS version. ![Extension Issues](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-e3c7a6de170fa071b3e0caf1ac8f93d283255a91%2Fextension-issues.webp?alt=media) ## React Developer Tools We support [React Developer Tools](https://github.com/facebook/react/tree/main/packages/react-devtools) out-of-the-box. Use the tools to inspect and change the props of your React components, and see the results immediately in Raycast. This is especially useful for complex commands with a lot of states. ![React Developer Tools](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-1f048664863696612821b23b146e8b134818b54d%2Fbasics-react-developer-tools.webp?alt=media) To get started, add the `react-devtools` to your extension. Open a terminal, navigate to your extension directory and run the following command: ```typescript npm install --save-dev react-devtools@6.1.1 ``` Then re-build your extension with `npm run dev`, open the command you want to debug in Raycast, and launch the React Developer Tools with `⌘` `⌥` `D`. Now select one of the React components, change a prop in the right sidebar, and hit enter. You'll notice the change immediately in Raycast. ### Alternative: Global installation of React Developer Tools If you prefer to install the `react-devtools` globally, you can do the following: ```bash npm install -g react-devtools@6.1.1 ``` Then you can run `react-devtools` from a terminal to launch the standalone DevTools app. Raycast connects automatically, and you can start debugging your component tree. ## Environments By default, extensions installed from the store run in Node production mode and development extensions in development mode. In development mode, the CLI output shows you additional errors and warnings (e.g. the infamous warning when you're missing the React `key` property for your list items); performance is generally better when running in production mode. You can force development extensions to run in Node production mode by going to Raycast Preferences > Advanced > "Use Node production environment". At runtime, you can check which Node environment you're running in: ```typescript if (process.env.NODE_ENV === "development") { // running in development Node environment } ``` To check whether you're running the store or local development version: ```typescript import { environment } from "@raycast/api"; if (environment.isDevelopment) { // running the development version } ``` --- # Source: https://developers.raycast.com/information/lifecycle/deeplinks.md # Deeplinks Deeplinks are Raycast-specific URLs you can use to launch any command, as long as it's installed and enabled in Raycast. They adhere to the following format: ``` raycast://extensions/// ``` | Name | Description | Type | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | author-or-owner | For store extensions, it's the value of the `owner` or the `author` field in the extension's [manifest](https://developers.raycast.com/information/manifest). For built-in extensions (such as `Calendar`), this is always `raycast`. | `string` | | extension-name | For store extensions, it's the value of the extension's `name` field in the extension's [manifest](https://developers.raycast.com/information/manifest). For built-in extensions (such as `Calendar`), this is the "slugified" extension name; in this case `calendar`. | `string` | | command-name | For store extensions, it's the value of the command's `name` field in the extension's [manifest](https://developers.raycast.com/information/manifest). For built-in commands (such as `My Schedule`), this is the "slugified" command name; in this case `my-schedule`. | `string` | To make fetching a command's Deeplink easier, each command in the Raycast root now has a `Copy Deeplink` action. {% hint style="info" %} Whenever a command is launched using a Deeplink, Raycast will ask you to confirm that you want to run the command. This is to ensure that you are aware of the command you are running. {% endhint %} ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-612ba5e034fc166e1bc0f57d0035cf242bcf0011%2Fdeeplink-confirmation.webp?alt=media) ## Query Parameters | Name | Description | Type | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | | launchType | Runs the command in the background, skipping bringing Raycast to the front. | Either `userInitiated` or `background` | | arguments | If the command accepts [arguments](https://developers.raycast.com/information/lifecycle/arguments), they can be passed using this query parameter. | URL-encoded JSON object. | | context | If the command make use of [LaunchContext](https://developers.raycast.com/api-reference/command#launchcontext), it can be passed using this query parameter. | URL-encoded JSON object. | | fallbackText | Some text to prefill the search bar or first text input of the command | `string` | --- # Source: https://developers.raycast.com/api-reference/user-interface/detail.md # Detail ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-c2161bc51a61a09b4a7aea8a1ed6ab3f65ddd566%2Fdetail.webp?alt=media) ## API Reference ### Detail Renders a markdown ([CommonMark](https://commonmark.org)) string with an optional metadata panel. Typically used as a standalone view or when navigating from a [List](https://developers.raycast.com/api-reference/user-interface/list). #### Example {% tabs %} {% tab title="Render a markdown string" %} ```typescript import { Detail } from "@raycast/api"; export default function Command() { return ; } ``` {% endtab %} {% tab title="Render an image from the assets directory" %} ```typescript import { Detail } from "@raycast/api"; export default function Command() { return ; } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | --------------- | ------------------------------------------------------------------------------ | ----------------- | ------- | | actions | A reference to an ActionPanel. | `React.ReactNode` | - | | isLoading | Indicates whether a loading bar should be shown or hidden below the search bar | `boolean` | - | | markdown | The CommonMark string to be rendered. | `string` | - | | metadata | The `Detail.Metadata` to be rendered in the right side area | `React.ReactNode` | - | | navigationTitle | The main title for that view displayed in Raycast | `string` | - | {% hint style="info" %} You can specify custom image dimensions by adding a `raycast-width` and `raycast-height` query string to the markdown image. For example: `![Image Title](example.png?raycast-width=250&raycast-height=250)` You can also specify a tint color to apply to an markdown image by adding a `raycast-tint-color` query string. For example: `![Image Title](example.png?raycast-tintColor=blue)` {% endhint %} {% hint style="info" %} You can now render [LaTeX](https://www.latex-project.org) in the markdown. We support the following delimiters: * Inline math: `\(...\)` and `\begin{math}...\end{math}` * Display math: `\[...\]`, `$$...$$` and `\begin{equation}...\end{equation}` {% endhint %} ### Detail.Metadata A Metadata view that will be shown in the right-hand-side of the `Detail`. Use it to display additional structured data about the main content shown in the `Detail` view. ![Detail-metadata illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-2ff9dfb88c0b6358b14ffac00c3cff10697634d0%2Fdetail-metadata.webp?alt=media) #### Example ```typescript import { Detail } from "@raycast/api"; // Define markdown here to prevent unwanted indentation. const markdown = ` # Pikachu ![](https://assets.pokemon.com/assets/cms2/img/pokedex/full/025.png) Pikachu that can generate powerful electricity have cheek sacs that are extra soft and super stretchy. `; export default function Main() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ------------------------------------------ | ---------------------------------- | ----------------- | ------- | | children\* | The elements of the Metadata view. | `React.ReactNode` | - | ### Detail.Metadata.Label A single value with an optional icon. ![Detail-metadata-label illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-6fe03ce7bad659317f81b002747ced317b60f20d%2Fdetail-metadata-label.webp?alt=media) #### Example ```typescript import { Detail } from "@raycast/api"; // Define markdown here to prevent unwanted indentation. const markdown = ` # Pikachu ![](https://assets.pokemon.com/assets/cms2/img/pokedex/full/025.png) Pikachu that can generate powerful electricity have cheek sacs that are extra soft and super stretchy. `; export default function Main() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------- | | title\* | The title of the item. | `string` | - | | icon | An icon to illustrate the value of the item. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | text | The text value of the item. Specifying `color` will display the text in the provided color. Defaults to Color.PrimaryText. | `string` or `{ color?:` [`Color`](https://developers.raycast.com/api-reference/colors#color)`; value: string }` | - | ### Detail.Metadata.Link An item to display a link. ![Detail-metadata-link illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-08ab8450b22fd002ea39ef5ff9a30f4f25f35861%2Fdetail-metadata-link.webp?alt=media) #### Example ```typescript import { Detail } from "@raycast/api"; // Define markdown here to prevent unwanted indentation. const markdown = ` # Pikachu ![](https://assets.pokemon.com/assets/cms2/img/pokedex/full/025.png) Pikachu that can generate powerful electricity have cheek sacs that are extra soft and super stretchy. `; export default function Main() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ---------------------------------------- | ------------------------------- | -------- | ------- | | target\* | The target of the link. | `string` | - | | text\* | The text value of the item. | `string` | - | | title\* | The title shown above the item. | `string` | - | ### Detail.Metadata.TagList A list of [`Tags`](#detail.metadata.taglist.item) displayed in a row. ![Detail-metadata-taglist illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-6a0a834fb8f2b9b715b7c38797c3abb3c2f0a4c5%2Fdetail-metadata-taglist.webp?alt=media) #### Example ```typescript import { Detail } from "@raycast/api"; // Define markdown here to prevent unwanted indentation. const markdown = ` # Pikachu ![](https://assets.pokemon.com/assets/cms2/img/pokedex/full/025.png) Pikachu that can generate powerful electricity have cheek sacs that are extra soft and super stretchy. `; export default function Main() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | ------------------------------------------ | ---------------------------------- | ----------------- | ------- | | children\* | The tags contained in the TagList. | `React.ReactNode` | - | | title\* | The title shown above the item. | `string` | - | ### Detail.Metadata.TagList.Item A Tag in a `Detail.Metadata.TagList`. #### Props | Prop | Description | Type | Default | | -------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | color | Changes the text color to the provided color and sets a transparent background with the same color. | [`Color.ColorLike`](https://developers.raycast.com/api-reference/colors#color.colorlike) | - | | icon | The optional icon tag icon. Required if the tag has no text. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onAction | Callback that is triggered when the item is clicked. | `() => void` | - | | text | The optional tag text. Required if the tag has no icon. | `string` | - | ### Detail.Metadata.Separator A metadata item that shows a separator line. Use it for grouping and visually separating metadata items. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-869ac40f387d81357bf011d3717ab975cc5645d9%2Fdetail-metadata-separator.webp?alt=media) ```typescript import { Detail } from "@raycast/api"; // Define markdown here to prevent unwanted indentation. const markdown = ` # Pikachu ![](https://assets.pokemon.com/assets/cms2/img/pokedex/full/025.png) Pikachu that can generate powerful electricity have cheek sacs that are extra soft and super stretchy. `; export default function Main() { return ( } /> ); } ``` --- # Source: https://developers.raycast.com/information/developer-tools.md # Developer Tools Raycast provides several tools to smoothen your experience when building extensions: * [Manage Extensions Command](https://developers.raycast.com/information/developer-tools/manage-extensions-command) *- A Raycast command to manage your extensions, add new command, etc.* * [CLI](https://developers.raycast.com/information/developer-tools/cli) *- A CLI to build, develop, and lint your extension* * [ESLint](https://developers.raycast.com/information/developer-tools/eslint) *- An ESLint configuration helping you follow best practices as you build your extension* * [Forked Extensions (community tool)](https://developers.raycast.com/information/developer-tools/forked-extensions) - *The extension for helping you manage your forked Raycast extensions* * [VS Code (community tool)](https://developers.raycast.com/information/developer-tools/vscode) *- A VS Code extension to enhance your development experience* --- # Source: https://developers.raycast.com/examples/doppler.md # Doppler Share Secrets {% hint style="info" %} The full source code of the example can be found [here](https://github.com/raycast/extensions/tree/main/extensions/doppler-share-secrets#readme). You can install the extension [here](https://www.raycast.com/thomas/doppler-share-secrets). {% endhint %} In this example we use a form to collect inputs from a user. To make it interesting, we use [Doppler](http://share.doppler.com) which is a service to make it easy to securely share sensitive information such as API keys or passwords. ![Example: Safely share secrets with Doppler](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-b36017e7ee6ad8162b27712895ac0ac036103b4d%2Fexample-doppler-share-secrets.webp?alt=media) The extension has multiple commands. In this example we're using a simple form with a textfield for the secret, a dropdown for an expiration after views and a second dropdown for an alternate expiration after a maximum of days. ## Add form items First, we render the static form. For this we add all the mentioned form items: ```typescript import { Action, ActionPanel, Clipboard, Form, Icon, showToast, Toast } from "@raycast/api"; import got from "got"; export default function Command() { return (
); } ``` Both dropdowns set the `storeValue` to true. This restores the last selected value when the command is opened again. This option is handy when your users select the same options often. In this case, we assume that users want to keep the expiration settings persisted. ## Submit form values Now that we have the form, we want to collect the inserted values, send them to Doppler and copy the URL that allows us to share the information securely. For this, we create a new action: ```tsx function ShareSecretAction() { async function handleSubmit(values: { secret: string; expireViews: number; expireDays: number }) { if (!values.secret) { showToast({ style: Toast.Style.Failure, title: "Secret is required", }); return; } const toast = await showToast({ style: Toast.Style.Animated, title: "Sharing secret", }); try { const { body } = await got.post("https://api.doppler.com/v1/share/secrets/plain", { json: { secret: values.secret, expire_views: values.expireViews, expire_days: values.expireDays, }, responseType: "json", }); await Clipboard.copy((body as any).authenticated_url); toast.style = Toast.Style.Success; toast.title = "Shared secret"; toast.message = "Copied link to clipboard"; } catch (error) { toast.style = Toast.Style.Failure; toast.title = "Failed sharing secret"; toast.message = String(error); } } return ; } ``` Let's break this down: * The `` returns an [``](https://developers.raycast.com/api-reference/user-interface/actions#action.submitform). * The `handleSubmit()` gets called when the form is submitted with its values. * First we check if the user entered a secret. If not, we show a toast. * Then we show a toast to hint that there is a network call in progress to share the secret. * We call [Doppler's API](https://docs.doppler.com/reference/share-secret) with the form values * If the network response succeeds, we copy the authenticated URL to the clipboard and show a success toast. * If the network response fails, we show a failure toast with additional information about the failure. ## Wire it up The last step is to add the `` to the form: ```typescript import { Action, ActionPanel, Clipboard, Form, Icon, showToast, Toast } from "@raycast/api"; import got from "got"; export default function Command() { return (
} > ); } function ShareSecretAction() { async function handleSubmit(values: { secret: string; expireViews: number; expireDays: number }) { if (!values.secret) { showToast({ style: Toast.Style.Failure, title: "Secret is required", }); return; } const toast = await showToast({ style: Toast.Style.Animated, title: "Sharing secret", }); try { const { body } = await got.post("https://api.doppler.com/v1/share/secrets/plain", { json: { secret: values.secret, expire_views: values.expireViews, expire_days: values.expireDays, }, responseType: "json", }); await Clipboard.copy((body as any).authenticated_url); toast.style = Toast.Style.Success; toast.title = "Shared secret"; toast.message = "Copied link to clipboard"; } catch (error) { toast.style = Toast.Style.Failure; toast.title = "Failed sharing secret"; toast.message = String(error); } } return ; } ``` And there you go. A simple form to enter a secret and get a URL that you can share with others that will "destroy itself" accordingly to your preferences. As next steps, you could use the `` to paste the link directly to front-most application or add another action that clears the form and let's you create another shareable link. --- # Source: https://developers.raycast.com/api-reference/environment.md # Environment The Environment APIs are useful to get context about the setup in which your command runs. You can get information about the extension and command itself as well as Raycast. Furthermore, a few paths are injected and are helpful to construct file paths that are related to the command's assets. ## API Reference ### environment Contains environment values such as the Raycast version, extension info, and paths. #### Example ```typescript import { environment } from "@raycast/api"; export default async function Command() { console.log(`Raycast version: ${environment.raycastVersion}`); console.log(`Owner or Author name: ${environment.ownerOrAuthorName}`); console.log(`Extension name: ${environment.extensionName}`); console.log(`Command name: ${environment.commandName}`); console.log(`Command mode: ${environment.commandMode}`); console.log(`Assets path: ${environment.assetsPath}`); console.log(`Support path: ${environment.supportPath}`); console.log(`Is development mode: ${environment.isDevelopment}`); console.log(`Appearance: ${environment.appearance}`); console.log(`Text size: ${environment.textSize}`); console.log(`LaunchType: ${environment.launchType}`); } ``` #### Properties | Property | Description | Type | | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | appearance\* | The appearance used by the Raycast application. | `"dark"` or `"light"` | | assetsPath\* | The absolute path to the assets directory of the extension. | `string` | | commandMode\* | The mode of the launched command, as specified in package.json | `"view"` or `"no-view"` or `"menu-bar"` | | commandName\* | The name of the launched command, as specified in package.json | `string` | | extensionName\* | The name of the extension, as specified in package.json | `string` | | isDevelopment\* | Indicates whether the command is a development command (vs. an installed command from the Store). | `boolean` | | launchType\* | The type of launch for the command (user initiated or background). | [`LaunchType`](#launchtype) | | ownerOrAuthorName\* | The name of the extension owner (if any) or author, as specified in package.json | `string` | | raycastVersion\* | The version of the main Raycast app | `string` | | supportPath\* | The absolute path for the support directory of an extension. Use it to read and write files related to your extension or command. | `string` | | textSize\* | The text size used by the Raycast application. | `"medium"` or `"large"` | | canAccess\* | | `(api: unknown) => boolean` | ### environment.canAccess Checks whether the user can access a specific API or not. #### Signature ```typescript function canAccess(api: any): bool; ``` #### Example ```typescript import { AI, showHUD, environment } from "@raycast/api"; import fs from "fs"; export default async function main() { if (environment.canAccess(AI)) { const answer = await AI.ask("Suggest 5 jazz songs"); await Clipboard.copy(answer); } else { await showHUD("You don't have access :("); } } ``` #### Return A Boolean indicating whether the user running the command has access to the API. ### getSelectedFinderItems Gets the selected items from Finder. #### Signature ```typescript async function getSelectedFinderItems(): Promise; ``` #### Example ```typescript import { getSelectedFinderItems, showToast, Toast } from "@raycast/api"; export default async function Command() { try { const selectedItems = await getSelectedFinderItems(); console.log(selectedItems); } catch (error) { await showToast({ style: Toast.Style.Failure, title: "Cannot copy file path", message: String(error), }); } } ``` #### Return A Promise that resolves with the [selected file system items](#filesystemitem). If Finder is not the frontmost application, the promise will be rejected. ### getSelectedText Gets the selected text of the frontmost application. #### Signature ```typescript async function getSelectedText(): Promise; ``` #### Example ```typescript import { getSelectedText, Clipboard, showToast, Toast } from "@raycast/api"; export default async function Command() { try { const selectedText = await getSelectedText(); const transformedText = selectedText.toUpperCase(); await Clipboard.paste(transformedText); } catch (error) { await showToast({ style: Toast.Style.Failure, title: "Cannot transform text", message: String(error), }); } } ``` #### Return A Promise that resolves with the selected text. If no text is selected in the frontmost application, the promise will be rejected. ## Types ### FileSystemItem Holds data about a File System item. Use the [getSelectedFinderItems](#getselectedfinderitems) method to retrieve values. #### Properties | Property | Description | Type | | -------------------------------------- | -------------------- | -------- | | path\* | The path to the item | `string` | ### LaunchType Indicates the type of command launch. Use this to detect whether the command has been launched from the background. #### Enumeration members | Name | Description | | ------------- | ---------------------------------------------------------- | | UserInitiated | A regular launch through user interaction | | Background | Scheduled through an interval and launched from background | --- # Source: https://developers.raycast.com/information/developer-tools/eslint.md # ESLint Raycast makes it easy to lint your extensions using the CLI's lint command (`ray lint`). Raycast provides by default an [opinionated ESLint configuration](https://github.com/raycast/eslint-config/blob/main/index.js) that includes everything you need to lint your Raycast extensions. The default configuration is as simple as this: ```js const { defineConfig } = require("eslint/config"); const raycastConfig = require("@raycast/eslint-config"); module.exports = defineConfig([...raycastConfig]); ``` It abstracts away the different ESLint dependencies used for Raycast extensions and includes different rule-sets. It also includes Raycast's own ESLint plugin rule-set that makes it easier for you to follow best practices when building extension. For example, there's a [rule](https://github.com/raycast/eslint-plugin/blob/main/docs/rules/prefer-title-case.md) helping you follow the Title Case convention for `Action` components. You can check Raycast's ESLint plugin rules directly on the [repository documentation](https://github.com/raycast/eslint-plugin#rules). ## Customization You're free to turn on/off rules or add new plugins as you see fit for your extensions. For example, you could add the rule [`@raycast/prefer-placeholders`](https://github.com/raycast/eslint-plugin/blob/main/docs/rules/prefer-placeholders.md) for your extension: ```js const { defineConfig } = require("eslint/config"); const raycastConfig = require("@raycast/eslint-config"); module.exports = defineConfig([ ...raycastConfig, { rules: { "@raycast/prefer-placeholders": "warn", }, }, ]); ``` To keep the consistency of development experiences across extensions, we don't encourage adding too many personal ESLint preferences to an extension. ## Migration Starting with version 1.48.8, the ESLint configuration is included automatically when creating a new extension using the `Create Extension` command. If your extension was created before this version, you can migrate following the steps outlined on the [v1.48.8](https://developers.raycast.com/migration/v1.48.8) page. --- # Source: https://developers.raycast.com/utilities/functions/executesql.md # executeSQL A function that executes a SQL query on a local SQLite database and returns the query result in JSON format. ## Signature ```ts function executeSQL(databasePath: string, query: string): Promise ``` ### Arguments * `databasePath` is the path to the local SQL database. * `query` is the SQL query to run on the database. ### Return Returns a `Promise` that resolves to an array of objects representing the query results. ## Example ```typescript import { closeMainWindow, Clipboard } from "@raycast/api"; import { executeSQL } from "@raycast/utils"; type Message = { body: string; code: string }; const DB_PATH = "/path/to/chat.db"; export default async function Command() { const query = ` SELECT body, code FROM message ORDER BY date DESC LIMIT 1; `; const messages = await executeSQL(DB_PATH, query); if (messages.length > 0) { const latestCode = messages[0].code; await Clipboard.paste(latestCode); await closeMainWindow(); } } ``` --- # Source: https://developers.raycast.com/misc/faq.md # FAQ
What's the difference between script commands and extensions? Script commands were the first way to extend Raycast. They are a simple way to execute a shell script and show some limited output in Raycast. Extensions are our next iteration to extend Raycast. While scripts can be written in pretty much any scripting language, extensions are written in TypeScript. They can show rich user interfaces like lists and forms but can also be "headless" and just run a simple script. Extensions can be shared with our community via our Store. This makes them easy to discover and use for not so technical folks that don't have homebrew or other shell integrations on their Mac.
Why can I not use react-dom? Even though you write JS/TS code, everything is rendered natively in Raycast. There isn't any HTML or CSS involved. Therefore you don't need the DOM-specific methods that the `react-dom` package provides. Instead, we implemented a custom [reconciler](https://reactjs.org/docs/reconciliation.html) that converts your React component tree to a render tree that Raycast understands. The render tree is used natively to construct a view hierarchy that is backed by [Apple's AppKit](https://developer.apple.com/documentation/appkit/). This is similar to how [React Native](https://reactnative.dev) works.
Can I import ESM packages in my extension? Yes, but you need to convert your extension to ESM. Quick steps: * Make sure you are using TypeScript 4.7 or later. * Add `"type": "module"` to your package.json. * Add `"module": "node16", "moduleResolution": "node16"` to your tsconfig.json. * Use only full relative file paths for imports: `import x from '.';` → `import x from './index.js';`. * Remove `namespace` usage and use `export` instead. * Use the [`node:` protocol](https://nodejs.org/api/esm.html#esm_node_imports) for Node.js built-in imports. * **You must use a `.js` extension in relative imports even though you're importing `.ts` files.**
--- # Source: https://developers.raycast.com/api-reference/feedback.md # Feedback Raycast has several ways to provide feedback to the user: * [Toast](https://developers.raycast.com/api-reference/feedback/toast) *- when an asynchronous operation is happening or when an error is thrown* * [HUD](https://developers.raycast.com/api-reference/feedback/hud) *- to confirm an action worked after closing Raycast* * [Alert](https://developers.raycast.com/api-reference/feedback/alert) *- to ask for confirmation before taking an action* ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-607b51d1dc3e47c6cdff37be217a0e4d42368a57%2Ftoast.webp?alt=media) --- # Source: https://developers.raycast.com/information/file-structure.md # File Structure An extension consists of at least an entry point file (e.g. `src/index.ts`) and a `package.json` manifest file. We add a few more support files when scaffolding an extension to streamline development with modern JavaScript tooling. The typical directory structure of a newly created extension looks like this: ```bash extension ├── .prettierrc ├── assets │ └── icon.png ├── eslint.config.js ├── node_modules ├── package-lock.json ├── package.json ├── src │ ├── command.tsx └── tsconfig.json ``` The directory contains all source files, assets, and a few support files. Let's go over each of them: ## Sources Put all your source files into the `src` folder. We recommend using TypeScript as a programming language. Our API is fully typed, which helps you catch errors at compile time rather than runtime. `ts`, `tsx`, `js` and `jsx` are supported as file extensions. As a rule of thumb, use `tsx` or `jsx` for commands with a UI. An extension consists of at least an entry point file (e.g. `src/command.ts`) per command and a `package.json` manifest file holding metadata about the extension, its commands, and its tools. The format of the manifest file is very similar to [that of npm packages](https://docs.npmjs.com/cli/v7/configuring-npm/package-json). In addition to some of the standard properties, there are some [additional properties](https://developers.raycast.com/information/manifest), in particular, the `commands` properties which describes the entry points exposed by the extension. Each command has a property `name` that maps to its main entry point file in the `src` folder. For example, a command with the name `create` in the `package.json` file, maps to the file `src/create{.ts,.tsx,.js,.jsx}`. ## Assets The optional `assets` folder can contain icons that will be packaged into the extension archive. All bundled assets can be referenced at runtime. Additionally, icons can be used in the `package.json` as extension or command icons. ## Support files The directory contains a few more files that setup common JavaScript tooling: * **eslint.config.js** describes rules for [ESLint](https://eslint.org), which you can run with `npm run lint`. It has recommendations for code style and best practices. Usually, you don't have to edit this file. * **.prettierrc** contains default rules for [Prettier](https://prettier.io) to format your code. We recommend to setup the [VS Code extension](https://prettier.io/docs/en/editors.html#visual-studio-code) to keep your code pretty automatically. * **node\_modules** contains all installed dependencies. You shouldn't make any manual changes to this folder. * **package-lock.json** is a file generated by npm to install your dependencies. You shouldn't make any manual changes to this file. * **package.json** is the [manifest file](https://developers.raycast.com/information/manifest) containing metadata about your extension such as its title, the commands, and its dependencies. * **tsconfig.json** configures your project to use TypeScript. Most likely, you don't have to edit this file. --- # Source: https://developers.raycast.com/ai/follow-best-practices-for-ai-extensions.md # Follow Best Practices for AI Extensions Working with LLMs can be tricky. Here are some best practices to make the most out of your AI Extension. 1. Use [Confirmations](https://developers.raycast.com/learn-core-concepts-of-ai-extensions#confirmations) to keep the human in the loop. You can use them dynamically based on the user's input. For example, you might ask for confirmation if moving a file would overwrite an existing file but not if it would create a new file. 2. Write [Evals](https://developers.raycast.com/ai/write-evals-for-your-ai-extension) for common use-cases to test your AI Extension and provide users with suggested prompts. 3. Include information in your tool's input on how to format parameters like IDs or dates. For example, you might mention that a date should be provided in ISO 8601 format. 4. Include information in your tool's input on how to get the required parameters. For example, you want to teach AI how to get a team ID that is required to create a new issue. --- # Source: https://developers.raycast.com/information/developer-tools/forked-extensions.md # Forked Extensions (community tool) This extension leverages the [Git sparse-checkout](https://git-scm.com/docs/git-sparse-checkout) feature to efficiently manage your forked extensions. Our goal is to eliminate the need for cloning the entire repository, which can exceed 20 GB in size, by enabling sparse-checkout. With this extension, you can forgo Ray CLI's commands, allowing you to use Git commands directly and regular [GitHub flow](https://docs.github.com/en/get-started/using-github/github-flow) for managing your extensions. ## Features * Explore full extension list * Only fork the extension you need to save space * Remove an extension from forked list * Synchronize the forked repository with the upstream repository on local ## Install The extension is available on the [Raycast Store](https://www.raycast.com/litomore/forked-extensions).

## Hint Please note with this extension you no longer need to use Ray CLI's `pull-contributions` and `publish` commands. Just use Git commands or your favorite Git GUI tool to manage your forked extensions. --- # Source: https://developers.raycast.com/api-reference/user-interface/form.md # Form Our `Form` component provides great user experience to collect some data from a user and submit it for extensions needs. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-b36017e7ee6ad8162b27712895ac0ac036103b4d%2Fexample-doppler-share-secrets.webp?alt=media) ## Two Types of Items: Controlled vs. Uncontrolled Items in React can be one of two types: controlled or uncontrolled. An uncontrolled item is the simpler of the two. It's the closest to a plain HTML input. React puts it on the page, and Raycast keeps track of the rest. Uncontrolled inputs require less code, but make it harder to do certain things. With a controlled item, YOU explicitly control the `value` that the item displays. You have to write code to respond to changes with defining `onChange` callback, store the current `value` somewhere, and pass that value back to the item to be displayed. It's a feedback loop with your code in the middle. It's more manual work to wire these up, but they offer the most control. You can take look at these two styles below under each of the supported items. ## Validation Before submitting data, it is important to ensure all required form controls are filled out, in the correct format. In Raycast, validation can be fully controlled from the API. To keep the same behavior as we have natively, the proper way of usage is to validate a `value` in the `onBlur` callback, update the `error` of the item and keep track of updates with the `onChange` callback to drop the `error` value. The [useForm](https://developers.raycast.com/utilities/react-hooks/useform) utils hook nicely wraps this behavior and is the recommended way to do deal with validations. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-06326f9b2b6de5d39b9c8aa37e677b23c122f26e%2Fform-validation.webp?alt=media) {% hint style="info" %} Keep in mind that if the Form has any errors, the [`Action.SubmitForm`](https://developers.raycast.com/api-reference/actions#action.submitform) `onSubmit` callback won't be triggered. {% endhint %} #### Example {% tabs %} {% tab title="FormValidationWithUtils.tsx" %} ```tsx import { Action, ActionPanel, Form, showToast, Toast } from "@raycast/api"; import { useForm, FormValidation } from "@raycast/utils"; interface SignUpFormValues { name: string; password: string; } export default function Command() { const { handleSubmit, itemProps } = useForm({ onSubmit(values) { showToast({ style: Toast.Style.Success, title: "Yay!", message: `${values.name} account created`, }); }, validation: { name: FormValidation.Required, password: (value) => { if (value && value.length < 8) { return "Password must be at least 8 symbols"; } else if (!value) { return "The item is required"; } }, }, }); return (
} > ); } ``` {% endtab %} {% tab title="FormValidationWithoutUtils.tsx" %} ```typescript import { Form } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [nameError, setNameError] = useState(); const [passwordError, setPasswordError] = useState(); function dropNameErrorIfNeeded() { if (nameError && nameError.length > 0) { setNameError(undefined); } } function dropPasswordErrorIfNeeded() { if (passwordError && passwordError.length > 0) { setPasswordError(undefined); } } return (
{ if (event.target.value?.length == 0) { setNameError("The field should't be empty!"); } else { dropNameErrorIfNeeded(); } }} /> { const value = event.target.value; if (value && value.length > 0) { if (!validatePassword(value)) { setPasswordError("Password should be at least 8 characters!"); } else { dropPasswordErrorIfNeeded(); } } else { setPasswordError("The field should't be empty!"); } }} /> ); } function validatePassword(value: string): boolean { return value.length >= 8; } ``` {% endtab %} {% endtabs %} ## Drafts Drafts are a mechanism to preserve filled-in inputs (but not yet submitted) when an end-user exits the command. To enable this mechanism, set the `enableDrafts` prop on your Form and populate the initial values of the Form with the [top-level prop `draftValues`](https://developers.raycast.com/information/lifecycle#launchprops). ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-a71310d2af9ec5e3b3e443bbf6f11ffc067adf52%2Fform-drafts.webp?alt=media) {% hint style="info" %} * Drafts for forms nested in navigation are not supported yet. In this case, you will see a warning about it. * Drafts won't preserve the [`Form.Password`](#form.passwordfield)'s values. * Drafts will be dropped once [`Action.SubmitForm`](https://developers.raycast.com/api-reference/actions#action.submitform) is triggered. * If you call [`popToRoot()`](https://developers.raycast.com/window-and-search-bar#poptoroot), drafts won't be preserved or updated. {% endhint %} #### Example {% tabs %} {% tab title="Uncontrolled Form" %} ```typescript import { Form, ActionPanel, Action, popToRoot, LaunchProps } from "@raycast/api"; interface TodoValues { title: string; description?: string; dueDate?: Date; } export default function Command(props: LaunchProps<{ draftValues: TodoValues }>) { const { draftValues } = props; return (
{ console.log("onSubmit", values); popToRoot(); }} /> } > ); } ``` {% endtab %} {% tab title="Controlled Form" %} ```typescript import { Form, ActionPanel, Action, popToRoot, LaunchProps } from "@raycast/api"; import { useState } from "react"; interface TodoValues { title: string; description?: string; dueDate?: Date; } export default function Command(props: LaunchProps<{ draftValues: TodoValues }>) { const { draftValues } = props; const [title, setTitle] = useState(draftValues?.title || ""); const [description, setDescription] = useState(draftValues?.description || ""); const [dueDate, setDueDate] = useState(draftValues?.dueDate || null); return (
{ console.log("onSubmit", values); popToRoot(); }} /> } > ); } ``` {% endtab %} {% endtabs %} ## API Reference ### Form Shows a list of form items such as [Form.TextField](#form.textfield), [Form.Checkbox](#form.checkbox) or [Form.Dropdown](#form.dropdown). Optionally add a [Form.LinkAccessory](#form.linkaccessory) in the right-hand side of the navigation bar. #### Props | Prop | Description | Type | Default | | ------------------ | ----------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------- | | actions | A reference to an ActionPanel. | `React.ReactNode` | - | | children | The Form.Item elements of the form. | `React.ReactNode` | - | | enableDrafts | Defines whether the Form.Items values will be preserved when user exits the screen. | `boolean` | - | | isLoading | Indicates whether a loading bar should be shown or hidden below the search bar | `boolean` | - | | navigationTitle | The main title for that view displayed in Raycast | `string` | - | | searchBarAccessory | Form.LinkAccessory that will be shown in the right-hand-side of the search bar. | `ReactElement<`[`Form.LinkAccessory.Props`](#props)`, string>` | - | ### Form.TextField A form item with a text field for input. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-e0d079518d5780a4b1c84d08642a4349bbc5e68d%2Fform-textfield.webp?alt=media) #### Example {% tabs %} {% tab title="Uncontrolled text field" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` {% endtab %} {% tab title="Controlled text field" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [name, setName] = useState(""); return (
console.log(values)} /> } > ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------- | | id\* | ID of the form item. Make sure to assign each form item a unique id. | `string` | - | | autoFocus | Indicates whether the item should be focused automatically once the form is rendered. | `boolean` | - | | defaultValue | The default value of the item. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. If you're using `storeValue` and configured it as `true` then the stored value will be set. If you configure `value` at the same time with `defaultValue`, the `value` will be set instead of `defaultValue`. | `string` | - | | error | An optional error message to show the form item validation issues. If the `error` is present, the Form Item will be highlighted with red border and will show an error message on the right. | `string` | - | | info | An optional info message to describe the form item. It appears on the right side of the item with an info icon. When the icon is hovered, the info message is shown. | `string` | - | | onBlur | The callback that will be triggered when the item loses its focus. | `(event: FormEvent) => void` | - | | onChange | The callback which will be triggered when the `value` of the item changes. | `(newValue: string) => void` | - | | onFocus | The callback which will be triggered should be called when the item is focused. | `(event: FormEvent) => void` | - | | placeholder | Placeholder text shown in the text field. | `string` | - | | storeValue | Indicates whether the value of the item should be persisted after submitting, and restored next time the form is rendered. | `boolean` | - | | title | The title displayed on the left side of the item. | `string` | - | | value | The current value of the item. | `string` | - | #### Methods (Imperative API) | Name | Signature | Description | | ----- | ------------ | -------------------------------------------------------------------------- | | focus | `() => void` | Makes the item request focus. | | reset | `() => void` | Resets the form item to its initial value, or `defaultValue` if specified. | ### Form.PasswordField A form item with a secure text field for password-entry in which the entered characters must be kept secret. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-70aadaea0c392c1dc97d0797f2f1d7b8e4d439ed%2Fform-password.webp?alt=media) #### Example {% tabs %} {% tab title="Uncontrolled password field" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` {% endtab %} {% tab title="Controlled password field" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [password, setPassword] = useState(""); return (
console.log(values)} /> } > ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------- | | id\* | ID of the form item. Make sure to assign each form item a unique id. | `string` | - | | autoFocus | Indicates whether the item should be focused automatically once the form is rendered. | `boolean` | - | | defaultValue | The default value of the item. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. If you're using `storeValue` and configured it as `true` then the stored value will be set. If you configure `value` at the same time with `defaultValue`, the `value` will be set instead of `defaultValue`. | `string` | - | | error | An optional error message to show the form item validation issues. If the `error` is present, the Form Item will be highlighted with red border and will show an error message on the right. | `string` | - | | info | An optional info message to describe the form item. It appears on the right side of the item with an info icon. When the icon is hovered, the info message is shown. | `string` | - | | onBlur | The callback that will be triggered when the item loses its focus. | `(event: FormEvent) => void` | - | | onChange | The callback which will be triggered when the `value` of the item changes. | `(newValue: string) => void` | - | | onFocus | The callback which will be triggered should be called when the item is focused. | `(event: FormEvent) => void` | - | | placeholder | Placeholder text shown in the password field. | `string` | - | | storeValue | Indicates whether the value of the item should be persisted after submitting, and restored next time the form is rendered. | `boolean` | - | | title | The title displayed on the left side of the item. | `string` | - | | value | The current value of the item. | `string` | - | #### Methods (Imperative API) | Name | Signature | Description | | ----- | ------------ | -------------------------------------------------------------------------- | | focus | `() => void` | Makes the item request focus. | | reset | `() => void` | Resets the form item to its initial value, or `defaultValue` if specified. | ### Form.TextArea A form item with a text area for input. The item supports multiline text entry. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-b86a65278162fe68c8661034a239d039a2e447cc%2Fform-textarea.webp?alt=media) #### Example {% tabs %} {% tab title="Uncontrolled text area" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; const DESCRIPTION = "We spend too much time staring at loading indicators. The Raycast team is dedicated to make everybody interact faster with their computers."; export default function Command() { return (
console.log(values)} /> } > ); } ``` {% endtab %} {% tab title="Controlled text area" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [description, setDescription] = useState(""); return (
console.log(values)} /> } > ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------- | | id\* | ID of the form item. Make sure to assign each form item a unique id. | `string` | - | | autoFocus | Indicates whether the item should be focused automatically once the form is rendered. | `boolean` | - | | defaultValue | The default value of the item. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. If you're using `storeValue` and configured it as `true` then the stored value will be set. If you configure `value` at the same time with `defaultValue`, the `value` will be set instead of `defaultValue`. | `string` | - | | enableMarkdown | Whether markdown will be highlighted in the TextArea or not. When enabled, markdown shortcuts starts to work for the TextArea (pressing `⌘ + B` will add `**bold**` around the selected text, `⌘ + I` will make the selected text italic, etc.) | `boolean` | - | | error | An optional error message to show the form item validation issues. If the `error` is present, the Form Item will be highlighted with red border and will show an error message on the right. | `string` | - | | info | An optional info message to describe the form item. It appears on the right side of the item with an info icon. When the icon is hovered, the info message is shown. | `string` | - | | onBlur | The callback that will be triggered when the item loses its focus. | `(event: FormEvent) => void` | - | | onChange | The callback which will be triggered when the `value` of the item changes. | `(newValue: string) => void` | - | | onFocus | The callback which will be triggered should be called when the item is focused. | `(event: FormEvent) => void` | - | | placeholder | Placeholder text shown in the text area. | `string` | - | | storeValue | Indicates whether the value of the item should be persisted after submitting, and restored next time the form is rendered. | `boolean` | - | | title | The title displayed on the left side of the item. | `string` | - | | value | The current value of the item. | `string` | - | #### Methods (Imperative API) | Name | Signature | Description | | ----- | ------------ | -------------------------------------------------------------------------- | | focus | `() => void` | Makes the item request focus. | | reset | `() => void` | Resets the form item to its initial value, or `defaultValue` if specified. | ### Form.Checkbox A form item with a checkbox. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-ef18048374669510de660607b9c0e368c07a00c1%2Fform-checkbox.webp?alt=media) #### Example {% tabs %} {% tab title="Uncontrolled checkbox" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` {% endtab %} {% tab title="Controlled checkbox" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [checked, setChecked] = useState(true); return (
console.log(values)} /> } > ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ------- | | id\* | ID of the form item. Make sure to assign each form item a unique id. | `string` | - | | label\* | The label displayed on the right side of the checkbox. | `string` | - | | autoFocus | Indicates whether the item should be focused automatically once the form is rendered. | `boolean` | - | | defaultValue | The default value of the item. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. If you're using `storeValue` and configured it as `true` then the stored value will be set. If you configure `value` at the same time with `defaultValue`, the `value` will be set instead of `defaultValue`. | `boolean` | - | | error | An optional error message to show the form item validation issues. If the `error` is present, the Form Item will be highlighted with red border and will show an error message on the right. | `string` | - | | info | An optional info message to describe the form item. It appears on the right side of the item with an info icon. When the icon is hovered, the info message is shown. | `string` | - | | onBlur | The callback that will be triggered when the item loses its focus. | `(event: FormEvent) => void` | - | | onChange | The callback which will be triggered when the `value` of the item changes. | `(newValue: boolean) => void` | - | | onFocus | The callback which will be triggered should be called when the item is focused. | `(event: FormEvent) => void` | - | | storeValue | Indicates whether the value of the item should be persisted after submitting, and restored next time the form is rendered. | `boolean` | - | | title | The title displayed on the left side of the item. | `string` | - | | value | The current value of the item. | `boolean` | - | #### Methods (Imperative API) | Name | Signature | Description | | ----- | ------------ | -------------------------------------------------------------------------- | | focus | `() => void` | Makes the item request focus. | | reset | `() => void` | Resets the form item to its initial value, or `defaultValue` if specified. | ### Form.DatePicker A form item with a date picker. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-e2b50c8a42be98887a58a493470d5007543befda%2Fform-datepicker.webp?alt=media) #### Example {% tabs %} {% tab title="Uncontrolled date picker" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` {% endtab %} {% tab title="Controlled date picker" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [date, setDate] = useState(null); return (
console.log(values)} /> } > ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------- | | id\* | ID of the form item. Make sure to assign each form item a unique id. | `string` | - | | autoFocus | Indicates whether the item should be focused automatically once the form is rendered. | `boolean` | - | | defaultValue | The default value of the item. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. If you're using `storeValue` and configured it as `true` then the stored value will be set. If you configure `value` at the same time with `defaultValue`, the `value` will be set instead of `defaultValue`. | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) | - | | error | An optional error message to show the form item validation issues. If the `error` is present, the Form Item will be highlighted with red border and will show an error message on the right. | `string` | - | | info | An optional info message to describe the form item. It appears on the right side of the item with an info icon. When the icon is hovered, the info message is shown. | `string` | - | | max | The maximum date (inclusive) allowed for selection. - If the PickDate type is `Type.Date`, only the full day date will be considered for comparison, ignoring the time components of the Date object. - If the PickDate type is `Type.DateTime`, both date and time components will be considered for comparison. The date should be a JavaScript Date object. | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) | - | | min | The minimum date (inclusive) allowed for selection. - If the PickDate type is `Type.Date`, only the full day date will be considered for comparison, ignoring the time components of the Date object. - If the PickDate type is `Type.DateTime`, both date and time components will be considered for comparison. The date should be a JavaScript Date object. | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) | - | | onBlur | The callback that will be triggered when the item loses its focus. | `(event: FormEvent<`[`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)`>) => void` | - | | onChange | The callback which will be triggered when the `value` of the item changes. | `(newValue:` [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)`) => void` | - | | onFocus | The callback which will be triggered should be called when the item is focused. | `(event: FormEvent<`[`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)`>) => void` | - | | storeValue | Indicates whether the value of the item should be persisted after submitting, and restored next time the form is rendered. | `boolean` | - | | title | The title displayed on the left side of the item. | `string` | - | | type | Indicates what types of date components can be picked Defaults to Form.DatePicker.Type.DateTime | [`Form.DatePicker.Type`](#form.datepicker.type) | - | | value | The current value of the item. | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) | - | #### Methods (Imperative API) | Name | Signature | Description | | ----- | ------------ | -------------------------------------------------------------------------- | | focus | `() => void` | Makes the item request focus. | | reset | `() => void` | Resets the form item to its initial value, or `defaultValue` if specified. | #### Form.DatePicker.isFullDay A method that determines if a given date represents a full day or a specific time. ```ts import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
{ if (Form.DatePicker.isFullDay(values.reminderDate)) { // the event is for a full day } else { // the event is at a specific time } }} /> } > ); } ``` ### Form.Dropdown A form item with a dropdown menu. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-a41244152d1b3069ff42e915e6d00b6259e3a2f1%2Fform-dropdown.webp?alt=media) #### Example {% tabs %} {% tab title="Uncontrolled dropdown" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` {% endtab %} {% tab title="Controlled dropdown" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [programmingLanguage, setProgrammingLanguage] = useState("typescript"); return (
console.log(values)} /> } > ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ------- | | id\* | ID of the form item. Make sure to assign each form item a unique id. | `string` | - | | autoFocus | Indicates whether the item should be focused automatically once the form is rendered. | `boolean` | - | | children | Sections or items. If Form.Dropdown.Item elements are specified, a default section is automatically created. | `React.ReactNode` | - | | defaultValue | The default value of the item. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. If you're using `storeValue` and configured it as `true` then the stored value will be set. If you configure `value` at the same time with `defaultValue`, the `value` will be set instead of `defaultValue`. | `string` | - | | error | An optional error message to show the form item validation issues. If the `error` is present, the Form Item will be highlighted with red border and will show an error message on the right. | `string` | - | | filtering | Toggles Raycast filtering. When `true`, Raycast will use the query in the search bar to filter the items. When `false`, the extension needs to take care of the filtering. You can further define how native filtering orders sections by setting an object with a `keepSectionOrder` property: When `true`, ensures that Raycast filtering maintains the section order as defined in the extension. When `false`, filtering may change the section order depending on the ranking values of items. | `boolean` or `{ keepSectionOrder: boolean }` | - | | info | An optional info message to describe the form item. It appears on the right side of the item with an info icon. When the icon is hovered, the info message is shown. | `string` | - | | isLoading | Indicates whether a loading indicator should be shown or hidden next to the search bar | `boolean` | - | | onBlur | The callback that will be triggered when the item loses its focus. | `(event: FormEvent) => void` | - | | onChange | The callback which will be triggered when the `value` of the item changes. | `(newValue: string) => void` | - | | onFocus | The callback which will be triggered should be called when the item is focused. | `(event: FormEvent) => void` | - | | onSearchTextChange | Callback triggered when the search bar text changes. | `(text: string) => void` | - | | placeholder | Placeholder text that will be shown in the dropdown search field. | `string` | - | | storeValue | Indicates whether the value of the item should be persisted after submitting, and restored next time the form is rendered. | `boolean` | - | | throttle | Defines whether the `onSearchTextChange` handler will be triggered on every keyboard press or with a delay for throttling the events. Recommended to set to `true` when using custom filtering logic with asynchronous operations (e.g. network requests). | `boolean` | - | | title | The title displayed on the left side of the item. | `string` | - | | value | The current value of the item. | `string` | - | #### Methods (Imperative API) | Name | Signature | Description | | ----- | ------------ | -------------------------------------------------------------------------- | | focus | `() => void` | Makes the item request focus. | | reset | `() => void` | Resets the form item to its initial value, or `defaultValue` if specified. | ### Form.Dropdown.Item A dropdown item in a [Form.Dropdown](#form.dropdown) #### Example ```typescript import { Action, ActionPanel, Form, Icon } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | title\* | The title displayed for the item. | `string` | - | | value\* | Value of the dropdown item. Make sure to assign each unique value for each item. | `string` | - | | icon | A optional icon displayed for the item. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | keywords | An optional property used for providing additional indexable strings for search. When filtering the items in Raycast, the keywords will be searched in addition to the title. | `string[]` | - | ### Form.Dropdown.Section Visually separated group of dropdown items. Use sections to group related menu items together. #### Example ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` #### Props | Prop | Description | Type | Default | | -------- | --------------------------------- | ----------------- | ------- | | children | The item elements of the section. | `React.ReactNode` | - | | title | Title displayed above the section | `string` | - | ### Form.TagPicker A form item with a tag picker that allows the user to select multiple items. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-0df8b3735a1881832202a9a88b4614abddf986a4%2Fform-tagpicker.webp?alt=media) #### Example {% tabs %} {% tab title="Uncontrolled tag picker" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` {% endtab %} {% tab title="Controlled tag picker" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [countries, setCountries] = useState(["ger", "ned", "pol"]); return (
console.log(values)} /> } > ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------- | | id\* | ID of the form item. Make sure to assign each form item a unique id. | `string` | - | | autoFocus | Indicates whether the item should be focused automatically once the form is rendered. | `boolean` | - | | children | The list of tags. | `React.ReactNode` | - | | defaultValue | The default value of the item. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. If you're using `storeValue` and configured it as `true` then the stored value will be set. If you configure `value` at the same time with `defaultValue`, the `value` will be set instead of `defaultValue`. | `string[]` | - | | error | An optional error message to show the form item validation issues. If the `error` is present, the Form Item will be highlighted with red border and will show an error message on the right. | `string` | - | | info | An optional info message to describe the form item. It appears on the right side of the item with an info icon. When the icon is hovered, the info message is shown. | `string` | - | | onBlur | The callback that will be triggered when the item loses its focus. | `(event: FormEvent) => void` | - | | onChange | The callback which will be triggered when the `value` of the item changes. | `(newValue: string[]) => void` | - | | onFocus | The callback which will be triggered should be called when the item is focused. | `(event: FormEvent) => void` | - | | placeholder | Placeholder text shown in the token field. | `string` | - | | storeValue | Indicates whether the value of the item should be persisted after submitting, and restored next time the form is rendered. | `boolean` | - | | title | The title displayed on the left side of the item. | `string` | - | | value | The current value of the item. | `string[]` | - | #### Methods (Imperative API) | Name | Signature | Description | | ----- | ------------ | -------------------------------------------------------------------------- | | focus | `() => void` | Makes the item request focus. | | reset | `() => void` | Resets the form item to its initial value, or `defaultValue` if specified. | ### Form.TagPicker.Item A tag picker item in a [Form.TagPicker](#form.tagpicker). #### Example ```typescript import { ActionPanel, Color, Form, Icon, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | title\* | The display title of the tag. | `string` | - | | value\* | Value of the tag. Make sure to assign unique value for each item. | `string` | - | | icon | An icon to show in the tag. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | ### Form.Separator A form item that shows a separator line. Use for grouping and visually separating form items. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8268f478b1023d780fa7f357b062a39eb8b09546%2Fform-separator.webp?alt=media) #### Example ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` ### Form.FilePicker A form item with a button to open a dialog to pick some files and/or some directories (depending on its props). {% hint style="info" %} While the user picked some items that existed, it might be possible for them to be deleted or changed when the `onSubmit` callback is called. Hence you should always make sure that the items exist before acting on them! {% endhint %} ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-a84e496e0d48f2d448078314a7a42af3db29abcf%2Fform-filepicker-multiple.webp?alt=media) ![Single Selection](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-53289fc13e994aeee7d356baf18b053340e8e927%2Fform-filepicker-single.webp?alt=media) #### Example {% tabs %} {% tab title="Uncontrolled file picker" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import fs from "fs"; export default function Command() { return (
{ const files = values.files.filter((file: any) => fs.existsSync(file) && fs.lstatSync(file).isFile()); console.log(files); }} /> } > ); } ``` {% endtab %} {% tab title="Single selection file picker" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import fs from "fs"; export default function Command() { return (
{ const file = values.files[0]; if (!fs.existsSync(file) || !fs.lstatSync(file).isFile()) { return false; } console.log(file); }} /> } > ); } ``` {% endtab %} {% tab title="Directory picker" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import fs from "fs"; export default function Command() { return (
{ const folder = values.folders[0]; if (!fs.existsSync(folder) || fs.lstatSync(folder).isDirectory()) { return false; } console.log(folder); }} /> } > ); } ``` {% endtab %} {% tab title="Controlled file picker" %} ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; import { useState } from "react"; export default function Command() { const [files, setFiles] = useState([]); return (
console.log(values)} /> } > ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------- | | id\* | ID of the form item. Make sure to assign each form item a unique id. | `string` | - | | allowMultipleSelection | Indicates whether the user can select multiple items or only one. | `boolean` | - | | autoFocus | Indicates whether the item should be focused automatically once the form is rendered. | `boolean` | - | | canChooseDirectories | Indicates whether it's possible to choose a directory. Note: On Windows, this property is ignored if `canChooseFiles` is set to `true`. | `boolean` | - | | canChooseFiles | Indicates whether it's possible to choose a file. | `boolean` | - | | defaultValue | The default value of the item. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. If you're using `storeValue` and configured it as `true` then the stored value will be set. If you configure `value` at the same time with `defaultValue`, the `value` will be set instead of `defaultValue`. | `string[]` | - | | error | An optional error message to show the form item validation issues. If the `error` is present, the Form Item will be highlighted with red border and will show an error message on the right. | `string` | - | | info | An optional info message to describe the form item. It appears on the right side of the item with an info icon. When the icon is hovered, the info message is shown. | `string` | - | | onBlur | The callback that will be triggered when the item loses its focus. | `(event: FormEvent) => void` | - | | onChange | The callback which will be triggered when the `value` of the item changes. | `(newValue: string[]) => void` | - | | onFocus | The callback which will be triggered should be called when the item is focused. | `(event: FormEvent) => void` | - | | showHiddenFiles | Indicates whether the file picker displays files that are normally hidden from the user. | `boolean` | - | | storeValue | Indicates whether the value of the item should be persisted after submitting, and restored next time the form is rendered. | `boolean` | - | | title | The title displayed on the left side of the item. | `string` | - | | value | The current value of the item. | `string[]` | - | #### Methods (Imperative API) | Name | Signature | Description | | ----- | ------------ | -------------------------------------------------------------------------- | | focus | `() => void` | Makes the item request focus. | | reset | `() => void` | Resets the form item to its initial value, or `defaultValue` if specified. | ### Form.Description A form item with a simple text label. Do *not* use this component to show validation messages for other form fields. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8dba909f1969413f109d4536b5f61bd638213019%2Fform-description.webp?alt=media) #### Example ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
console.log(values)} /> } > ); } ``` #### Props | Prop | Description | Type | Default | | -------------------------------------- | ------------------------------------------------------------- | -------- | ------- | | text\* | Text that will be displayed in the middle. | `string` | - | | title | The display title of the left side from the description item. | `string` | - | ### Form.LinkAccessory A link that will be shown in the right-hand side of the navigation bar. #### Example ```typescript import { ActionPanel, Form, Action } from "@raycast/api"; export default function Command() { return (
} actions={ console.log(values)} /> } > ); } ``` #### Props | Prop | Description | Type | Default | | ---------------------------------------- | --------------------------- | -------- | ------- | | target\* | The target of the link. | `string` | - | | text\* | The text value of the item. | `string` | - | ## Types #### Form.Event Some Form.Item callbacks (like `onFocus` and `onBlur`) can return a `Form.Event` object that you can use in a different ways. | Property | Description | Type | | ---------------------------------------- | -------------------------------------------------------- | ------------------------------------- | | target\* | An interface containing target data related to the event | `{ id: string; value?: any }` | | type\* | A type of event | [`Form.Event.Type`](#form.event.type) | #### Example ```typescript import { Form } from "@raycast/api"; export default function Main() { return (
{[1, 2, 3, 4, 5, 6, 7].map((num) => ( ))} {[1, 2, 3, 4, 5, 6, 7].map((num) => ( ))} ); } function logEvent(event: Form.Event) { console.log(`Event '${event.type}' has happened for '${event.target.id}'. Current 'value': '${event.target.value}'`); } ``` #### Form.Event.Type The different types of [`Form.Event`](#form.event). Can be `"focus"` or `"blur"`. ### Form.Values Values of items in the form. For type-safe form values, you can define your own interface. Use the ID's of the form items as the property name. #### Example ```typescript import { Form, Action, ActionPanel } from "@raycast/api"; interface Values { todo: string; due?: Date; } export default function Command() { function handleSubmit(values: Values) { console.log(values); } return (
} > ); } ``` #### Properties | Name | Type | Required | Description | | ----------------- | ----- | -------- | ------------------------------- | | \[itemId: string] | `any` | Yes | The form value of a given item. | ### Form.DatePicker.Type The types of date components the user can pick with a `Form.DatePicker`. #### Enumeration members | Name | Description | | -------- | ---------------------------------------------------------------- | | DateTime | Hour and second can be picked in addition to year, month and day | | Date | Only year, month, and day can be picked | *** ## Imperative API You can use React's [useRef](https://reactjs.org/docs/hooks-reference.html#useref) hook to create variables which have access to imperative APIs (such as `.focus()` or `.reset()`) exposed by the native form items. ```typescript import { useRef } from "react"; import { ActionPanel, Form, Action } from "@raycast/api"; interface FormValues { nameField: string; bioTextArea: string; datePicker: string; } export default function Command() { const textFieldRef = useRef(null); const textAreaRef = useRef(null); const datePickerRef = useRef(null); const passwordFieldRef = useRef(null); const dropdownRef = useRef(null); const tagPickerRef = useRef(null); const firstCheckboxRef = useRef(null); const secondCheckboxRef = useRef(null); async function handleSubmit(values: FormValues) { console.log(values); datePickerRef.current?.focus(); dropdownRef.current?.reset(); } return (
textFieldRef.current?.focus()} /> textAreaRef.current?.focus()} /> datePickerRef.current?.focus()} /> passwordFieldRef.current?.focus()} /> dropdownRef.current?.focus()} /> tagPickerRef.current?.focus()} /> firstCheckboxRef.current?.focus()} /> secondCheckboxRef.current?.focus()} /> textFieldRef.current?.reset()} /> textAreaRef.current?.reset()} /> datePickerRef.current?.reset()} /> passwordFieldRef.current?.reset()} /> dropdownRef.current?.reset()} /> tagPickerRef.current?.reset()} /> firstCheckboxRef.current?.reset()} /> secondCheckboxRef.current?.reset()} /> } > { console.log(newValue); }} ref={dropdownRef} > { console.log(t); }} > {["one", "two", "three"].map((tag) => ( ))} { console.log("first checkbox onChange ", checked); }} /> { console.log("second checkbox onChange ", checked); }} /> ); } ``` --- # Source: https://developers.raycast.com/utilities/functions.md # Functions - [createDeeplink](/utilities/functions/createdeeplink.md) - [executeSQL](/utilities/functions/executesql.md) - [runAppleScript](/utilities/functions/runapplescript.md) - [runPowerShellScript](/utilities/functions/runpowershellscript.md) - [showFailureToast](/utilities/functions/showfailuretoast.md) - [withCache](/utilities/functions/withcache.md) --- # Source: https://developers.raycast.com/utilities/oauth/getaccesstoken.md # getAccessToken Utility function designed for retrieving authorization tokens within a component. It ensures that your React components have the necessary authentication state, either through OAuth or a personal access token. {% hint style="info" %} `getAccessToken` **must** be used within components that are nested inside a component wrapped with [`withAccessToken`](https://developers.raycast.com/utilities/oauth/withaccesstoken). Otherwise, the function will fail with an error. {% endhint %} ## Signature ```tsx function getAccessToken(): { token: string; type: "oauth" | "personal"; }; ``` ### Return The function returns an object containing the following properties: * `token`: A string representing the access token. * `type`: An optional string that indicates the type of token retrieved. It can either be `oauth` for OAuth tokens or `personal` for personal access tokens. ## Example ```tsx import { Detail } from "@raycast/api"; import { authorize } from "./oauth"; function AuthorizedComponent() { const { token } = getAccessToken(); return ; } export default withAccessToken({ authorize })(AuthorizedComponent); ``` --- # Source: https://developers.raycast.com/utilities/icons/getavataricon.md # getAvatarIcon Icon to represent an avatar when you don't have one. The generated avatar will be generated from the initials of the name and have a colorful but consistent background. ![Avatar Icon example](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-3c6269820bd9ecb9d18550e31d2fff626deefdc5%2Futils-avatar-icon.png?alt=media) ## Signature ```ts function getAvatarIcon( name: string, options?: { background?: string; gradient?: boolean; }, ): Image.Asset; ``` * `name` is a string of the subject's name. * `options.background` is a hexadecimal representation of a color to be used as the background color. By default, the hook will pick a random but consistent (eg. the same name will the same color) color from a set handpicked to nicely match Raycast. * `options.gradient` is a boolean to choose whether the background should have a slight gradient or not. By default, it will. Returns an [Image.Asset](https://developers.raycast.com/api-reference/user-interface/icons-and-images) that can be used where Raycast expects them. ## Example ```tsx import { List } from "@raycast/api"; import { getAvatarIcon } from "@raycast/utils"; export default function Command() { return ( ); } ``` --- # Source: https://developers.raycast.com/utilities/icons/getfavicon.md # getFavicon Icon showing the favicon of a website. A favicon (favorite icon) is a tiny icon included along with a website, which is displayed in places like the browser's address bar, page tabs, and bookmarks menu. ![Favicon example](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8383499b76a723de43e610079031cd2543c52a66%2Futils-favicon.png?alt=media) ## Signature ```ts function getFavicon( url: string | URL, options?: { fallback?: Image.Fallback; size?: boolean; mask?: Image.Mask; }, ): Image.ImageLike; ``` * `name` is a string of the subject's name. * `options.fallback` is a [Image.Fallback](https://developers.raycast.com/api-reference/user-interface/icons-and-images#image.fallback) icon in case the Favicon is not found. By default, the fallback will be `Icon.Link`. * `options.size` is the size of the returned favicon. By default, it is 64 pixels. * `options.mask` is the size of the [Image.Mask](https://developers.raycast.com/api-reference/user-interface/icons-and-images#image.mask) to apply to the favicon. Returns an [Image.ImageLike](https://developers.raycast.com/api-reference/user-interface/icons-and-images) that can be used where Raycast expects them. ## Example ```tsx import { List } from "@raycast/api"; import { getFavicon } from "@raycast/utils"; export default function Command() { return ( ); } ``` --- # Source: https://developers.raycast.com/utilities/icons/getprogressicon.md # getProgressIcon Icon to represent the progress of a task, a project, *something*. ![Progress Icon example](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8d31dd8b07fabd4eba1a4ab2d4f256bc50f0fb9c%2Futils-progress-icon.png?alt=media) ## Signature ```ts function getProgressIcon( progress: number, color?: Color | string, options?: { background?: Color | string; backgroundOpacity?: number; }, ): Image.Asset; ``` * `progress` is a number between 0 and 1 (0 meaning not started, 1 meaning finished). * `color` is a Raycast `Color` or a hexadecimal representation of a color. By default it will be `Color.Red`. * `options.background` is a Raycast `Color` or a hexadecimal representation of a color for the background of the progress icon. By default, it will be `white` if the Raycast's appearance is `dark`, and `black` if the appearance is `light`. * `options.backgroundOpacity` is the opacity of the background of the progress icon. By default, it will be `0.1`. Returns an [Image.Asset](https://developers.raycast.com/api-reference/user-interface/icons-and-images) that can be used where Raycast expects them. ## Example ```tsx import { List } from "@raycast/api"; import { getProgressIcon } from "@raycast/utils"; export default function Command() { return ( ); } ``` --- # Source: https://developers.raycast.com/utilities/oauth/getting-google-client-id.md # Getting a Google client ID Follow these steps to get a Google client ID: ## Step 1: Access Google Cloud Console Navigate to the [Google Cloud Console](https://console.developers.google.com/apis/credentials). ## Step 2: Create a Project (if needed) 1. Click **Create Project**. 2. Provide a **Project Name**. 3. Select an optional **Organization**. 4. Click **Create**. ## Step 3: Enable Required APIs 1. Go to **Enabled APIs & services**. 2. Click **ENABLE APIS AND SERVICES**. 3. Search for and enable the required API (e.g., Google Drive API). ## Step 4: Configure OAuth Consent Screen 1. Click on **OAuth consent screen**. 2. Choose **Internal** or **External** (choose **External** if you intend to publish the extension in the Raycast store). 3. Enter these details: * **App name**: Raycast (Your Extension Name) * **User support email**: * **Logo**: Paste Raycast's logo over there ([Link to Raycast logo](https://raycastapp.notion.site/Raycast-Press-Kit-ce1ccf8306b14ac8b8d47b3276bf34e0#29cbc2f3841444fdbdcb1fdff2ea2abf)) * **Application home page**: * **Application privacy policy link**: * **Application terms of service link**: * **Authorized domains**: Click **ADD DOMAIN** then add `raycast.com` * **Developer contact**: 4. Add the necessary scopes for your app (visit the [Google OAuth scopes docs](https://developers.google.com/identity/protocols/oauth2/scopes) if you manually need to add scopes) 5. Add your own email as a test user and others if needed 6. Review and go back to the dashboard ## Step 5: Create an OAuth Client ID 1. Go to **Credentials**, click **CREATE CREDENTIALS**, then **OAuth client ID** 2. Choose **iOS** as the application type 3. Set the **Bundle ID** to `com.raycast`. 4. Copy your **Client ID** ## Step 6: Use Your New Client ID 🎉 {% hint style="info" %} You'll need to publish the app in the **OAuth consent screen** so that everyone can use it (and not only test users). The process can be more or less complex depending on whether you use sensitive or restrictive scopes. {% endhint %} --- # Source: https://developers.raycast.com/utilities/getting-started.md # Source: https://developers.raycast.com/teams/getting-started.md # Source: https://developers.raycast.com/ai/getting-started.md # Source: https://developers.raycast.com/basics/getting-started.md # Getting Started ## System Requirements Before you can create your first extension, make sure you have the following prerequisites. * You have Raycast 1.26.0 or higher installed. * You have [Node.js](https://nodejs.org) 22.14 or higher installed. We recommend [nvm](https://github.com/nvm-sh/nvm) to install Node. * You have [npm](http://npmjs.com) 7 or higher * You are familiar with [React](https://reactjs.org) and [TypeScript](https://www.typescriptlang.org). Don't worry, you don't need to be an expert. If you need some help with the basics, check out TypeScript's [Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) and React's [Getting Started](https://react.dev/learn) guide. ## Sign In ![Opening the "Store" command in Raycast](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-829557d913eae5961c97c1493babd3e6371a30f1%2Fwelcome.webp?alt=media) You need to be signed in to use the following extension development commands. * **Store:** Search and install all published extensions * **Create Extension:** Create new extensions from templates * **Import Extension:** Import extensions from source code * **Manage Extensions**: List and edit your published extensions --- # Source: https://developers.raycast.com/api-reference/user-interface/grid.md # Grid The `Grid` component is provided as an alternative to the [List](https://developers.raycast.com/api-reference/list#list) component when the defining characteristic of an item is an image. {% hint style="info" %} Because its API tries to stick as closely to [List](https://developers.raycast.com/api-reference/list#list)'s as possible, changing a view from [List](https://developers.raycast.com/api-reference/list#list) to [Grid](#grid) should be as simple as: * making sure you're using at least version 1.36.0 of the `@raycast/api` package * updating your imports from `import { List } from '@raycast/api'` to `import { Grid } from '@raycast/api'`; * removing the `isShowingDetail` prop from the top-level `List` component, along with all [List.Item](https://developers.raycast.com/api-reference/list#list.item)s' `detail` prop * renaming all [List.Item](https://developers.raycast.com/api-reference/list#list.item)s' h`icon` prop to `content` * removing all [List.Item](https://developers.raycast.com/api-reference/list#list.item)s' `accessories`, `accessoryIcon` and \`accessoryTitle props; [Grid.Item](#grid.item) does not *currently* support accessories * finally, replacing all usages of `List` with `Grid`. {% endhint %} ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-4bb3d7e88613cf9ccba01c798f5d2aa62edfaeac%2Fgrid.webp?alt=media) ## Search Bar The search bar allows users to interact quickly with grid items. By default, [Grid.Items](#grid.item) are displayed if the user's input can be (fuzzy) matched to the item's `title` or `keywords`. ### Custom filtering Sometimes, you may not want to rely on Raycast's filtering, but use/implement your own. If that's the case, you can set the `Grid`'s `filtering` [prop](#props) to false, and the items displayed will be independent of the search bar's text.\ Note that `filtering` is also implicitly set to false if an `onSearchTextChange` listener is specified. If you want to specify a change listener and *still* take advantage of Raycast's built-in filtering, you can explicitly set `filtering` to true. ```typescript import { useEffect, useState } from "react"; import { Grid } from "@raycast/api"; const items = [ { content: "🙈", keywords: ["see-no-evil", "monkey"] }, { content: "🥳", keywords: ["partying", "face"] }, ]; export default function Command() { const [searchText, setSearchText] = useState(""); const [filteredList, filterList] = useState(items); useEffect(() => { filterList(items.filter((item) => item.keywords.some((keyword) => keyword.includes(searchText)))); }, [searchText]); return ( {filteredList.map((item) => ( ))} ); } ``` ### Programmatically updating the search bar Other times, you may want the content of the search bar to be updated by the extension, for example, you may store a list of the user's previous searches and, on the next visit, allow them to "continue" where they left off. To do so, you can use the `searchText` [prop](#props). ```typescript import { useState } from "react"; import { Action, ActionPanel, Grid } from "@raycast/api"; const items = [ { content: "🙈", keywords: ["see-no-evil", "monkey"] }, { content: "🥳", keywords: ["partying", "face"] }, ]; export default function Command() { const [searchText, setSearchText] = useState(""); return ( {items.map((item) => ( setSearchText(item.content)} /> } /> ))} ); } ``` ### Dropdown Some extensions may benefit from giving users a second filtering dimension. A media file management extension may allow users to view only videos or only images, an image-searching extension may allow switching ssearch engines, etc. This is where the `searchBarAccessory` [prop](#props) is useful. Pass it a [Grid.Dropdown](#grid.dropdown) component, and it will be displayed on the right-side of the search bar. Invoke it either by using the global shortcut `⌘` `P` or by clicking on it. ### Pagination {% hint style="info" %} Pagination requires version 1.69.0 or higher of the `@raycast/api` package. {% endhint %} `Grid`s have built-in support for pagination. To opt in to pagination, you need to pass it a `pagination` prop, which is an object providing 3 pieces of information: * `onLoadMore` - will be called by Raycast when the user reaches the end of the grid, either using the keyboard or the mouse. When it gets called, the extension is expected to perform an async operation which eventually can result in items being appended to the end of the grid. * `hasMore` - indicates to Raycast whether it *should* call `onLoadMore` when the user reaches the end of the grid. * `pageSize` - indicates how many placeholder items Raycast should add to the end of the grid when it calls `onLoadMore`. Once `onLoadMore` finishes executing, the placeholder items will be replaced by the newly-added grid items. Note that extensions have access to a limited amount of memory. As your extension paginates, its memory usage will increase. Paginating extensively could lead to the extension eventually running out of memory and crashing. To protect against the extension crashing due to memory exhaustion, Raycast monitors the extension's memory usage and employs heuristics to determine whether it's safe to paginate further. If it's deemed unsafe to continue paginating, `onLoadMore` will not be triggered when the user scrolls to the bottom, regardless of the `hasMore` value. Additionally, during development, a warning will be printed in the terminal. For convenience, most of the [hooks](https://developers.raycast.com/utilities/getting-started) that we provide have built-in pagination support. Here's an example of how to add pagination support to a simple command using [usePromise](https://developers.raycast.com/utilities/react-hooks/usepromise), and one "from scratch". {% tabs %} {% tab title="GridWithUsePromisePagination.tsx" %} ```typescript import { setTimeout } from "node:timers/promises"; import { useState } from "react"; import { Grid } from "@raycast/api"; import { usePromise } from "@raycast/utils"; export default function Command() { const [searchText, setSearchText] = useState(""); const { isLoading, data, pagination } = usePromise( (searchText: string) => async (options: { page: number }) => { await setTimeout(200); const newData = Array.from({ length: 25 }, (_v, index) => ({ index, page: options.page, text: searchText })); return { data: newData, hasMore: options.page < 10 }; }, [searchText] ); return ( {data?.map((item) => ( ))} ); } ``` {% endtab %} {% tab title="GridWithPagination.tsx" %} ```typescript import { setTimeout } from "node:timers/promises"; import { useCallback, useEffect, useRef, useState } from "react"; import { Grid } from "@raycast/api"; type State = { searchText: string; isLoading: boolean; hasMore: boolean; data: { index: number; page: number; text: string; }[]; nextPage: number; }; const pageSize = 20; export default function Command() { const [state, setState] = useState({ searchText: "", isLoading: true, hasMore: true, data: [], nextPage: 0 }); const cancelRef = useRef(null); const loadNextPage = useCallback(async (searchText: string, nextPage: number, signal?: AbortSignal) => { setState((previous) => ({ ...previous, isLoading: true })); await setTimeout(200); const newData = Array.from({ length: pageSize }, (_v, index) => ({ index, page: nextPage, text: searchText, })); if (signal?.aborted) { return; } setState((previous) => ({ ...previous, data: [...previous.data, ...newData], isLoading: false, hasMore: nextPage < 10, })); }, []); const onLoadMore = useCallback(() => { setState((previous) => ({ ...previous, nextPage: previous.nextPage + 1 })); }, []); const onSearchTextChange = useCallback( (searchText: string) => { if (searchText === state.searchText) return; setState((previous) => ({ ...previous, data: [], nextPage: 0, searchText, })); }, [state.searchText] ); useEffect(() => { cancelRef.current?.abort(); cancelRef.current = new AbortController(); loadNextPage(state.searchText, state.nextPage, cancelRef.current?.signal); return () => { cancelRef.current?.abort(); }; }, [loadNextPage, state.searchText, state.nextPage]); return ( {state.data.map((item) => ( ))} ); } ``` {% endtab %} {% endtabs %} {% hint style="warning" %} Pagination might not work properly if all grid items are rendered and visible at once, as `onLoadMore` won't be triggered. This typically happens when an API returns 10 results by default, all fitting within the Raycast window. To fix this, try displaying more items, like 20. {% endhint %} ## Examples {% tabs %} {% tab title="Grid.tsx" %} ```jsx import { Grid } from "@raycast/api"; export default function Command() { return ( ); } ``` {% endtab %} {% tab title="GridWithSections.tsx" %} ```typescript import { Grid } from "@raycast/api"; export default function Command() { return ( ); } ``` {% endtab %} {% tab title="GridWithActions.tsx" %} ```typescript import { ActionPanel, Action, Grid } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` {% endtab %} {% tab title="GridWithEmptyView\.tsx" %} ```typescript import { useEffect, useState } from "react"; import { Grid, Image } from "@raycast/api"; export default function CommandWithCustomEmptyView() { const [state, setState] = useState<{ searchText: string; items: { content: Image.ImageLike; title: string }[]; }>({ searchText: "", items: [] }); useEffect(() => { console.log("Running effect after state.searchText changed. Current value:", JSON.stringify(state.searchText)); // perform an API call that eventually populates `items`. }, [state.searchText]); return ( setState((previous) => ({ ...previous, searchText: newValue }))}> {state.searchText === "" && state.items.length === 0 ? ( ) : ( state.items.map((item, index) => ) )} ); } ``` {% endtab %} {% endtabs %} ## API Reference ### Grid Displays [Grid.Section](#grid.section)s or [Grid.Item](#grid.item)s. The grid uses built-in filtering by indexing the title & keywords of its items. #### Example ```typescript import { Grid } from "@raycast/api"; const items = [ { content: "🙈", keywords: ["see-no-evil", "monkey"] }, { content: "🥳", keywords: ["partying", "face"] }, ]; export default function Command() { return ( {items.map((item) => ( ))} ); } ``` #### Props | Prop | Description | Type | Default | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------- | | actions | A reference to an ActionPanel. It will only be shown when there aren't any children. | `React.ReactNode` | - | | aspectRatio | Aspect ratio for the Grid.Item elements. Defaults to 1. | `"1"` or `"3/2"` or `"2/3"` or `"4/3"` or `"3/4"` or `"16/9"` or `"9/16"` | - | | children | Grid sections or items. If Grid.Item elements are specified, a default section is automatically created. | `React.ReactNode` | - | | columns | Column count for the grid's sections. Minimum value is 1, maximum value is 8. | `number` | - | | filtering | Toggles Raycast filtering. When `true`, Raycast will use the query in the search bar to filter the items. When `false`, the extension needs to take care of the filtering. You can further define how native filtering orders sections by setting an object with a `keepSectionOrder` property: When `true`, ensures that Raycast filtering maintains the section order as defined in the extension. When `false`, filtering may change the section order depending on the ranking values of items. | `boolean` or `{ keepSectionOrder: boolean }` | - | | fit | Fit for the Grid.Item element content. Defaults to "contain" | [`Grid.Fit`](#grid.fit) | - | | inset | Indicates how much space there should be between a Grid.Items' content and its borders. The absolute value depends on the value of the `itemSize` prop. | [`Grid.Inset`](#grid.inset) | - | | isLoading | Indicates whether a loading bar should be shown or hidden below the search bar | `boolean` | - | | navigationTitle | The main title for that view displayed in Raycast | `string` | - | | onSearchTextChange | Callback triggered when the search bar text changes. | `(text: string) => void` | - | | onSelectionChange | Callback triggered when the item selection in the grid changes. When the received id is `null`, it means that all items have been filtered out and that there are no item selected | `(id: string) => void` | - | | pagination | Configuration for pagination | `{ hasMore: boolean; onLoadMore: () => void; pageSize: number }` | - | | searchBarAccessory | Grid.Dropdown that will be shown in the right-hand-side of the search bar. | `ReactElement<`[`List.Dropdown.Props`](https://developers.raycast.com/api-reference/list#props)`, string>` | - | | searchBarPlaceholder | Placeholder text that will be shown in the search bar. | `string` | - | | searchText | The text that will be displayed in the search bar. | `string` | - | | selectedItemId | Selects the item with the specified id. | `string` | - | | throttle | Defines whether the `onSearchTextChange` handler will be triggered on every keyboard press or with a delay for throttling the events. Recommended to set to `true` when using custom filtering logic with asynchronous operations (e.g. network requests). | `boolean` | - | ### Grid.Dropdown A dropdown menu that will be shown in the right-hand-side of the search bar. #### Example ```typescript import { Grid, Image } from "@raycast/api"; import { useState } from "react"; const types = [ { id: 1, name: "Smileys", value: "smileys" }, { id: 2, name: "Animals & Nature", value: "animals-and-nature" }, ]; const items: { [key: string]: { content: Image.ImageLike; keywords: string[] }[] } = { smileys: [{ content: "🥳", keywords: ["partying", "face"] }], "animals-and-nature": [{ content: "🙈", keywords: ["see-no-evil", "monkey"] }], }; export default function Command() { const [type, setType] = useState("smileys"); return ( setType(newValue)}> {types.map((type) => ( ))} } > {(items[type] || []).map((item) => ( ))} ); } ``` #### Props | Prop | Description | Type | Default | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ------- | | tooltip\* | Tooltip displayed when hovering the dropdown. | `string` | - | | children | Dropdown sections or items. If Dropdown.Item elements are specified, a default section is automatically created. | `React.ReactNode` | - | | defaultValue | The default value of the dropdown. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. **If you're using `storeValue` and configured it as `true`** ***and***** a Dropdown.Item with the same value exists, then it will be selected.** **If you configure `value` at the same time as `defaultValue`, the `value` will have precedence over `defaultValue`.** | `string` | - | | filtering | Toggles Raycast filtering. When `true`, Raycast will use the query in the search bar to filter the items. When `false`, the extension needs to take care of the filtering. You can further define how native filtering orders sections by setting an object with a `keepSectionOrder` property: When `true`, ensures that Raycast filtering maintains the section order as defined in the extension. When `false`, filtering may change the section order depending on the ranking values of items. | `boolean` or `{ keepSectionOrder: boolean }` | - | | id | ID of the dropdown. | `string` | - | | isLoading | Indicates whether a loading indicator should be shown or hidden next to the search bar | `boolean` | - | | onChange | Callback triggered when the dropdown selection changes. | `(newValue: string) => void` | - | | onSearchTextChange | Callback triggered when the search bar text changes. | `(text: string) => void` | - | | placeholder | Placeholder text that will be shown in the dropdown search field. | `string` | - | | storeValue | Indicates whether the value of the dropdown should be persisted after selection, and restored next time the dropdown is rendered. | `boolean` | - | | throttle | Defines whether the `onSearchTextChange` handler will be triggered on every keyboard press or with a delay for throttling the events. Recommended to set to `true` when using custom filtering logic with asynchronous operations (e.g. network requests). | `boolean` | - | | value | The currently value of the dropdown. | `string` | - | ### Grid.Dropdown.Item A dropdown item in a [Grid.Dropdown](#grid.dropdown) #### Example ```typescript import { Grid } from "@raycast/api"; export default function Command() { return ( } > ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | title\* | The title displayed for the item. | `string` | - | | value\* | Value of the dropdown item. Make sure to assign each unique value for each item. | `string` | - | | icon | An optional icon displayed for the item. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | keywords | An optional property used for providing additional indexable strings for search. When filtering the items in Raycast, the keywords will be searched in addition to the title. | `string[]` | - | ### Grid.Dropdown.Section Visually separated group of dropdown items. Use sections to group related menu items together. #### Example ```typescript import { Grid } from "@raycast/api"; export default function Command() { return ( } > ); } ``` #### Props | Prop | Description | Type | Default | | -------- | --------------------------------- | ----------------- | ------- | | children | The item elements of the section. | `React.ReactNode` | - | | title | Title displayed above the section | `string` | - | ### Grid.EmptyView A view to display when there aren't any items available. Use to greet users with a friendly message if the\ extension requires user input before it can show any items e.g. when searching for an image, a gif etc. Raycast provides a default `EmptyView` that will be displayed if the Grid component either has no children,\ or if it has children, but none of them match the query in the search bar. This too can be overridden by passing an\ empty view alongside the other `Grid.Item`s. Note that the `EmptyView` is *never* displayed if the `Grid`'s `isLoading` property is true and the search bar is empty. ![Grid EmptyView illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-ce76aa8900d21be445aba5e7acd3ef7fa7687e9e%2Fgrid-empty-view.webp?alt=media) #### Example ```typescript import { useEffect, useState } from "react"; import { Grid, Image } from "@raycast/api"; export default function CommandWithCustomEmptyView() { const [state, setState] = useState<{ searchText: string; items: { content: Image.ImageLike; title: string }[]; }>({ searchText: "", items: [] }); useEffect(() => { console.log("Running effect after state.searchText changed. Current value:", JSON.stringify(state.searchText)); // perform an API call that eventually populates `items`. }, [state.searchText]); return ( setState((previous) => ({ ...previous, searchText: newValue }))}> {state.searchText === "" && state.items.length === 0 ? ( ) : ( state.items.map((item, index) => ) )} ); } ``` #### Props | Prop | Description | Type | Default | | ----------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | actions | A reference to an ActionPanel. | `React.ReactNode` | - | | description | An optional description for why the empty view is shown. | `string` | - | | icon | An icon displayed in the center of the EmptyView. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | title | The main title displayed for the Empty View. | `string` | - | ### Grid.Item A item in the [Grid](#grid). This is one of the foundational UI components of Raycast. A grid item represents a single entity. It can be an image, an emoji, a GIF etc. You most likely want to perform actions on this item, so make it clear\ to the user what this item is about. #### Example ```typescript import { Grid } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Props | Prop | Description | Type | Default | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | content\* | An image or color, optionally with a tooltip, representing the content of the grid item. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) or `{ color:` [`Color.ColorLike`](https://developers.raycast.com/api-reference/colors#color.colorlike) `}` or `{ tooltip: string; value: Image.ImageLike` or `{ color: Color.ColorLike; } }` | - | | accessory | An optional Grid.Item.Accessory item displayed underneath a Grid.Item. | [`Grid.Item.Accessory`](#grid.item.accessory) | - | | actions | An ActionPanel that will be updated for the selected grid item. | `React.ReactNode` | - | | id | ID of the item. This string is passed to the `onSelectionChange` handler of the Grid when the item is selected. Make sure to assign each item a unique ID or a UUID will be auto generated. | `string` | - | | keywords | An optional property used for providing additional indexable strings for search. When filtering the list in Raycast through the search bar, the keywords will be searched in addition to the title. | `string[]` | - | | quickLook | Optional information to preview files with Quick Look. Toggle the preview ith Action.ToggleQuickLook. | `{ name?: string; path: "fs".PathLike }` | - | | subtitle | An optional subtitle displayed below the title. | `string` | - | | title | An optional title displayed below the content. | `string` | - | ### Grid.Section A group of related [Grid.Item](#grid.item). Sections are a great way to structure your grid. For example, you can group photos taken in the same place or in the same day. This way, the user can quickly access what is most relevant. Sections can specify their own `columns`, `fit`, `aspectRatio` and `inset` props, separate from what is defined on the main [Grid](#grid) component. #### Example ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-274bdfb26a191e298c4248a6d7031d08d725f484%2Fgrid-styled-sections.webp?alt=media) {% tabs %} {% tab title="GridWithSection.tsx" %} ```typescript import { Grid } from "@raycast/api"; export default function Command() { return ( ); } ``` {% endtab %} {% tab title="GridWithStyledSection.tsx" %} ```typescript import { Grid, Color } from "@raycast/api"; export default function Command() { return ( {Object.entries(Color).map(([key, value]) => ( ))} ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ----------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------- | | aspectRatio | Aspect ratio for the Grid.Item elements. Defaults to 1. | `"1"` or `"3/2"` or `"2/3"` or `"4/3"` or `"3/4"` or `"16/9"` or `"9/16"` | - | | children | The Grid.Item elements of the section. | `React.ReactNode` | - | | columns | Column count for the section. Minimum value is 1, maximum value is 8. | `number` | - | | fit | Fit for the Grid.Item element content. Defaults to "contain" | [`Grid.Fit`](#grid.fit) | - | | inset | Inset for the Grid.Item element content. Defaults to "none". | [`Grid.Inset`](#grid.inset) | - | | subtitle | An optional subtitle displayed next to the title of the section. | `string` | - | | title | Title displayed above the section. | `string` | - | ## Types ### Grid.Item.Accessory An interface describing an accessory view in a `Grid.Item`. ![Grid.Item accessories illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-a7b63e239c1418d77e7ec242f0e34c711a44dd7d%2Fgrid-item-accessories.webp?alt=media) ### Grid.Inset An enum representing the amount of space there should be between a Grid Item's content and its borders. The absolute value depends on the value of [Grid](#grid)'s or [Grid.Section](#grid.section)'s `columns` prop. #### Enumeration members | Name | Description | | ------ | ------------- | | Small | Small insets | | Medium | Medium insets | | Large | Large insets | ### Grid.ItemSize (deprecated) An enum representing the size of the Grid's child [Grid.Item](#grid.item)s. #### Enumeration members | Name | Description | | ------ | --------------------- | | Small | Fits 8 items per row. | | Medium | Fits 5 items per row. | | Large | Fits 3 items per row. | ### Grid.Fit An enum representing how [Grid.Item](#grid.item)'s content should be fit. #### Enumeration members | Name | Description | | ------- | ------------------------------------------------------------------------------------------------------------------------------- | | Contain | The content will be contained within the grid cell, with vertical/horizontal bars if its aspect ratio differs from the cell's. | | Fill | The content will be scaled proportionally so that it fill the entire cell; parts of the content could end up being cropped out. | --- # Source: https://developers.raycast.com/examples/hacker-news.md # Hacker News {% hint style="info" %} The source code of the example can be found [here](https://github.com/raycast/extensions/tree/main/extensions/hacker-news#readme). You can install it [here](https://www.raycast.com/thomas/hacker-news). {% endhint %} Who doesn't like a good morning read on [Hacker News](https://news.ycombinator.com) with a warm coffee?! In this example, we create a simple list with the top stories on the frontpage. ![Example: Read frontpage of Hacker News](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-50ab2659c3f7430f15f90eb4dd6b7a6d6f0dd005%2Fexample-hacker-news.webp?alt=media) ## Load top stories First, let's get the latest top stories. For this we use a [RSS feed](https://hnrss.org): ```typescript import { Action, ActionPanel, List, showToast, Toast, Keyboard } from "@raycast/api"; import { useEffect, useState } from "react"; import Parser from "rss-parser"; const parser = new Parser(); interface State { items?: Parser.Item[]; error?: Error; } export default function Command() { const [state, setState] = useState({}); useEffect(() => { async function fetchStories() { try { const feed = await parser.parseURL("https://hnrss.org/frontpage?description=0&count=25"); setState({ items: feed.items }); } catch (error) { setState({ error: error instanceof Error ? error : new Error("Something went wrong"), }); } } fetchStories(); }, []); console.log(state.items); // Prints stories return ; } ``` Breaking this down: * We use a third-party dependency to parse the RSS feed and initially the parser. * We define our command state as a TypeScript interface. * We use [React's `useEffect`](https://reactjs.org/docs/hooks-effect.html) hook to parse the RSS feed after the command did mount. * We print the top stories to the console. * We render a list and show the loading indicator as long as we load the stories. ## Render stories Now that we got the data from Hacker News, we want to render the stories. For this, we create a new React component and a few helper functions that render a story: ```typescript function StoryListItem(props: { item: Parser.Item; index: number }) { const icon = getIcon(props.index + 1); const points = getPoints(props.item); const comments = getComments(props.item); return ( ); } const iconToEmojiMap = new Map([ [1, "1️⃣"], [2, "2️⃣"], [3, "3️⃣"], [4, "4️⃣"], [5, "5️⃣"], [6, "6️⃣"], [7, "7️⃣"], [8, "8️⃣"], [9, "9️⃣"], [10, "🔟"], ]); function getIcon(index: number) { return iconToEmojiMap.get(index) ?? "⏺"; } function getPoints(item: Parser.Item) { const matches = item.contentSnippet?.match(/(?<=Points:\s*)(\d+)/g); return matches?.[0]; } function getComments(item: Parser.Item) { const matches = item.contentSnippet?.match(/(?<=Comments:\s*)(\d+)/g); return matches?.[0]; } ``` To give the list item a nice look, we use a simple number emoji as icon, add the creator's name as subtitle and the points and comments as accessory title. Now we can render the ``: ```typescript export default function Command() { const [state, setState] = useState({}); // ... return ( {state.items?.map((item, index) => ( ))} ); } ``` ## Add actions When we select a story in the list, we want to be able to open it in the browser and also copy it's link so that we can share it in our watercooler Slack channel. For this, we create a new React Component: ```typescript function Actions(props: { item: Parser.Item }) { return ( {props.item.link && } {props.item.guid && } {props.item.link && ( )} ); } ``` The component takes a story and renders an [``](https://developers.raycast.com/api-reference/user-interface/action-panel) with our required actions. We add the actions to the ``: ```typescript function StoryListItem(props: { item: Parser.Item; index: number }) { // ... return ( } /> ); } ``` ## Handle errors Lastly, we want to be a good citizen and handle errors appropriately to guarantee a smooth experience. We'll show a toast whenever our network request fails: ```typescript export default function Command() { const [state, setState] = useState({}); // ... if (state.error) { showToast({ style: Toast.Style.Failure, title: "Failed loading stories", message: state.error.message, }); } // ... } ``` ## Wrapping up That's it, you have a working extension to read the frontpage of Hacker News. As next steps, you can add another command to show the jobs feed or add an action to copy a Markdown formatted link. --- # Source: https://developers.raycast.com/api-reference/feedback/hud.md # HUD When the user takes an action that has the side effect of closing Raycast (for example when copying something in the [Clipboard](https://developers.raycast.com/api-reference/clipboard)), you can use a HUD to confirm that the action worked properly. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-690446648e9c7bb76403f9d177ecfc8a3851ee8a%2Fhud.webp?alt=media) ## API Reference ### showHUD A HUD will automatically hide the main window and show a compact message at the bottom of the screen. #### Signature ```typescript async function showHUD( title: string, options?: { clearRootSearch?: boolean; popToRootType?: PopToRootType } ): Promise; ``` #### Example ```typescript import { showHUD } from "@raycast/api"; export default async function Command() { await showHUD("Hey there 👋"); } ``` `showHUD` closes the main window when called, so you can use the same options as `closeMainWindow`: ```typescript import { PopToRootType, showHUD } from "@raycast/api"; export default async function Command() { await showHUD("Hey there 👋", { clearRootSearch: true, popToRootType: PopToRootType.Immediate }); } ``` #### Parameters | Name | Description | Type | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | | title\* | The title that will be displayed in the HUD. | `string` | | options | Can be used to control the behaviour after closing the main window. | `Object` | | options.clearRootSearch | Clears the text in the root search bar and scrolls to the top | `boolean` | | options.popToRootType | Defines the pop to root behavior (PopToRootType); the default is to to respect the user's "Pop to Root Search" preference in Raycast | [`PopToRootType`](https://developers.raycast.com/window-and-search-bar#poptoroottype) | #### Return A Promise that resolves when the HUD is shown. --- # Source: https://developers.raycast.com/api-reference/user-interface/icons-and-images.md # Icons & Images ## API Reference ### Icon List of built-in icons that can be used for actions or list items. #### Example ```typescript import { Icon, List } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Enumeration members |


AddPerson

|


Airplane

|


AirplaneFilled

| | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | |


AirplaneLanding

|


AirplaneTakeoff

|


Airpods

| |


Alarm

|


AlarmRinging

|


AlignCentre

| |


AlignLeft

|


AlignRight

|


AmericanFootball

| |


Anchor

|


AppWindow

|


AppWindowGrid2x2

| |


AppWindowGrid3x3

|


AppWindowList

|


AppWindowSidebarLeft

| |


AppWindowSidebarRight

|


ArrowClockwise

|


ArrowCounterClockwise

| |


ArrowDown

|


ArrowDownCircle

|


ArrowDownCircleFilled

| |


ArrowLeft

|


ArrowLeftCircle

|


ArrowLeftCircleFilled

| |


ArrowNe

|


ArrowRight

|


ArrowRightCircle

| |


ArrowRightCircleFilled

|


ArrowUp

|


ArrowUpCircle

| |


ArrowUpCircleFilled

|


ArrowsContract

|


ArrowsExpand

| |


AtSymbol

|


BandAid

|


BankNote

| |


BarChart

|


BarCode

|


BathTub

| |


Battery

|


BatteryCharging

|


BatteryDisabled

| |


Bell

|


BellDisabled

|


Bike

| |


Binoculars

|


Bird

|


BlankDocument

| |


Bluetooth

|


Boat

|


Bold

| |


Bolt

|


BoltDisabled

|


Book

| |


Bookmark

|


Box

|


Brush

| |


Bubble

|


Bug

|


Building

| |


BulletPoints

|


BullsEye

|


BullsEyeMissed

| |


Buoy

|


Calculator

|


Calendar

| |


Camera

|


Car

|


Cart

| |


Cd

|


Center

|


Check

| |


CheckCircle

|


CheckList

|


CheckRosette

| |


Checkmark

|


ChessPiece

|


ChevronDown

| |


ChevronDownSmall

|


ChevronLeft

|


ChevronLeftSmall

| |


ChevronRight

|


ChevronRightSmall

|


ChevronUp

| |


ChevronUpDown

|


ChevronUpSmall

|


Circle

| |


CircleDisabled

|


CircleEllipsis

|


CircleFilled

| |


CircleProgress

|


CircleProgress100

|


CircleProgress25

| |


CircleProgress50

|


CircleProgress75

|


ClearFormatting

| |


Clipboard

|


Clock

|


Cloud

| |


CloudLightning

|


CloudRain

|


CloudSnow

| |


CloudSun

|


Code

|


CodeBlock

| |


Cog

|


Coin

|


Coins

| |


CommandSymbol

|


Compass

|


ComputerChip

| |


Contrast

|


CopyClipboard

|


CreditCard

| |


CricketBall

|


Crop

|


Crown

| |


Crypto

|


DeleteDocument

|


Desktop

| |


Devices

|


Dna

|


Document

| |


Dot

|


Download

|


Droplets

| |


Duplicate

|


EditShape

|


Eject

| |


Ellipsis

|


EllipsisVertical

|


Emoji

| |


EmojiSad

|


Envelope

|


Eraser

| |


ExclamationMark

|


Exclamationmark

|


Exclamationmark2

| |


Exclamationmark3

|


Eye

|


EyeDisabled

| |


EyeDropper

|


Female

|


FilmStrip

| |


Filter

|


Finder

|


Fingerprint

| |


Flag

|


Folder

|


Footprints

| |


Forward

|


ForwardFilled

|


FountainTip

| |


FullSignal

|


GameController

|


Gauge

| |


Gear

|


Geopin

|


Germ

| |


Gift

|


Glasses

|


Globe

| |


Goal

|


Hammer

|


HardDrive

| |


Hashtag

|


Heading

|


Headphones

| |


Heart

|


HeartDisabled

|


Heartbeat

| |


Highlight

|


Hourglass

|


House

| |


Humidity

|


Image

|


Important

| |


Info

|


Italics

|


Key

| |


Keyboard

|


Layers

|


Leaderboard

| |


Leaf

|


LevelMeter

|


LightBulb

| |


LightBulbOff

|


LineChart

|


Link

| |


List

|


Livestream

|


LivestreamDisabled

| |


Lock

|


LockDisabled

|


LockUnlocked

| |


Logout

|


Lorry

|


Lowercase

| |


MagnifyingGlass

|


Male

|


Map

| |


Mask

|


Maximize

|


MedicalSupport

| |


Megaphone

|


MemoryChip

|


MemoryStick

| |


Message

|


Microphone

|


MicrophoneDisabled

| |


Minimize

|


Minus

|


MinusCircle

| |


MinusCircleFilled

|


Mobile

|


Monitor

| |


Moon

|


MoonDown

|


MoonUp

| |


Moonrise

|


Mountain

|


Mouse

| |


Move

|


Mug

|


MugSteam

| |


Multiply

|


Music

|


Network

| |


NewDocument

|


NewFolder

|


Paperclip

| |


Paragraph

|


Patch

|


Pause

| |


PauseFilled

|


Pencil

|


Person

| |


PersonCircle

|


PersonLines

|


Phone

| |


PhoneRinging

|


PieChart

|


Pill

| |


Pin

|


PinDisabled

|


Play

| |


PlayFilled

|


Plug

|


Plus

| |


PlusCircle

|


PlusCircleFilled

|


PlusMinusDivideMultiply

| |


PlusSquare

|


PlusTopRightSquare

|


Power

| |


Print

|


QuestionMark

|


QuestionMarkCircle

| |


Quicklink

|


QuotationMarks

|


QuoteBlock

| |


Racket

|


Raindrop

|


RaycastLogoNeg

| |


RaycastLogoPos

|


Receipt

|


Redo

| |


RemovePerson

|


Repeat

|


Replace

| |


ReplaceOne

|


Reply

|


Rewind

| |


RewindFilled

|


Rocket

|


Rosette

| |


RotateAntiClockwise

|


RotateClockwise

|


Rss

| |


Ruler

|


SaveDocument

|


Shield

| |


ShortParagraph

|


Shuffle

|


Sidebar

| |


Signal0

|


Signal1

|


Signal2

| |


Signal3

|


Snippets

|


Snowflake

| |


SoccerBall

|


Speaker

|


SpeakerDown

| |


SpeakerHigh

|


SpeakerLow

|


SpeakerOff

| |


SpeakerOn

|


SpeakerUp

|


SpeechBubble

| |


SpeechBubbleActive

|


SpeechBubbleImportant

|


SquareEllipsis

| |


StackedBars1

|


StackedBars2

|


StackedBars3

| |


StackedBars4

|


Star

|


StarCircle

| |


StarDisabled

|


Stars

|


Stop

| |


StopFilled

|


Stopwatch

|


Store

| |


StrikeThrough

|


Sun

|


Sunrise

| |


Swatch

|


Switch

|


Syringe

| |


Tack

|


TackDisabled

|


Tag

| |


Temperature

|


TennisBall

|


Terminal

| |


Text

|


TextCursor

|


TextInput

| |


TextSelection

|


ThumbsDown

|


ThumbsDownFilled

| |


ThumbsUp

|


ThumbsUpFilled

|


Ticket

| |


Torch

|


Train

|


Trash

| |


Tray

|


Tree

|


Trophy

| |


TwoPeople

|


Umbrella

|


Underline

| |


Undo

|


Upload

|


Uppercase

| |


Video

|


VideoDisabled

|


Wallet

| |


Wand

|


Warning

|


Waveform

| |


Weights

|


Wifi

|


WifiDisabled

| |


Wind

|


Window

|


Windsock

| |


WrenchScrewdriver

|


WristWatch

|


XMarkCircle

| |


XMarkCircleFilled

|


XMarkCircleHalfDash

|


XMarkTopRightSquare

| |


Xmark

| | | ### Image.Mask Available masks that can be used to change the shape of an image. Can be handy to shape avatars or other items in a list. #### Example ```typescript import { Image, List } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Enumeration members | Name | Value | | ---------------- | ------------------ | | Circle | "circle" | | RoundedRectangle | "roundedRectangle" | ## Types ### Image Display different types of images, including network images or bundled assets. Apply image transforms to the source, such as a `mask` or a `tintColor`. {% hint style="info" %} Tip: Suffix your local assets with `@dark` to automatically provide a dark theme option, eg: `icon.png` and `icon@dark.png`. {% endhint %} #### Example ```typescript // Built-in icon const icon = Icon.Eye; // Built-in icon with tint color const tintedIcon = { source: Icon.Bubble, tintColor: Color.Red }; // Bundled asset with circular mask const avatar = { source: "avatar.png", mask: Image.Mask.Circle }; // Implicit theme-aware icon // with 'icon.png' and 'icon@dark.png' in the `assets` folder const icon = "icon.png"; // Explicit theme-aware icon const icon = { source: { light: "https://example.com/icon-light.png", dark: "https://example.com/icon-dark.png" } }; ``` #### Properties | Property | Description | Type | | ---------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | source\* | The Image.Source of the image. | [`Image.Source`](#image.source) | | fallback | Image.Fallback image, in case `source` can't be loaded. | [`Image.Fallback`](#image.fallback) | | mask | A Image.Mask to apply to the image. | [`Image.Mask`](#image.mask) | | tintColor | A Color.ColorLike to tint all the non-transparent pixels of the image. | [`Color.ColorLike`](https://developers.raycast.com/api-reference/colors#color.colorlike) | ### FileIcon An icon as it's used in the Finder. #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Properties | Property | Description | Type | | ------------------------------------------ | -------------------------------------------------- | -------- | | fileIcon\* | The path to a file or folder to get its icon from. | `string` | ### Image.ImageLike ```typescript ImageLike: URL | Asset | Icon | FileIcon | Image; ``` Union type for the supported image types. #### Example ```typescript import { Icon, Image, List } from "@raycast/api"; export default function Command() { return ( ); } ``` ### Image.Source ```typescript Image.Source: URL | Asset | Icon | { light: URL | Asset; dark: URL | Asset } ``` The source of an [Image](#image). Can be either a remote URL, a local file resource, a built-in [Icon](#icon) or a single emoji. For consistency, it's best to use the built-in [Icon](#icon) in lists, the Action Panel, and other places. If a specific icon isn't built-in, you can reference custom ones from the `assets` folder of the extension by file name, e.g. `my-icon.png`. Alternatively, you can reference an absolute HTTPS URL that points to an image or use an emoji. You can also specify different remote or local assets for light and dark theme. #### Example ```typescript import { Icon, List } from "@raycast/api"; export default function Command() { return ( ); } ``` ### Image.Fallback ```typescript Image.Fallback: Asset | Icon | { light: Asset; dark: Asset } ``` A fallback [Image](#image) that will be displayed in case the source image cannot be loaded. Can be either a local file resource, a built-in [Icon](#icon), a single emoji, or a theme-aware asset. Any specified `mask` or `tintColor` will also apply to the fallback image. #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( ); } ``` ### Image.URL Image is a string representing a URL. #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( ); } ``` ### Image.Asset Image is a string representing an asset from the `assets/` folder #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( ); } ``` --- # Source: https://developers.raycast.com/utilities/icons.md # Icons - [getAvatarIcon](/utilities/icons/getavataricon.md) - [getFavicon](/utilities/icons/getfavicon.md) - [getProgressIcon](/utilities/icons/getprogressicon.md) --- # Source: https://developers.raycast.com/basics/install-an-extension.md # Install an Extension All published extensions are discoverable in the Raycast Store. Use the [web interface](https://raycast.com/store) or the Store command to find what you're looking for. ## In-app Store The easiest way to discover extensions is the in-app store. Open the Store command in Raycast and search for an extension. Press `⌘` `↵` to install the selected extension or press `↵` to see more details about it. ![Store in Raycast](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-61b0b1e3be8f41d44c9b3b7464a431b43824bd03%2Fbasics-inapp-store.webp?alt=media) ## Web Store Alternatively, you can use our [web store](https://raycast.com/store). Press `⌘` `K` to open the command palette, search for an extension and open it. ![Web Store](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-4734fd8f31c97bb29fffbe7be8b532ad55214512%2Fbasics-web-store.webp?alt=media) Then press the Install Extension button in the top right corner and follow the steps in Raycast. ![Install extension from the Web Store](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-0a6cb5bb4070227ee51ef3e30ab727e23e22f0e3%2Fbasics-install-extension.webp?alt=media) ## Use installed extensions After an extension is installed, you can search for its commands in the root search. The extension can be further configured in the Extensions preferences tab. --- # Source: https://developers.raycast.com/api-reference/keyboard.md # Keyboard The Keyboard APIs are useful to make your actions accessible via the keyboard shortcuts. Shortcuts help users to use your command without touching the mouse. {% hint style="info" %} Use the [Common shortcuts](#keyboard.shortcut.common) whenever possible to keep a consistent user experience throughout Raycast. {% endhint %} ## Types ### Keyboard.Shortcut A keyboard shortcut is defined by one or more modifier keys (command, control, etc.) and a single key equivalent (a character or special key). See [KeyModifier](#keyboard.keymodifier) and [KeyEquivalent](#keyboard.keyequivalent) for supported values. #### Example ```typescript import { Action, ActionPanel, Detail, Keyboard } from "@raycast/api"; export default function Command() { return ( console.log("Go up")} /> console.log("Go down")} /> console.log("Go left")} /> console.log("Go right")} /> console.log("Open")} />
} /> ); } ``` #### Properties | Property | Description | Type | | ------------------------------------------- | ------------------------------------------- | --------------------------------------------------- | | key\* | The key of the keyboard shortcut. | [`Keyboard.KeyEquivalent`](#keyboard.keyequivalent) | | modifiers\* | The modifier keys of the keyboard shortcut. | [`Keyboard.KeyModifier`](#keyboard.keymodifier)`[]` | If the shortcut contains some "ambiguous" modifiers (eg. `ctrl`, or `cmd`, or `windows`), you will need to specify the shortcut for both platforms: ```js { macOS: { modifiers: ["cmd", "shift"], key: "c" }, Windows: { modifiers: ["ctrl", "shift"], key: "c" }, } ``` ### Keyboard.Shortcut.Common A collection of shortcuts that are commonly used throughout Raycast. Using them should help provide a more consistent experience and preserve muscle memory. | Name | macOS | Windows | | --------------- | --------- | -------------------- | | Copy | ⌘ + ⇧ + C | `ctrl` + `shift` + C | | CopyDeeplink | ⌘ + ⇧ + C | `ctrl` + `shift` + C | | CopyName | ⌘ + ⇧ + . | `ctrl` + `alt` + C | | CopyPath | ⌘ + ⇧ + , | `alt` + `shift` + C | | Save | ⌘ + S | `ctrl` + S | | Duplicate | ⌘ + D | `ctrl` + `shift` + S | | Edit | ⌘ + E | `ctrl` + E | | MoveDown | ⌘ + ⇧ + ↓ | `ctrl` + `shift` + ↓ | | MoveUp | ⌘ + ⇧ + ↑ | `ctrl` + `shift` + ↑ | | New | ⌘ + N | `ctrl` + N | | Open | ⌘ + O | `ctrl` + O | | OpenWith | ⌘ + ⇧ + O | `ctrl` + `shift` + O | | Pin | ⌘ + ⇧ + P | `ctrl` + . | | Refresh | ⌘ + R | `ctrl` + R | | Remove | ⌃ + X | `ctrl` + D | | RemoveAll | ⌃ + ⇧ + X | `ctrl` + `shift` + D | | ToggleQuickLook | ⌘ + Y | `ctrl` + Y | ### Keyboard.KeyEquivalent ```typescript KeyEquivalent: "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "." | "," | ";" | "=" | "+" | "-" | "[" | "]" | "{" | "}" | "«" | "»" | "(" | ")" | "/" | "\\" | "'" | "`" | "§" | "^" | "@" | "$" | "return" | "delete" | "deleteForward" | "tab" | "arrowUp" | "arrowDown" | "arrowLeft" | "arrowRight" | "pageUp" | "pageDown" | "home" | "end" | "space" | "escape" | "enter" | "backspace"; ``` KeyEquivalent of a [Shortcut](#keyboard.shortcut) ### Keyboard.KeyModifier ```typescript KeyModifier: "cmd" | "ctrl" | "opt" | "shift" | "alt" | "windows"; ``` Modifier of a [Shortcut](#keyboard.shortcut). Note that `"alt"` and `"opt"` are the same key, they are just named differently on macOS and Windows. --- # Source: https://developers.raycast.com/ai/learn-core-concepts-of-ai-extensions.md # Learn Core Concepts of AI Extensions AI Extensions rely on three core concepts: Tools, Instructions, and Evals. Each of these concepts plays a crucial role in the development of AI Extensions. Let's take a closer look at each of them. ## Tools To turn a regular extension into an AI extension, you need to add a set of tools that allow Raycast AI to interact with your extension. A tool is a function that takes an input and returns a value. Here's an example of a simple tool: ```typescript export default function tool() { return "Hello, world!"; } ``` ### Inputs Tools can take an input. For example, a `greet` tool takes a `name` as an input and returns a greeting to the user. ```typescript type Input = { name: string; }; export default function tool(input: Input) { return `Hello, ${input.name}!`; } ``` Those inputs can be used to provide more context to the tool. For example, you can pass a title, a description, and a due date to a `createTask` tool. ```typescript type Input = { /** * The title of the task */ title: string; /** * The description of the task */ description?: string; /** * The due date of the task in ISO 8601 format */ dueDate?: string; }; export default function tool(input: Input) { // ... create the task } ``` {% hint style="info" %} A tool expects a single object as its input. {% endhint %} ### Descriptions To better teach AI how to use your tools, you can add descriptions as JSDoc comments (eg. `/** ... */`) to tools and their inputs. The better you describe your tools, the more likely AI is to use them correctly. ```typescript type Input = { /** * The first name of the user to greet */ name: string; }; /** * Greet the user with a friendly message */ export default function tool(input: Input) { return `Hello, ${input.name}!`; } ``` ### Confirmations Sometimes you want to keep the human in the loop. For example, you can ask the user to confirm an action before it is executed. For this, you can export a `confirmation` function. ```typescript import { Tool } from "@raycast/api"; type Input = { /** * The first name of the user to greet */ name: string; }; export const confirmation: Tool.Confirmation = async (input) => { return { message: `Are you sure you want to greet ${input.name}?`, }; }; /** * Greet the user with a friendly message */ export default function tool(input: Input) { return `Hello, ${input.name}!`; } ``` The `confirmation` function is called before the actual tool is executed. If the user confirms, the tool is executed afterwards. If the user cancels, the tool is not executed. You can customize the confirmation further by providing details about the action that needs to be confirmed. See [Tool Reference](https://developers.raycast.com/api-reference/tool) for more information. ## Instructions Sometimes you want to provide additional instructions to the AI that are not specific to a single tool but to the entire AI extension. For example, you can provide a list of do's and don'ts for the AI to follow. Those are defined in the [`package.json` file](https://developers.raycast.com/information/manifest) under the `ai` key. ```json { "ai": { "instructions": "When you don't know the user's first name, ask for it." } } ``` A user can use multiple AI Extensions in a conversation. Therefore, you should make sure that your instructions don't conflict with the instructions of other AI Extensions. For example, avoid phrases like "You are a ... assistant" because other AI Extensions might provide a different skill set. Instead, you should focus on providing general instructions that describe the specifics of your AI Extension. For example, describe the relationship between issues, projects, and teams for a project management app. ## Evals Evals are a way to test your AI extension. Think of them as integrations tests. They are defined in the [`package.json` file](https://developers.raycast.com/information/manifest) under the `ai` key. They are also used as suggested prompts for the user to learn how to make the most out of your AI Extension. ## Structure An eval consists of the following parts: * `input` is a text prompt that you expect from users of your AI Extension. It should include `@` mention the name of your extension (`name` from `package.json`). * `mocks` – mocked results of tool calls. It is required to give AI the context, i.e. if you write an eval for `@todo-list What are my todos?` you need to provide the actual list in `get-todos` mock. * `expected` – array of expectations, similar to `expect` statements in unit / integration tests. * `usedAsExample` – if true, the eval will be used as an example prompt for the user. True by default. ## Expectations Expectations are used to check if the AI response matches the expected behavior. You have different options to choose from: * `includes`: Check that AI response includes some substring (case-insensitive), for example `{"includes": "added" }` * `matches`: Check that AI response matches some regexp, for example check if response contains a Markdown link `{ "matches": "\\[([^\\]]+)\\]\\(([^\\s\\)]+)(?:\\s+\"([^\"]+)\")?\\)" }` * `meetsCriteria`: Check that AI response meets some plain-text criteria (validated using AI). Useful when AI varies the response and it is hard to match it using `includes` or `matches`. Example: `{ "meetsCriteria": "Tells that label with this name doesn't exist" }` * `callsTool`: Check that during the request AI called some tool included from your AI extension. There are two forms: * Short form to check if AI tool with specific name was called. Example: `{ "callsTool": "get-todos" }` * Long form to check tool arguments: `{ callsTool: { name: "name", arguments: { arg1: matcher, arg2: matcher } } }`. Matches could be complex and combine any supported rules: * `eq` (used by default for any value that is not object or array) * `includes` * `matches` * `and` (used by default if array is used) * `or` * `not` #### Example {% tabs %} {% tab title="Simple Expectation" %} ```json { "ai": { "evals": [ { "expected": [ { "callsTool": { "name": "greet", "arguments": { "name": "thomas" } } } ] } ] } } ``` {% endtab %} {% tab title="Nested Expectations" %} ```json { "ai": { "evals": [ { "expected": [ { "callsTool": { "name": "create-comment", "arguments": { "issueId": "ISS-1", "body": { "includes": "waiting for design" } } } } ] } ] } } ``` {% endtab %} {% tab title="Nested Expectations With Dot Notation" %} ```json { "ai": { "evals": [ { "expected": [ { "callsTool": { "name": "greet", "arguments": { "user.name": "thomas" } } } ] } ] } } ``` {% endtab %} {% tab title="Negative Expectation" %} ```json { "ai": { "evals": [ { "expected": [ { "not": { "callsTool": "create-issue" } } ] } ] } } ``` {% endtab %} {% endtabs %} ## AI File If your instructions or evals start getting too long and clutter your `package.json` file, you can move them to a separate file. It can be either a `ai.json`, `ai.yaml`, or `ai.json5` file in the root of your extension next to the `package.json` file. The structure of the AI file is the same as in the `package.json` file. {% tabs %} {% tab title="ai.json" %} ```json { "instructions": "When you don't know the user's first name, ask for it." } ``` {% endtab %} {% tab title="ai.yaml" %} ```yaml instructions: | When you don't know the user's first name, ask for it. ``` {% endtab %} {% tab title="ai.json5" %} ```json5 { instructions: "When you don't know the user's first name, ask for it.", } ``` {% endtab %} {% endtabs %} {% hint style="info" %} The AI file is optional. If you don't provide it, Raycast will use the instructions and evals from the `package.json` file. We found that [`yaml`](https://yaml.org/) and [`json5`](https://json5.org/) can be more readable for long instructions. {% endhint %} --- # Source: https://developers.raycast.com/information/lifecycle.md # Lifecycle A command is typically launched, runs for a while, and then is unloaded. ## Launch When a command is launched in Raycast, the command code is executed right away. If the extension exports a default function, this function will automatically be called. If you return a React component in the exported default function, it will automatically be rendered as the root component. For commands that don't need a user interface (`mode` property set to "`no-view"` in the manifest), you can export an async function and perform API methods using async/await. {% tabs %} {% tab title="View Command" %} ```typescript import { Detail } from "@raycast/api"; // Returns the main React component for a view command export default function Command() { return ; } ``` {% endtab %} {% tab title="No-View Command" %} ```typescript import { showHUD } from "@raycast/api"; // Runs async. code in a no-view command export default async function Command() { await showHUD("Hello"); } ``` {% endtab %} {% endtabs %} There are different ways to launch a command: * The user searches for the command in the root search and executes it. * The user registers an alias for the command and presses it. * Another command launches the command *via* [`launchCommand`](https://developers.raycast.com/api-reference/command#launchcommand). * The command was launched in the [background](https://developers.raycast.com/information/lifecycle/background-refresh). * A [Form's Draft](https://developers.raycast.com/api-reference/user-interface/form#drafts) was saved and the user executes it. * A user registers the command as a [fallback command](https://manual.raycast.com/fallback-commands) and executes it when there are no results in the root search. * A user clicks a [Deeplink](https://developers.raycast.com/information/lifecycle/deeplinks) Depending on how the command was launched, different arguments will be passed to the exported default function. ```typescript import { Detail, LaunchProps } from "@raycast/api"; // Access the different launch properties via the argument passed to the function export default function Command(props: LaunchProps) { return ; } ``` ### LaunchProps | Property | Description | Type | | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | arguments\* | Use these values to populate the initial state for your command. | [`Arguments`](https://developers.raycast.com/information/arguments#arguments) | | launchType\* | The type of launch for the command (user initiated or background). | [`LaunchType`](https://developers.raycast.com/api-reference/environment#launchtype) | | draftValues | When a user enters the command via a draft, this object will contain the user inputs that were saved as a draft. Use its values to populate the initial state for your Form. | [`Form.Values`](https://developers.raycast.com/api-reference/user-interface/form#form.values) | | fallbackText | When the command is launched as a fallback command, this string contains the text of the root search. | `string` | | launchContext | When the command is launched programmatically via `launchCommand`, this object contains the value passed to `context`. | [`LaunchContext`](https://developers.raycast.com/api-reference/command#launchcontext) | ## Unloading When the command is unloaded (typically by popping back to root search for view commands or after the script finishes for no-view commands), Raycast unloads the entire command from memory. Note that there are memory limits for commands, and if those limits are exceeded, the command gets terminated, and users will see an error message. --- # Source: https://developers.raycast.com/api-reference/user-interface/list.md # List Our `List` component provides great user experience out of the box: * Use built-in filtering for best performance. * Group-related items in sections with titles and subtitles. * Show loading indicator for longer operations. * Use the search query for typeahead experiences, optionally throttled. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-d9ff7a3c3f01cd1e09f7931c91be432700f545d9%2Flist.webp?alt=media) ## Search Bar The search bar allows users to interact quickly with list items. By default, [List.Items](#list.item) are displayed if the user's input can be (fuzzy) matched to the item's `title` or `keywords`. ### Custom filtering Sometimes, you may not want to rely on Raycast's filtering, but use/implement your own. If that's the case, you can set the `List`'s `filtering` [prop](#props) to false, and the items displayed will be independent of the search bar's text.\ Note that `filtering` is also implicitly set to false if an `onSearchTextChange` listener is specified. If you want to specify a change listener and *still* take advantage of Raycast's built-in filtering, you can explicitly set `filtering` to true. ```typescript import { useEffect, useState } from "react"; import { Action, ActionPanel, List } from "@raycast/api"; const items = ["Augustiner Helles", "Camden Hells", "Leffe Blonde", "Sierra Nevada IPA"]; export default function Command() { const [searchText, setSearchText] = useState(""); const [filteredList, filterList] = useState(items); useEffect(() => { filterList(items.filter((item) => item.includes(searchText))); }, [searchText]); return ( {filteredList.map((item) => ( console.log(`${item} selected`)} /> } /> ))} ); } ``` ### Programmatically updating the search bar Other times, you may want the content of the search bar to be updated by the extension, for example, you may store a list of the user's previous searches and, on the next visit, allow them to "continue" where they left off. To do so, you can use the `searchText` [prop](#props). ```typescript import { useEffect, useState } from "react"; import { Action, ActionPanel, List } from "@raycast/api"; const items = ["Augustiner Helles", "Camden Hells", "Leffe Blonde", "Sierra Nevada IPA"]; export default function Command() { const [searchText, setSearchText] = useState(""); return ( {items.map((item) => ( setSearchText(item)} /> } /> ))} ); } ``` ### Dropdown Some extensions may benefit from giving users a second filtering dimension. A todo extension may allow users to use different groups, a newspaper-reading extension may want to allow quickly switching categories, etc. This is where the `searchBarAccessory` [prop](#props) is useful. Pass it a [List.Dropdown](#list.dropdown) component, and it will be displayed on the right-side of the search bar. Invoke it either by using the global shortcut `⌘` `P` or by clicking on it. ### Pagination {% hint style="info" %} Pagination requires version 1.69.0 or higher of the `@raycast/api` package. {% endhint %} `List`s have built-in support for pagination. To opt in to pagination, you need to pass it a `pagination` prop, which is an object providing 3 pieces of information: * `onLoadMore` - will be called by Raycast when the user reaches the end of the list, either using the keyboard or the mouse. When it gets called, the extension is expected to perform an async operation which eventually can result in items being appended to the end of the list. * `hasMore` - indicates to Raycast whether it *should* call `onLoadMore` when the user reaches the end of the list. * `pageSize` - indicates how many placeholder items Raycast should add to the end of the list when it calls `onLoadMore`. Once `onLoadMore` finishes executing, the placeholder items will be replaced by the newly-added list items. Note that extensions have access to a limited amount of memory. As your extension paginates, its memory usage will increase. Paginating extensively could lead to the extension eventually running out of memory and crashing. To protect against the extension crashing due to memory exhaustion, Raycast monitors the extension's memory usage and employs heuristics to determine whether it's safe to paginate further. If it's deemed unsafe to continue paginating, `onLoadMore` will not be triggered when the user scrolls to the bottom, regardless of the `hasMore` value. Additionally, during development, a warning will be printed in the terminal. For convenience, most of the [hooks](https://developers.raycast.com/utilities/getting-started) that we provide have built-in pagination support. Here's an example of how to add pagination support to a simple command using [usePromise](https://developers.raycast.com/utilities/react-hooks/usepromise), and one "from scratch". {% tabs %} {% tab title="ListWithUsePromisePagination.tsx" %} ```typescript import { setTimeout } from "node:timers/promises"; import { useState } from "react"; import { List } from "@raycast/api"; import { usePromise } from "@raycast/utils"; export default function Command() { const [searchText, setSearchText] = useState(""); const { isLoading, data, pagination } = usePromise( (searchText: string) => async (options: { page: number }) => { await setTimeout(200); const newData = Array.from({ length: 25 }, (_v, index) => ({ index, page: options.page, text: searchText, })); return { data: newData, hasMore: options.page < 10 }; }, [searchText] ); return ( {data?.map((item) => ( ))} ); } ``` {% endtab %} {% tab title="ListWithPagination.tsx" %} ```typescript import { setTimeout } from "node:timers/promises"; import { useCallback, useEffect, useRef, useState } from "react"; import { List } from "@raycast/api"; type State = { searchText: string; isLoading: boolean; hasMore: boolean; data: { index: number; page: number; text: string; }[]; nextPage: number; }; const pageSize = 20; export default function Command() { const [state, setState] = useState({ searchText: "", isLoading: true, hasMore: true, data: [], nextPage: 0 }); const cancelRef = useRef(null); const loadNextPage = useCallback(async (searchText: string, nextPage: number, signal?: AbortSignal) => { setState((previous) => ({ ...previous, isLoading: true })); await setTimeout(500); const newData = Array.from({ length: pageSize }, (_v, index) => ({ index, page: nextPage, text: searchText, })); if (signal?.aborted) { return; } setState((previous) => ({ ...previous, data: [...previous.data, ...newData], isLoading: false, hasMore: nextPage < 10, })); }, []); const onLoadMore = useCallback(() => { setState((previous) => ({ ...previous, nextPage: previous.nextPage + 1 })); }, []); const onSearchTextChange = useCallback( (searchText: string) => { if (searchText === state.searchText) return; setState((previous) => ({ ...previous, data: [], nextPage: 0, searchText, })); }, [state.searchText] ); useEffect(() => { cancelRef.current?.abort(); cancelRef.current = new AbortController(); loadNextPage(state.searchText, state.nextPage, cancelRef.current?.signal); return () => { cancelRef.current?.abort(); }; }, [loadNextPage, state.searchText, state.nextPage]); return ( {state.data.map((item) => ( ))} ); } ``` {% endtab %} {% endtabs %} {% hint style="warning" %} Pagination might not work properly if all list items are rendered and visible at once, as `onLoadMore` won't be triggered. This typically happens when an API returns 10 results by default, all fitting within the Raycast window. To fix this, try displaying more items, like 20. {% endhint %} ## Examples {% tabs %} {% tab title="List.tsx" %} ```jsx import { List } from "@raycast/api"; export default function Command() { return ( ); } ``` {% endtab %} {% tab title="ListWithSections.tsx" %} ```jsx import { List } from "@raycast/api"; export default function Command() { return ( ); } ``` {% endtab %} {% tab title="ListWithActions.tsx" %} ```jsx import { ActionPanel, Action, List } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` {% endtab %} {% tab title="ListWithDetail.tsx" %} ```jsx import { useState } from "react"; import { Action, ActionPanel, List } from "@raycast/api"; import { useCachedPromise } from "@raycast/utils"; interface Pokemon { name: string; height: number; weight: number; id: string; types: string[]; abilities: Array<{ name: string; isMainSeries: boolean }>; } const pokemons: Pokemon[] = [ { name: "bulbasaur", height: 7, weight: 69, id: "001", types: ["Grass", "Poison"], abilities: [ { name: "Chlorophyll", isMainSeries: true }, { name: "Overgrow", isMainSeries: true }, ], }, { name: "ivysaur", height: 10, weight: 130, id: "002", types: ["Grass", "Poison"], abilities: [ { name: "Chlorophyll", isMainSeries: true }, { name: "Overgrow", isMainSeries: true }, ], }, ]; export default function Command() { const [showingDetail, setShowingDetail] = useState(true); const { data, isLoading } = useCachedPromise(() => new Promise((resolve) => resolve(pokemons))); return ( {data && data.map((pokemon) => { const props: Partial = showingDetail ? { detail: ( ), } : { accessories: [{ text: pokemon.types.join(" ") }] }; return ( setShowingDetail(!showingDetail)} /> } /> ); })} ); } ``` {% endtab %} {% tab title="ListWithEmptyView\.tsx" %} ```typescript import { useEffect, useState } from "react"; import { List } from "@raycast/api"; export default function CommandWithCustomEmptyView() { const [state, setState] = useState({ searchText: "", items: [] }); useEffect(() => { // perform an API call that eventually populates `items`. }, [state.searchText]); return ( setState((previous) => ({ ...previous, searchText: newValue }))}> {state.searchText === "" && state.items.length === 0 ? ( ) : ( state.items.map((item) => ) )} ); } ``` {% endtab %} {% endtabs %} ## API Reference ### List Displays [List.Section](#list.section) or [List.Item](#list.item). The list uses built-in filtering by indexing the title of list items and additionally keywords. #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Props | Prop | Description | Type | Default | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------- | | actions | A reference to an ActionPanel. It will only be shown when there aren't any children. | `React.ReactNode` | - | | children | List sections or items. If List.Item elements are specified, a default section is automatically created. | `React.ReactNode` | - | | filtering | Toggles Raycast filtering. When `true`, Raycast will use the query in the search bar to filter the items. When `false`, the extension needs to take care of the filtering. You can further define how native filtering orders sections by setting an object with a `keepSectionOrder` property: When `true`, ensures that Raycast filtering maintains the section order as defined in the extension. When `false`, filtering may change the section order depending on the ranking values of items. | `boolean` or `{ keepSectionOrder: boolean }` | - | | isLoading | Indicates whether a loading bar should be shown or hidden below the search bar | `boolean` | - | | isShowingDetail | Whether the List should have an area on the right side of the items to show additional details about the selected item. When true, it is recommended not to show any accessories on the `List.Item` and instead show the additional information in the `List.Item.Detail` view. | `boolean` | - | | navigationTitle | The main title for that view displayed in Raycast | `string` | - | | onSearchTextChange | Callback triggered when the search bar text changes. | `(text: string) => void` | - | | onSelectionChange | Callback triggered when the item selection in the list changes. When the received id is `null`, it means that all items have been filtered out and that there are no item selected | `(id: string) => void` | - | | pagination | Configuration for pagination | `{ hasMore: boolean; onLoadMore: () => void; pageSize: number }` | - | | searchBarAccessory | List.Dropdown that will be shown in the right-hand-side of the search bar. | `ReactElement<`[`List.Dropdown.Props`](#props)`, string>` | - | | searchBarPlaceholder | Placeholder text that will be shown in the search bar. | `string` | - | | searchText | The text that will be displayed in the search bar. | `string` | - | | selectedItemId | Selects the item with the specified id. | `string` | - | | throttle | Defines whether the `onSearchTextChange` handler will be triggered on every keyboard press or with a delay for throttling the events. Recommended to set to `true` when using custom filtering logic with asynchronous operations (e.g. network requests). | `boolean` | - | ### List.Dropdown A dropdown menu that will be shown in the right-hand-side of the search bar. #### Example ```typescript import { List } from "@raycast/api"; type DrinkType = { id: string; name: string }; function DrinkDropdown(props: { drinkTypes: DrinkType[]; onDrinkTypeChange: (newValue: string) => void }) { const { drinkTypes, onDrinkTypeChange } = props; return ( { onDrinkTypeChange(newValue); }} > {drinkTypes.map((drinkType) => ( ))} ); } export default function Command() { const drinkTypes: DrinkType[] = [ { id: "1", name: "Beer" }, { id: "2", name: "Wine" }, ]; const onDrinkTypeChange = (newValue: string) => { console.log(newValue); }; return ( } > ); } ``` #### Props | Prop | Description | Type | Default | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ------- | | tooltip\* | Tooltip displayed when hovering the dropdown. | `string` | - | | children | Dropdown sections or items. If Dropdown.Item elements are specified, a default section is automatically created. | `React.ReactNode` | - | | defaultValue | The default value of the dropdown. Keep in mind that `defaultValue` will be configured once per component lifecycle. This means that if a user changes the value, `defaultValue` won't be configured on re-rendering. **If you're using `storeValue` and configured it as `true`** ***and***** a Dropdown.Item with the same value exists, then it will be selected.** **If you configure `value` at the same time as `defaultValue`, the `value` will have precedence over `defaultValue`.** | `string` | - | | filtering | Toggles Raycast filtering. When `true`, Raycast will use the query in the search bar to filter the items. When `false`, the extension needs to take care of the filtering. You can further define how native filtering orders sections by setting an object with a `keepSectionOrder` property: When `true`, ensures that Raycast filtering maintains the section order as defined in the extension. When `false`, filtering may change the section order depending on the ranking values of items. | `boolean` or `{ keepSectionOrder: boolean }` | - | | id | ID of the dropdown. | `string` | - | | isLoading | Indicates whether a loading indicator should be shown or hidden next to the search bar | `boolean` | - | | onChange | Callback triggered when the dropdown selection changes. | `(newValue: string) => void` | - | | onSearchTextChange | Callback triggered when the search bar text changes. | `(text: string) => void` | - | | placeholder | Placeholder text that will be shown in the dropdown search field. | `string` | - | | storeValue | Indicates whether the value of the dropdown should be persisted after selection, and restored next time the dropdown is rendered. | `boolean` | - | | throttle | Defines whether the `onSearchTextChange` handler will be triggered on every keyboard press or with a delay for throttling the events. Recommended to set to `true` when using custom filtering logic with asynchronous operations (e.g. network requests). | `boolean` | - | | value | The currently value of the dropdown. | `string` | - | ### List.Dropdown.Item A dropdown item in a [List.Dropdown](#list.dropdown) #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( } > ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | title\* | The title displayed for the item. | `string` | - | | value\* | Value of the dropdown item. Make sure to assign each unique value for each item. | `string` | - | | icon | An optional icon displayed for the item. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | keywords | An optional property used for providing additional indexable strings for search. When filtering the items in Raycast, the keywords will be searched in addition to the title. | `string[]` | - | ### List.Dropdown.Section Visually separated group of dropdown items. Use sections to group related menu items together. #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( } > ); } ``` #### Props | Prop | Description | Type | Default | | -------- | --------------------------------- | ----------------- | ------- | | children | The item elements of the section. | `React.ReactNode` | - | | title | Title displayed above the section | `string` | - | ### List.EmptyView A view to display when there aren't any items available. Use to greet users with a friendly message if the\ extension requires user input before it can show any list items e.g. when searching for a package, an article etc. Raycast provides a default `EmptyView` that will be displayed if the List component either has no children,\ or if it has children, but none of them match the query in the search bar. This too can be overridden by passing an\ empty view alongside the other `List.Item`s. Note that the `EmptyView` is *never* displayed if the `List`'s `isLoading` property is true and the search bar is empty. ![List EmptyView illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-b6810472c5725620a5d412613fdd084fd3b77902%2Flist-empty-view.webp?alt=media) #### Example ```typescript import { useEffect, useState } from "react"; import { List } from "@raycast/api"; export default function CommandWithCustomEmptyView() { const [state, setState] = useState({ searchText: "", items: [] }); useEffect(() => { // perform an API call that eventually populates `items`. }, [state.searchText]); return ( setState((previous) => ({ ...previous, searchText: newValue }))}> {state.searchText === "" && state.items.length === 0 ? ( ) : ( state.items.map((item) => ) )} ); } ``` #### Props | Prop | Description | Type | Default | | ----------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | actions | A reference to an ActionPanel. | `React.ReactNode` | - | | description | An optional description for why the empty view is shown. | `string` | - | | icon | An icon displayed in the center of the EmptyView. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | title | The main title displayed for the Empty View. | `string` | - | ### List.Item A item in the [List](#list). This is one of the foundational UI components of Raycast. A list item represents a single entity. It can be a\ GitHub pull request, a file, or anything else. You most likely want to perform actions on this item, so make it clear\ to the user what this list item is about. #### Example ```typescript import { Icon, List } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | title\* | The main title displayed for that item, optionally with a tooltip. | `string` or `{ tooltip?: string; value: string }` | - | | accessories | An optional array of List.Item.Accessory items displayed on the right side in a List.Item. | [`List.Item.Accessory`](#list.item.accessory)`[]` | - | | actions | An ActionPanel that will be updated for the selected list item. | `React.ReactNode` | - | | detail | The `List.Item.Detail` to be rendered in the right side area when the parent List is showing details and the item is selected. | `React.ReactNode` | - | | icon | An optional icon displayed for the list item. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) or `{ tooltip: string; value:` [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) `}` | - | | id | ID of the item. This string is passed to the `onSelectionChange` handler of the List when the item is selected. Make sure to assign each item a unique ID or a UUID will be auto generated. | `string` | - | | keywords | An optional property used for providing additional indexable strings for search. When filtering the list in Raycast through the search bar, the keywords will be searched in addition to the title. | `string[]` | - | | quickLook | Optional information to preview files with Quick Look. Toggle the preview with Action.ToggleQuickLook. | `{ name?: string; path: "fs".PathLike }` | - | | subtitle | An optional subtitle displayed next to the main title, optionally with a tooltip. | `string` or `{ tooltip?: string; value?: string }` | - | ### List.Item.Detail A Detail view that will be shown in the right-hand-side of the `List`. When shown, it is recommended not to show any accessories on the `List.Item` and instead bring those additional information in the `List.Item.Detail` view. ![List-detail illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-d310e56b42ce2e77b85ecc5909b8bcbe12fa112f%2Flist-detail.webp?alt=media) #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( } /> ); } ``` #### Props | Prop | Description | Type | Default | | --------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------- | | isLoading | Indicates whether a loading bar should be shown or hidden above the detail | `boolean` | - | | markdown | The CommonMark string to be rendered in the right side area when the parent List is showing details and the item is selected. | `string` | - | | metadata | The `List.Item.Detail.Metadata` to be rendered in the bottom side of the `List.Item.Detail` | `React.ReactNode` | - | ### List.Item.Detail.Metadata A Metadata view that will be shown in the bottom side of the `List.Item.Detail`. Use it to display additional structured data about the content of the `List.Item`. #### Example {% tabs %} {% tab title="Metadata + Markdown" %} ![List Detail-metadata illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-4c02baa9f1d9292a03aa094018b662872ce6e942%2Flist-detail-metadata-split.webp?alt=media) ```typescript import { List } from "@raycast/api"; export default function Metadata() { const markdown = ` ![Illustration](https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png) There is a plant seed on its back right from the day this Pokémon is born. The seed slowly grows larger. `; return ( } /> } /> ); } ``` {% endtab %} {% tab title="Metadata Standalone" %} ![List Detail-metadata illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-81529bdb81a2efffcd13a86e8ea913f5cb78a18f%2Flist-detail-metadata-standalone.webp?alt=media) ```typescript import { List } from "@raycast/api"; export default function Metadata() { return ( } /> } /> ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | ------------------------------------------ | ---------------------------------- | ----------------- | ------- | | children\* | The elements of the Metadata view. | `React.ReactNode` | - | ### List.Item.Detail.Metadata.Label A title with, optionally, an icon and/or text to its right. ![List Detail-metadata-label illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-d918c5dc5d2c7eaee3011fd542f21afe88b629ce%2Flist-detail-metadata-label.webp?alt=media) #### Example ```typescript import { List } from "@raycast/api"; export default function Metadata() { return ( } /> } /> ); } ``` #### Props | Prop | Description | Type | Default | | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------- | | title\* | The title of the item. | `string` | - | | icon | An icon to illustrate the value of the item. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | text | The text value of the item. Specifying `color` will display the text in the provided color. Defaults to Color.PrimaryText. | `string` or `{ color?:` [`Color`](https://developers.raycast.com/api-reference/colors#color)`; value: string }` | - | ### List.Item.Detail.Metadata.Link An item to display a link. ![List Detail-metadata-link illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-04a9b7dc4177f80744d123856c2b5aaae9c1f694%2Flist-detail-metadata-link.webp?alt=media) #### Example ```typescript import { List } from "@raycast/api"; export default function Metadata() { return ( } /> } /> ); } ``` #### Props | Prop | Description | Type | Default | | ---------------------------------------- | ------------------------------- | -------- | ------- | | target\* | The target of the link. | `string` | - | | text\* | The text value of the item. | `string` | - | | title\* | The title shown above the item. | `string` | - | ### List.Item.Detail.Metadata.TagList A list of [`Tags`](#list.item.detail.metadata.taglist.item) displayed in a row. ![List Detail-metadata-tag-list illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-2dee98df60c54a79f38571f7109a787da32dc0e1%2Flist-detail-metadata-tag-list.webp?alt=media) #### Example ```typescript import { List } from "@raycast/api"; export default function Metadata() { return ( } /> } /> ); } ``` #### Props | Prop | Description | Type | Default | | ------------------------------------------ | ---------------------------------- | ----------------- | ------- | | children\* | The tags contained in the TagList. | `React.ReactNode` | - | | title\* | The title shown above the item. | `string` | - | ### List.Item.Detail.Metadata.TagList.Item A Tag in a `List.Item.Detail.Metadata.TagList`. #### Props | Prop | Description | Type | Default | | -------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------- | | color | Changes the text color to the provided color and sets a transparent background with the same color. | [`Color.ColorLike`](https://developers.raycast.com/api-reference/colors#color.colorlike) | - | | icon | The optional icon tag icon. Required if the tag has no text. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) | - | | onAction | Callback that is triggered when the item is clicked. | `() => void` | - | | text | The optional tag text. Required if the tag has no icon. | `string` | - | ### List.Item.Detail.Metadata.Separator A metadata item that shows a separator line. Use it for grouping and visually separating metadata items. ![List Detail-metadata-separator illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-e67bdba1103ac032a5090d87e484e2b35f547f1c%2Flist-detail-metadata-separator.webp?alt=media) #### Example ```typescript import { List } from "@raycast/api"; export default function Metadata() { return ( } /> } /> ); } ``` ### List.Section A group of related [List.Item](#list.item). Sections are a great way to structure your list. For example, group GitHub issues with the same status and order them by priority.\ This way, the user can quickly access what is most relevant. #### Example ```typescript import { List } from "@raycast/api"; export default function Command() { return ( ); } ``` #### Props | Prop | Description | Type | Default | | -------- | ---------------------------------------------------------------- | ----------------- | ------- | | children | The List.Item elements of the section. | `React.ReactNode` | - | | subtitle | An optional subtitle displayed next to the title of the section. | `string` | - | | title | Title displayed above the section. | `string` | - | ## Types ### List.Item.Accessory An interface describing an accessory view in a `List.Item`. ![List.Item accessories illustration](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-f9c66d7d4f09101ce3ab239c5dbd509f3304b776%2Flist-item-accessories.webp?alt=media) #### Properties | Property | Description | Type | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | tag\* | A string or Date that will be used as the label, optionally colored. The date is formatted relatively to the current time (for example `new Date()` will be displayed as `"now"`, yesterday's Date will be displayed as "1d", etc.). Color changes the text color to the provided color and sets a transparent background with the same color. Defaults to Color.SecondaryText. | `string` or [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) or `undefined` or `null` or `{ color?:` [`Color.ColorLike`](https://developers.raycast.com/api-reference/colors#color.colorlike)`; value: string` or [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) or `undefined` or `null }` | | text | An optional text that will be used as the label, optionally colored. Color changes the text color to the provided color. Defaults to Color.SecondaryText. | `string` or `null` or `{ color?:` [`Color`](https://developers.raycast.com/api-reference/colors#color)`; value: string` or `undefined` or `null }` | | date | An optional Date that will be used as the label, optionally colored. The date is formatted relatively to the current time (for example `new Date()` will be displayed as `"now"`, yesterday's Date will be displayed as "1d", etc.). Color changes the text color to the provided color. Defaults to Color.SecondaryText. | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) or `null` or `{ color?:` [`Color`](https://developers.raycast.com/api-reference/colors#color)`; value:` [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) or `undefined` or `null }` | | icon | An optional Image.ImageLike that will be used as the icon. | [`Image.ImageLike`](https://developers.raycast.com/api-reference/icons-and-images#image.imagelike) or `null` | | tooltip | An optional tooltip shown when the accessory is hovered. | `string` or `null` | #### Example ```typescript import { Color, Icon, List } from "@raycast/api"; export default function Command() { return ( ); } ``` --- # Source: https://developers.raycast.com/information/developer-tools/manage-extensions-command.md # Manage Extensions Command Raycast provides a built-in command to manage your extensions. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-98a70fc3a77ad10cc32267b282a16b49a38abc00%2Fmanage-extensions.webp?alt=media) For each extensions, there are a few actions to manage them. ## Add New Command One such action is the `Add New Command` action. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-5843ab46283de8d365e94b4c04a36e11cf930344%2Fadd-new-command.webp?alt=media) It will prompt you for the information about the new command before updating the manifest of the extension and creating the file for you based on the template you selected. --- # Source: https://developers.raycast.com/information/manifest.md # Manifest The `package.json` manifest file is a superset of npm's `package.json` file. This way, you only need one file to configure your extension. This document covers only the Raycast specific fields. Refer to [npm's documentation](https://docs.npmjs.com/cli/v7/configuring-npm/package-json) for everything else. Here is a typical manifest file: ```javascript { "name": "my-extension", "title": "My Extension", "description": "My extension that can do a lot of things", "icon": "icon.png", "author": "thomas", "platforms": ["macOS", "Windows"], "categories": ["Fun", "Communication"], "license": "MIT", "commands": [ { "name": "index", "title": "Send Love", "description": "A command to send love to each other", "mode": "view" } ] } ``` ## Extension properties All Raycast related properties for an extension. | Property | Description | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name\* | A unique name for the extension. This is used in the Store link to your extension, so keep it short and URL compatible. | | title\* | The title of the extension that is shown to the user in the Store as well as the preferences. Use this title to describe your extension well that users can find it in the Store. | | description\* | The full description of the extension shown in the Store. | | icon\* | A reference to an icon file in the assets folder. Use png format with a size of 512 x 512 pixels. To support light and dark theme, add two icons, one with `@dark` as suffix, e.g. `icon.png` and `icon@dark.png`. | | author \* | Your Raycast Store handle (username) | | platforms \* | An Array of platforms supported by the extension(`"macOS"` or `"Windows"`). If the extension uses some platform-specific APIs, restrict which platform can install it. | | categories\* | An array of categories that your extension belongs in. | | commands\* | An array of [commands](https://developers.raycast.com/terminology#command) exposed by the extension, see [Command properties](#command-properties). | | tools | An array of tools that the AI can use to interact with this extension, see [Tool properties](#tool-properties). | | ai | Additional information related to the AI capabilities of the extension, see [AI properties](#ai-properties). | | owner | Used for extensions published under an organisation. When defined, the extension will be [private](https://developers.raycast.com/teams/getting-started) (except when specifying `access`). | | access | Either `"public"` or `"private"`. Public extensions are downloadable by anybody, while [private](https://developers.raycast.com/teams/getting-started) extensions can only be downloaded by a member of a given organization. | | contributors | An array of Raycast store handles (usernames) of people who have meaningfully contributed and are maintaining to this extension. | | pastContributors | An array of Raycast store handles (usernames) of people who have meaningfully contributed to the extension's commands but do not maintain it anymore. | | keywords | An array of keywords for which the extension can be searched for in the Store. | | preferences | Extensions can contribute preferences that are shown in Raycast Preferences > Extensions. You can use preferences for configuration values and passwords or personal access tokens, see [Preference properties](#preference-properties). | | external | An Array of package or file names that should be excluded from the build. The package will not be bundled, but the import is preserved and will be evaluated at runtime. | ## Command properties All properties for a [command](https://developers.raycast.com/terminology#command). | Property | Description | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name\* | A unique id for the command. The name directly maps to the entry point file for the command. So a command named "index" would map to `src/index.ts` (or any other supported TypeScript or JavaScript file extension such as `.tsx`, `.js`, `.jsx`). | | title\* | The display name of the command, shown to the user in the Store, Preferences, and in Raycast's root search. | | subtitle | The optional subtitle of the command in the root search. Usually, this is the service or domain that your command is associated with. You can dynamically update this property using [`updateCommandMetadata`](https://developers.raycast.com/api-reference/command#updatecommandmetadata). | | description\* | It helps users understand what the command does. It will be displayed in the Store and in Preferences. | | icon |

An optional reference to an icon file in the assets folder. Use png format with a size of at least 512 x 512 pixels. To support light and dark theme, add two icons, one with @dark as suffix, e.g. icon.png and .

If no icon is specified, the extension icon will be used.

| | mode\* | A value of `view` indicates that the command will show a main view when performed. `no-view` means that the command does not push a view to the main navigation stack in Raycast. The latter is handy for directly opening a URL or other API functionalities that don't require a user interface. `menu-bar` indicates that this command will return a [Menu Bar Extra](https://developers.raycast.com/api-reference/menu-bar-commands) | | interval | The value specifies that a `no-view` or `menu-bar` command should be launched in the background every X seconds (s), minutes (m), hours (h) or days (d). Examples: 90s, 1m, 12h, 1d. The minimum value is 1 minute (1m). | | keywords | An optional array of keywords for which the command can be searched in Raycast. | | arguments | An optional array of arguments that are requested from user when the command is called, see [Argument properties](#argument-properties). | | preferences | Commands can optionally contribute preferences that are shown in Raycast Preferences > Extensions when selecting the command. You can use preferences for configuration values and passwords or personal access tokens, see [Preference properties](#preference-properties). Commands automatically "inherit" extension preferences and can also override entries with the same `name`. | | disabledByDefault |

Specify whether the command should be enabled by default or not. By default, all commands are enabled but there are some cases where you might want to include additional commands and let the user enable them if they need it.

Note that this flag is only used when installing a new extension or when there is a new command.

| ## Preference properties All properties for extension or command-specific preferences. Use the [Preferences API](https://developers.raycast.com/api-reference/preferences) to access their values. | Property | Description | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name\* | A unique id for the preference. | | title\* |

The display name of the preference shown in Raycast preferences.

For "checkbox", "textfield" and "password", it is shown as a section title above the respective input element.

If you want to group multiple checkboxes into a single section, set the title of the first checkbox and leave the title of the other checkboxes empty.

| | description\* | It helps users understand what the preference does. It will be displayed as a tooltip when hovering over it. | | type\* | The preference type. We currently support `"textfield"` and `"password"` (for secure entry), `"checkbox"`, `"dropdown"`, `"appPicker"`, `"file"`, and `"directory"`. | | required\* | Indicates whether the value is required and must be entered by the user before the extension is usable. | | placeholder | Text displayed in the preference's field when no value has been input. | | default |

The optional default value for the field. For textfields, this is a string value; for checkboxes a boolean; for dropdowns the value of an object in the data array; for appPickers an application name, bundle ID or path.

Additionally, you can specify a different value per plaform by passing an object: { "macOS": ..., "Windows": ... }.

| Depending on the `type` of the Preference, some additional properties can be required: ### Additional properties for `checkbox` Preference | Property | Description | | --------------------------------------- | ------------------------------------------------------ | | label\* | The label of the checkbox. Shown next to the checkbox. | ### Additional properties for `dropdown` Preference | Property | Description | | -------------------------------------- | ---------------------------------------------------------------------------------------------------- | | data\* | An array of objects with `title` and `value` properties, e.g.: `[{"title": "Item 1", "value": "1"}]` | ## Argument properties All properties for command arguments. Use the [Arguments API](https://developers.raycast.com/information/lifecycle/arguments) to access their values. | Property | Description | | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | name\* | A unique id for the argument. This value will be used to as the key in the object passed as [top-level prop](https://developers.raycast.com/lifecycle/arguments#arguments). | | type\* | The argument type. We currently support `"text"`, `"password"` (for secure entry), and `"dropdown"`. When the type is `password`, entered text will be replaced with asterisks. Most common use case – passing passwords or secrets to commands. | | placeholder\* | Placeholder for the argument's input field. | | required | Indicates whether the value is required and must be entered by the user before the command is opened. Default value for this is `false`. | Depending on the `type` of the Argument, some additional properties can be required: ### Additional properties for `dropdown` Argument | Property | Description | | -------------------------------------- | ---------------------------------------------------------------------------------------------------- | | data\* | An array of objects with `title` and `value` properties, e.g.: `[{"title": "Item 1", "value": "1"}]` | ## Tool Properties All properties for a tool. | Property | Description | | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name\* | A unique id for the tool. The name directly maps to the entry point file for the tool. So a tool named "index" would map to `src/tools/index.ts` (or any other supported TypeScript file extension such as `.tsx`). | | title\* | The display name of the tool, shown to the user in the Store and Preferences. | | description\* | It helps users and the AI understand what the tool does. It will be displayed in the Store and in Preferences. | | icon |

An optional reference to an icon file in the assets folder. Use png format with a size of at least 512 x 512 pixels. To support light and dark theme, add two icons, one with @dark as suffix, e.g. icon.png and .

If no icon is specified, the extension icon will be used.

| ## AI Properties All properties for the AI capabilities of the extension. Alternatively, this object can be written in a `ai.json` (or `ai.yaml`) file at the root of the extension. | Property | Description | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | instructions | A string containing additional instructions for the AI. It will be added as a system message whenever the extension is mentioned. It can for example be used to help the AI respond with a format that makes more sense for the extension: `Always format pull requests and issues as markdown links: [pull-request-title](https://github.com/:org/:repo/pull/:number) and [issue-title](https://github.com/:org/:repo/issues/:number)` | | evals | Evals for AI Extension. [More details](https://raycastapp.notion.site/AI-Extensions-Evals-15fd6e4a8215800598cad77d8afb5dc8?pvs=73) | --- # Source: https://developers.raycast.com/api-reference/menu-bar-commands.md # Menu Bar Commands The `MenuBarExtra` component can be used to create commands which populate the [extras](https://developer.apple.com/design/human-interface-guidelines/components/system-experiences/the-menu-bar#menu-bar-commands) section of macOS' menu bar. {% hint style="info" %} Menubar commands aren't available on Windows. {% endhint %} ## Getting Started If you don't have an extension yet, follow the [getting started](https://developers.raycast.com/basics/getting-started) guide and then return to this page. Now that your extension is ready, let's open its `package.json` file and add a new entry to its `commands` array, ensuring its `mode` property is set to `menu-bar`. For this guide, let's add the following: ```json { "name": "github-pull-requests", "title": "Pull Requests", "subtitle": "GitHub", "description": "See your GitHub pull requests at a glance", "mode": "menu-bar" }, ``` {% hint style="info" %} Check out the [command properties entry](https://developers.raycast.com/information/manifest#command-properties) in the manifest file documentation for more detailed information on each of those properties. {% endhint %} Create `github-pull-requests.tsx` in your extensions `src/` folder and add the following: ```typescript import { MenuBarExtra } from "@raycast/api"; export default function Command() { return ( { console.log("seen pull request clicked"); }} /> { console.log("unseen pull request clicked"); }} /> ); } ``` If your development server is running, the command should appear in your root search, and running the command should result in the `GitHub` icon appearing in your menu bar. ![GitHub Pull Requests menu bar command](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-d11d9547c3a590b50f9beacaf2211ee0056e4372%2Fmenu-bar-command.gif?alt=media) {% hint style="info" %} macOS has the final say on whether a given menu bar extra is displayed. If you have a lot of items there, it is possible that the command we just ran doesn't show up. If that's the case, try to clear up some space in the menu bar, either by closing some of the items you don't need or by hiding them using [HiddenBar](https://github.com/dwarvesf/hidden), [Bartender](https://www.macbartender.com/), or similar apps. {% endhint %} Of course, our pull request command wouldn't be of that much use if we had to tell it to update itself every single time. To add [background refresh](https://developers.raycast.com/information/lifecycle/background-refresh) to our command, we need to open the `package.json` file we modified earlier and add an `interval` key to the command configuration object: ```json { "name": "github-pull-requests", "title": "Pull Requests", "subtitle": "GitHub", "description": "See your GitHub pull requests at a glance", "mode": "menu-bar", "interval": "5m" } ``` Your root search should look similar to: ![Menu Bar Command - Activate Background Refresh](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-25f5659825806f33bb7cb303da04680bdb29bcbf%2Fmenu-bar-activate-command.webp?alt=media) Running it once should activate it to: ![Menu Bar Command - Refresh](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8f655dafc225472ab9096af040de0243f651dfb3%2Fmenu-bar-refresh.webp?alt=media) ## Lifecycle Although `menu-bar` commands can result in items permanently showing up in the macOS menu bar, they are not long-lived processes. Instead, as with other commands, Raycast loads them into memory on demand, executes their code and then tries to unload them at the next convenient time. There are five distinct events that can result in a `menu-bar`'s item being placed in the menu bar, so let's walk through each one. ### From the root search Same as any other commands, `menu-bar` commands can be run directly from Raycast's root search. Eventually, they may result in a new item showing up in your menu bar (if you have enough room and if the command returns a `MenuBarExtra`), or in a previous item disappearing, if the command returns `null`. In this case, Raycast will load your command code, execute it, wait for the `MenuBarExtra`'s `isLoading` prop to switch to `false`, and unload the command. {% hint style="danger" %} If your command returns a `MenuBarExtra`, it *must* either not set `isLoading` - in which case Raycast will render and immediately unload the command, or set it to `true` while it's performing an async task (such as an API call) and then set it to `false` once it's done. Same as above, Raycast will load the command code, execute it, wait for `MenuBarExtra`'s `isLoading` prop to switch to `false`, and then unload the command. {% endhint %} ### At a set interval If your `menu-bar` command also makes use of [background refresh](https://developers.raycast.com/information/lifecycle/background-refresh) *and* it has background refresh activated, Raycast will run the command at set intervals. In your command, you can use `environment.launchType` to check whether it is launched in the background or by the user. {% hint style="info" %} To ease testing, commands configured to run in the background have an extra action in development mode:\ ![Menu Bar Command - Run in Background](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-5132ca095672c674588b6bc23879af43d9a303d9%2Fmenu-bar-run-in-background.webp?alt=media) {% endhint %} ### When the user clicks the command's icon / title in the menu bar One of the bigger differences to `view` or `no-view` commands is that `menu-bar` commands have an additional entry point: when the user clicks their item in the menu bar. If the item has a menu (i.e. `MenuBarExtra` provides at least one child), Raycast will load the command code, execute it and keep it in memory while the menu is open. When the menu closes (either by the user clicking outside, or by clicking a `MenuBarExtra.Item`), the command is then unloaded. ### When Raycast is restarted This case assumes that your command has run at least once, resulting in an item being placed in the menu bar. If that's the case, quitting and starting Raycast again should put the same item in your menu bar. However, that item will be restored from Raycast's database - *not* by loading and executing the command. ### When a menu bar command is re-enabled in preferences This case should work the same as when Raycast is restarted. ## Best practices * make generous use of the [Cache API](https://developers.raycast.com/api-reference/cache) and our [Utilities](https://developers.raycast.com/utilities/getting-started) in order to provide quick feedback and ensure action handlers work as expected * make sure you set `isLoading` to false when your command finishes executing * avoid setting long titles in `MenuBarExtra`, `MenuBarExtra.Submenu` or `MenuBarExtra.Item` * don't put identical `MenuBarExtra.Item`s at the same level (direct children of `MenuBarExtra` or in the same `Submenu`) as their `onAction` handlers will not be executed correctly ## API Reference ### MenuBarExtra Adds an item to the menu bar, optionally with a menu attached in case its `children` prop is non-empty. {% hint style="info" %} `menu-bar` commands don't always need to return a `MenuBarExtra`. Sometimes it makes sense to remove an item from the menu bar, in which case you can write your command logic to return `null` instead. {% endhint %} #### Example ```typescript import { Icon, MenuBarExtra, open } from "@raycast/api"; const data = { archivedBookmarks: [{ name: "Google Search", url: "www.google.com" }], newBookmarks: [{ name: "Raycast", url: "www.raycast.com" }], }; export default function Command() { return ( {data?.newBookmarks.map((bookmark) => ( open(bookmark.url)} /> ))} {data?.archivedBookmarks.map((bookmark) => ( open(bookmark.url)} /> ))} ); } ``` #### Props | Prop | Description | Type | Default | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------- | | children | `MenuBarExtra.Item`s, `MenuBarExtra.Submenu`s, `MenuBarExtra.Separator` or a mix of either. | `React.ReactNode` | - | | icon | The icon that is displayed in the menu bar. | [`Image.ImageLike`](https://developers.raycast.com/user-interface/icons-and-images#image.imagelike) | - | | isLoading | Indicates to Raycast that it should not unload the command, as it is still executing. If you set make use of `isLoading`, you need to make sure you set it to `false` at the end of the task you are executing (such as an API call), so Raycast can then unload the command. | `boolean` | - | | title | The string that is displayed in the menu bar. | `string` | - | | tooltip | A tooltip to display when the cursor hovers the item in the menu bar. | `string` | - | ### MenuBarExtra.Item An item in the [MenuBarExtra](#menubarextra) or in a [MenuBarExtra.Submenu](#menubarextra.submenu). #### Example {% tabs %} {% tab title="ItemWithTitle.tsx" %} An item that only provides a `title` prop will be rendered as disabled. Use this to create section titles. ```typescript import { Icon, MenuBarExtra } from "@raycast/api"; export default function Command() { return ( ); } ``` {% endtab %} {% tab title="ItemWithTitleAndIcon.tsx" %} Similarly, an item that provides a `title` and an `icon` prop will also be rendered as disabled. ```typescript import { Icon, MenuBarExtra } from "@raycast/api"; export default function Command() { return ( ); } ``` {% endtab %} {% tab title="ItemWithAction.tsx" %} An item that provides an `onAction` prop alongside `title` (and optionally `icon`) will *not* be rendered as disabled. When users click this item in the menu bar, the action handler will be executed. ```typescript import { Icon, MenuBarExtra, open } from "@raycast/api"; export default function Command() { return ( open("https://raycast.com")} /> ); } ``` {% endtab %} {% tab title="ItemWithAlternate.tsx" %} If an item provides another `MenuBarEtra.Item` via its `alternate`, prop, the second item will be shown then the user presses the ⌥ (opt) key. There are a few limitation: 1. The `alternate` item may not have a custom shortcut. Instead, it will inherit its parent's shortcut, with the addition of ⌥ (opt) as a modifier. 2. The `alternate` item may not also specify an alternate. 3. A parent item that provides an `alternate` may not use ⌥ (opt) as a modifier. ```typescript import { Icon, MenuBarExtra, open } from "@raycast/api"; export default function Command() { return ( open("https://raycast.com")} alternate={ open("https://raycast.com/store")} /> } /> ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | --------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | ------- | | title\* | The main title displayed for this item. | `string` | - | | alternate | A MenuBarExtra.Item to be displayed when a user presses the ⌥ (opt) key. | `ReactElement<`[`MenuBarExtra.Item.Props`](#props)`>` | - | | icon | An optional icon for this item. | [`Image.ImageLike`](https://developers.raycast.com/user-interface/icons-and-images#image.imagelike) | - | | onAction | An action handler called when the user clicks the item. | `(event:` [`MenuBarExtra.ActionEvent`](#menubarextra.actionevent)`) => void` | - | | shortcut | A shortcut used to invoke this item when its parent menu is open. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | - | | subtitle | The subtitle displayed for this item. | `string` | - | | tooltip | A tooltip to display when the cursor hovers the item. | `string` | - | ### MenuBarExtra.Submenu `MenuBarExtra.Submenu`s reveal their items when people interact with them. They're a good way to group items that naturally belong together, but keep in mind that submenus add complexity to your interface - so use them sparingly! #### Example {% tabs %} {% tab title="Bookmarks.tsx" %} ```typescript import { Icon, MenuBarExtra, open } from "@raycast/api"; export default function Command() { return ( open("https://raycast.com")} /> open("https://github.com/pulls")} /> open("https://github.com/issues")} /> ); } ``` {% endtab %} {% tab title="DisabledSubmenu.tsx" %} Submenus with no children will show up as disabled. ```typescript import { Icon, MenuBarExtra, open } from "@raycast/api"; export default function Command() { return ( ); } ``` {% endtab %} {% endtabs %} #### Props | Prop | Description | Type | Default | | --------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------- | | title\* | The main title displayed for this submenu. | `string` | - | | children | `MenuBarExtra.Item`s, `MenuBarExtra.Submenu`s, `MenuBarExtra.Separator` or a mix of either. | `React.ReactNode` | - | | icon | An optional icon for this submenu. | [`Image.ImageLike`](https://developers.raycast.com/user-interface/icons-and-images#image.imagelike) | - | ### MenuBarExtra.Section An item to group related menu items. It has an optional title and a separator is added automatically between sections. #### Example ```typescript import { Icon, MenuBarExtra, open } from "@raycast/api"; const data = { archivedBookmarks: [{ name: "Google Search", url: "www.google.com" }], newBookmarks: [{ name: "Raycast", url: "www.raycast.com" }], }; export default function Command() { return ( {data?.newBookmarks.map((bookmark) => ( open(bookmark.url)} /> ))} {data?.archivedBookmarks.map((bookmark) => ( open(bookmark.url)} /> ))} ); } ``` #### Props | Prop | Description | Type | Default | | -------- | --------------------------------- | ----------------- | ------- | | children | The item elements of the section. | `React.ReactNode` | - | | title | Title displayed above the section | `string` | - | ## Types ### MenuBarExtra.ActionEvent An interface describing Action events in callbacks. #### Properties | Property | Description | Type | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | type\* | A type of the action event \* `left-click` is a left mouse click on the MenuBarExtra.Item or a Keyboard.Shortcut \* `right-click` is a right mouse click on the MenuBarExtra.Item | `"left-click"` or `"right-click"` | #### Example ```typescript import { MenuBarExtra } from "@raycast/api"; export default function Command() { return ( console.log("Action Event Type", event.type)} /> ); } ``` --- # Source: https://developers.raycast.com/misc/migration.md # Migration This section contains guides to help migrate your extension to a newer version of the API. ## How to automatically migrate your extensions Whenever possible, we provide tools to automate the migration to a newer version of the API using [codemods](https://github.com/facebook/jscodeshift). To run the codemods, run the following command in your extension directory: ```bash npx ray migrate ``` or ```bash npx @raycast/migration@latest . ``` It will detect the version of the API you were previously using and apply all the migrations that have been available since. After running it, do go through the updated files and make sure nothing is broken - there are always edge cases. --- # Source: https://developers.raycast.com/api-reference/user-interface/navigation.md # Navigation ## API Reference ### useNavigation A hook that lets you push and pop view components in the navigation stack. You most likely won't use this hook too often. To push a new component, use the [Push Action](https://developers.raycast.com/api-reference/actions#action.push). When a user presses `ESC`, we automatically pop to the previous component. #### Signature ```typescript function useNavigation(): Navigation; ``` #### Example ```typescript import { Action, ActionPanel, Detail, useNavigation } from "@raycast/api"; function Ping() { const { push } = useNavigation(); return ( push()} /> } /> ); } function Pong() { const { pop } = useNavigation(); return ( } /> ); } export default function Command() { return ; } ``` #### Return A [Navigation](#navigation) object with [Navigation.push](#navigation) and [Navigation.pop](#navigation) functions. Use the functions to alter the navigation stack. ## Types ### Navigation Return type of the [useNavigation](#usenavigation) hook to perform push and pop actions. #### Properties | Property | Description | Type | | -------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------- | | pop\* | Pop current view component from the navigation stack. | `() => void` | | push\* | Push a new view component to the navigation stack. | `(component: React.ReactNode, onPop: () => void) => void` | --- # Source: https://developers.raycast.com/utilities/oauth.md # Source: https://developers.raycast.com/api-reference/oauth.md # OAuth ## Prerequisites A Raycast extension can use OAuth for authorizing access to a provider's resources on the user's behalf. Since Raycast is a desktop app and the extensions are considered "public", we only support the [PKCE flow](https://datatracker.ietf.org/doc/html/rfc7636) (Proof Key for Code Exchange, pronounced “pixy”). This flow is the official recommendation for native clients that cannot keep a client secret. With PKCE, the client dynamically creates a secret and uses the secret again during code exchange, ensuring that only the client that performed the initial request can exchange the code for the access token (”proof of possession”). {% hint style="info" %} Providers such as Google, Twitter, GitLab, Spotify, Zoom, Asana or Dropbox are all PKCE-ready. However, if your provider doesn't support PKCE, you can use our [PKCE proxy](https://oauth.raycast.com). It allows extensions to securely use an OAuth flow without exposing any secret. {% endhint %} ## OAuth Flow ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-73fbf2da0684ab57bbdba779e06b3bbcfb895a01%2Foauth-overlay-twitter.webp?alt=media) The OAuth flow from an extension looks like this: 1. The extension initiates the OAuth flow and starts authorization 2. Raycast shows the OAuth overlay ("Connect to provider…") 3. The user opens the provider's consent page in the web browser 4. After the user consent, the provider redirects back to Raycast 5. Raycast opens the extension where authorization is completed When the flow is complete, the extension has received an access token from the provider and can perform API calls.\ The API provides functions for securely storing and retrieving token sets, so that an extension can check whether the user is already logged in and whether an expired access token needs to be refreshed. Raycast also automatically shows a logout preference. ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-fa10ad21218f2a0f28d9a4fef60575518e1c0f1a%2Foauth-overlay-twitter-success.webp?alt=media) ## OAuth App You first need to register a new OAuth app with your provider. This is usually done in the provider's developer portal. After registering, you will receive a client ID. You also need to configure a redirect URI, see the next section. Note: Make sure to choose an app type that supports PKCE. Some providers still show you a client secret, which you don't need and should *not* hardcode in the extension, or support PKCE only for certain types such as "desktop", "native" or "mobile" app types. ## Authorizing An extension can initiate the OAuth flow and authorize by using the methods on [OAuth.PKCEClient](#oauth.pkceclient). You can create a new client and configure it with a provider name, icon and description that will be shown in the OAuth overlay. You can also choose between different redirect methods; depending on which method you choose, you need to configure this value as redirect URI in your provider's registered OAuth app. (See the [OAuth.RedirectMethod](#oauth.redirectmethod) docs for each method to get concrete examples for supported redirect URI.) If you can choose, use `OAuth.RedirectMethod.Web` and enter `https://raycast.com/redirect?packageName=Extension` (whether you have to add the `?packageName=Extension` depends on the provider). ```typescript import { OAuth } from "@raycast/api"; const client = new OAuth.PKCEClient({ redirectMethod: OAuth.RedirectMethod.Web, providerName: "Twitter", providerIcon: "twitter-logo.png", description: "Connect your Twitter account…", }); ``` Next you create an authorization request with the authorization endpoint, client ID, and scope values. You receive all values from your provider's docs and when you register a new OAuth app. The returned [AuthorizationRequest](#oauth.authorizationrequest) contains parameters such as the code challenge, verifier, state and redirect URI as standard OAuth authorization request. You can also customize the authorization URL through [OAuth.AuthorizationOptions](#oauth.authorizationoptions) if you need to. ```typescript const authRequest = await client.authorizationRequest({ endpoint: "https://twitter.com/i/oauth2/authorize", clientId: "YourClientId", scope: "tweet.read users.read follows.read", }); ``` To get the authorization code needed for the token exchange, you call [authorize](#oauth.pkceclient-authorize) with the request from the previous step.\ This call shows the Raycast OAuth overlay and provides the user with an option to open the consent page in the web browser.\ The authorize promise is resolved after the redirect back to Raycast and into the extension: ```typescript const { authorizationCode } = await client.authorize(authRequest); ``` {% hint style="info" %} When in development mode, make sure not to trigger auto-reloading (e.g. by saving a file) while you're testing an active OAuth authorization and redirect. This would cause an OAuth state mismatch when you're redirected back into the extension since the client would be reinitialized on reload. {% endhint %} Now that you have received the authorization code, you can exchange this code for an access token using your provider's token endpoint. This token exchange (and the following API calls) can be done with your preferred Node HTTP client library. Example using `node-fetch`: ```typescript async function fetchTokens(authRequest: OAuth.AuthorizationRequest, authCode: string): Promise { const params = new URLSearchParams(); params.append("client_id", "YourClientId"); params.append("code", authCode); params.append("code_verifier", authRequest.codeVerifier); params.append("grant_type", "authorization_code"); params.append("redirect_uri", authRequest.redirectURI); const response = await fetch("https://api.twitter.com/2/oauth2/token", { method: "POST", body: params, }); if (!response.ok) { console.error("fetch tokens error:", await response.text()); throw new Error(response.statusText); } return (await response.json()) as OAuth.TokenResponse; } ``` ## Token Storage The PKCE client exposes methods for storing, retrieving and deleting token sets. A [TokenSet](#oauth.tokenset) contains an access token and typically also a refresh token, expires value, and the current scope. Since this data is returned by the provider's token endpoint as standard OAuth JSON response, you can directly store the response ([OAuth.TokenResponse](#oauth.tokenresponse)) or alternatively use [OAuth.TokenSetOptions](#oauth.tokensetoptions): ```typescript await client.setTokens(tokenResponse); ``` Once the token set is stored, Raycast will automatically show a logout preference for the extension. When the user logs out, the token set gets removed. The [TokenSet](#oauth.tokenset) also enables you to check whether the user is logged in before starting the authorization flow: ```typescript const tokenSet = await client.getTokens(); ``` ## Token Refresh Since access tokens usually expire, an extension should provide a way to refresh the access token, otherwise users would be logged out or see errors.\ Some providers require you to add an offline scope so that you get a refresh token. (Twitter, for example, needs the scope `offline.access` or it only returns an access token.)\ A basic refresh flow could look like this: ```typescript const tokenSet = await client.getTokens(); if (tokenSet?.accessToken) { if (tokenSet.refreshToken && tokenSet.isExpired()) { await client.setTokens(await refreshTokens(tokenSet.refreshToken)); } return; } // authorize... ``` This code would run before starting the authorization flow. It checks the presence of a token set to see whether the user is logged in and then checks whether there is a refresh token and the token set is expired (through the convenience method `isExpired()` on the [TokenSet](#oauth.tokenset)). If it is expired, the token is refreshed and updated in the token set. Example using `node-fetch`: ```typescript async function refreshTokens(refreshToken: string): Promise { const params = new URLSearchParams(); params.append("client_id", "YourClientId"); params.append("refresh_token", refreshToken); params.append("grant_type", "refresh_token"); const response = await fetch("https://api.twitter.com/2/oauth2/token", { method: "POST", body: params, }); if (!response.ok) { console.error("refresh tokens error:", await response.text()); throw new Error(response.statusText); } const tokenResponse = (await response.json()) as OAuth.TokenResponse; tokenResponse.refresh_token = tokenResponse.refresh_token ?? refreshToken; return tokenResponse; } ``` ## Examples We've provided [OAuth example integrations for Google, Twitter, and Dropbox](https://github.com/raycast/extensions/tree/main/examples/api-examples) that demonstrate the entire flow shown above. ## API Reference ### OAuth.PKCEClient Use [OAuth.PKCEClient.Options](#oauth.pkceclient.options) to configure what's shown on the OAuth overlay. #### Signature ```typescript constructor(options: OAuth.PKCEClient.Options): OAuth.PKCEClient ``` #### Example ```typescript import { OAuth } from "@raycast/api"; const client = new OAuth.PKCEClient({ redirectMethod: OAuth.RedirectMethod.Web, providerName: "Twitter", providerIcon: "twitter-logo.png", description: "Connect your Twitter account…", }); ``` #### Methods | Method | | --------------------------------------------------------------------------------------------------------------- | | [`authorizationRequest(options: AuthorizationRequestOptions): Promise`](#oauth.pkceclient-authorizationrequest) | | [`authorize(options: AuthorizationRequest \| AuthorizationOptions): Promise`](#oauth.pkceclient-authorize) | | [`setTokens(options: TokenSetOptions \| TokenResponse): Promise`](#oauth.pkceclient-settokens) | | [`getTokens(): Promise`](#oauth.pkceclient-gettokens) | | [`removeTokens(): Promise`](#oauth.pkceclient-removetokens) | ### OAuth.PKCEClient#authorizationRequest Creates an authorization request for the provided authorization endpoint, client ID, and scopes. You need to first create the authorization request before calling [authorize](#oauth.pkceclient-authorize). The generated code challenge for the PKCE request uses the S256 method. #### Signature ```typescript authorizationRequest(options: AuthorizationRequestOptions): Promise; ``` #### Example ```typescript const authRequest = await client.authorizationRequest({ endpoint: "https://twitter.com/i/oauth2/authorize", clientId: "YourClientId", scope: "tweet.read users.read follows.read", }); ``` #### Parameters | Name | Type | Description | | ----------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------- | | options\* | [`AuthorizationRequestOptions`](#oauth.authorizationrequestoptions) | The options used to create the authorization request. | #### Return A promise for an [AuthorizationRequest](#oauth.authorizationrequest) that you can use as input for [authorize](#oauth.pkceclient-authorize). ### OAuth.PKCEClient#authorize Starts the authorization and shows the OAuth overlay in Raycast. As parameter you can either directly use the returned request from [authorizationRequest](#oauth.authorizationrequest), or customize the URL by extracting parameters from [AuthorizationRequest](#oauth.authorizationrequest) and providing your own URL via [AuthorizationOptions](#oauth.authorizationoptions). Eventually the URL will be used to open the authorization page of the provider in the web browser. #### Signature ```typescript authorize(options: AuthorizationRequest | AuthorizationOptions): Promise; ``` #### Example ```typescript const { authorizationCode } = await client.authorize(authRequest); ``` #### Parameters | Name | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------ | | options\* | [`AuthorizationRequest`](#oauth.authorizationrequest) `\|` [`AuthorizationOptions`](#oauth.authorizationoptions) | The options used to authorize. | #### Return A promise for an [AuthorizationResponse](#oauth.authorizationresponse), which contains the authorization code needed for the token exchange. The promise is resolved when the user was redirected back from the provider's authorization page to the Raycast extension. ### OAuth.PKCEClient#setTokens Securely stores a [TokenSet](#oauth.tokenset) for the provider. Use this after fetching the access token from the provider. If the provider returns a a standard OAuth JSON token response, you can directly pass the [TokenResponse](#oauth.tokenresponse).\ At a minimum, you need to set the `accessToken`, and typically you also set `refreshToken` and `isExpired`. Raycast automatically shows a logout preference for the extension when a token set was saved. If you want to make use of the convenience `isExpired()` method, the property `expiresIn` must be configured. #### Signature ```typescript setTokens(options: TokenSetOptions | TokenResponse): Promise; ``` #### Example ```typescript await client.setTokens(tokenResponse); ``` #### Parameters | Name | Type | Description | | ----------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------- | | options\* | [`TokenSetOptions`](#oauth.tokensetoptions) `\|` [`TokenResponse`](#oauth.tokenresponse) | The options used to store the token set. | #### Return A promise that resolves when the token set has been stored. ### OAuth.PKCEClient#getTokens Retrieves the stored [TokenSet](#oauth.tokenset) for the client. You can use this to initially check whether the authorization flow should be initiated or the user is already logged in and you might have to refresh the access token. #### Signature ```typescript getTokens(): Promise; ``` #### Example ```typescript const tokenSet = await client.getTokens(); ``` #### Return A promise that resolves when the token set has been retrieved. ### OAuth.PKCEClient#removeTokens Removes the stored [TokenSet](#oauth.tokenset) for the client.\ Raycast automatically shows a logout preference that removes the token set. Use this method only if you need to provide an additional logout option in your extension or you want to remove the token set because of a migration. #### Signature ```typescript removeTokens(): Promise; ``` #### Example ```typescript await client.removeTokens(); ``` #### Return A promise that resolves when the token set has been removed. ## Types ### OAuth.PKCEClient.Options The options for creating a new [PKCEClient](#oauth.pkceclient). #### Properties | Property | Description | Type | | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | providerName\* | The name of the provider, displayed in the OAuth overlay. | `string` | | redirectMethod\* | The redirect method for the OAuth flow. Make sure to set this to the correct method for the provider, see OAuth.RedirectMethod for more information. | [`OAuth.RedirectMethod`](#oauth.redirectmethod) | | description | An optional description, shown in the OAuth overlay. You can use this to customize the message for the end user, for example for handling scope changes or other migrations. Raycast shows a default message if this is not configured. | `string` | | providerIcon | An icon displayed in the OAuth overlay. Make sure to provide at least a size of 64x64 pixels. | [`Image.ImageLike`](https://developers.raycast.com/user-interface/icons-and-images#image.imagelike) | | providerId | An optional ID for associating the client with a provider. Only set this if you use multiple different clients in your extension. | `string` | ### OAuth.RedirectMethod Defines the supported redirect methods for the OAuth flow. You can choose between web and app-scheme redirect methods, depending on what the provider requires when setting up the OAuth app. For examples on what redirect URI you need to configure, see the docs for each method. #### Enumeration members | Name | Value | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Web |

Use this type for a redirect back to the Raycast website, which will then open the extension. In the OAuth app, configure
(This is a static redirect URL for all extensions.)
If the provider does not accept query parameters in redirect URLs, you can alternatively use and then customize the AuthorizationRequest via its extraParameters property. For example add: extraParameters: { "redirect\_uri": "" }

| | App | Use this type for an app-scheme based redirect that directly opens Raycast. In the OAuth app, configure `raycast://oauth?package_name=Extension` | | AppURI |

Use this type for a URI-style app scheme that directly opens Raycast. In the OAuth app, configure com.raycast:/oauth?package\_name=Extension
(Note the single slash – Google, for example, would require this flavor for an OAuth app where the Bundle ID is com.raycast)

| ### OAuth.AuthorizationRequestOptions The options for an authorization request via [authorizationRequest](#oauth.authorizationrequest). | Property | Description | Type | | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | | clientId\* | The client ID of the configured OAuth app. | `string` | | endpoint\* | The URL to the authorization endpoint for the OAuth provider. | `string` | | scope\* | A space-delimited list of scopes for identifying the resources to access on the user's behalf. The scopes are typically shown to the user on the provider's consent screen in the browser. Note that some providers require the same scopes be configured in the registered OAuth app. | `string` | | extraParameters | Optional additional parameters for the authorization request. Note that some providers require additional parameters, for example to obtain long-lived refresh tokens. | `{ [string]: string }` | ### OAuth.AuthorizationRequestURLParams Values of [AuthorizationRequest](#oauth.authorizationrequest).\ The PKCE client automatically generates the values for you and returns them for [authorizationRequest](#oauth.authorizationrequest) | Property | Description | Type | | ----------------------------------------------- | -------------------------------- | -------- | | codeChallenge\* | The PKCE `code_challenge` value. | `string` | | codeVerifier\* | The PKCE `code_verifier` value. | `string` | | redirectURI\* | The OAuth `redirect_uri` value. | `string` | | state\* | The OAuth `state` value. | `string` | ### OAuth.AuthorizationRequest The request returned by [authorizationRequest](#oauth.authorizationrequest).\ Can be used as direct input to [authorize](#oauth.pkceclient-authorize), or to extract parameters for constructing a custom URL in [AuthorizationOptions](#oauth.authorizationoptions). | Property | Description | Type | | ----------------------------------------------- | -------------------------------- | -------------- | | codeChallenge\* | The PKCE `code_challenge` value. | `string` | | codeVerifier\* | The PKCE `code_verifier` value. | `string` | | redirectURI\* | The OAuth `redirect_uri` value. | `string` | | state\* | The OAuth `state` value. | `string` | | toURL\* | | `() => string` | #### Methods | Name | Type | Description | | ------- | -------------- | -------------------------------------- | | toURL() | `() => string` | Constructs the full authorization URL. | ### OAuth.AuthorizationOptions Options for customizing [authorize](#oauth.pkceclient-authorize).\ You can use values from [AuthorizationRequest](#oauth.authorizationrequest) to build your own URL. | Property | Description | Type | | ------------------------------------- | --------------------------- | -------- | | url\* | The full authorization URL. | `string` | ### OAuth.AuthorizationResponse The response returned by [authorize](#oauth.pkceclient-authorize), containing the authorization code after the provider redirect. You can then exchange the authorization code for an access token using the provider's token endpoint. | Property | Description | Type | | --------------------------------------------------- | ----------------------------------------------- | -------- | | authorizationCode\* | The authorization code from the OAuth provider. | `string` | ### OAuth.TokenSet Describes the TokenSet created from an OAuth provider's token response. The `accessToken` is the only required parameter but typically OAuth providers also return a refresh token, an expires value, and the scope.\ Securely store a token set via [setTokens](#oauth.pkceclient-settokens) and retrieve it via [getTokens](#oauth.pkceclient-gettokens). | Property | Description | Type | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | accessToken\* | The access token returned by an OAuth token request. | `string` | | updatedAt\* | The date when the token set was stored via OAuth.PKCEClient.setTokens. | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) | | isExpired\* | | `() => boolean` | | expiresIn | An optional expires value (in seconds) returned by an OAuth token request. | `number` | | idToken | An optional id token returned by an identity request (e.g. /me, Open ID Connect). | `string` | | refreshToken | An optional refresh token returned by an OAuth token request. | `string` | | scope | The optional space-delimited list of scopes returned by an OAuth token request. You can use this to compare the currently stored access scopes against new access scopes the extension might require in a future version, and then ask the user to re-authorize with new scopes. | `string` | #### Methods | Name | Type | Description | | ----------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | isExpired() | `() => boolean` | A convenience method for checking whether the access token has expired. The method factors in some seconds of "buffer", so it returns true a couple of seconds before the actual expiration time. This requires the `expiresIn` parameter to be set. | ### OAuth.TokenSetOptions Options for a [TokenSet](#oauth.tokenset) to store via [setTokens](#oauth.pkceclient-settokens). | Property | Description | Type | | --------------------------------------------- | --------------------------------------------------------------------------------- | -------- | | accessToken\* | The access token returned by an OAuth token request. | `string` | | expiresIn | An optional expires value (in seconds) returned by an OAuth token request. | `number` | | idToken | An optional id token returned by an identity request (e.g. /me, Open ID Connect). | `string` | | refreshToken | An optional refresh token returned by an OAuth token request. | `string` | | scope | The optional scope value returned by an OAuth token request. | `string` | ### OAuth.TokenResponse Defines the standard JSON response for an OAuth token request.\ The response can be directly used to store a [TokenSet](#oauth.tokenset) via [setTokens](#oauth.pkceclient-settokens). | Property | Description | Type | | ----------------------------------------------- | ----------------------------------------------------------------------------------------- | -------- | | access\_token\* | The `access_token` value returned by an OAuth token request. | `string` | | expires\_in | An optional `expires_in` value (in seconds) returned by an OAuth token request. | `number` | | id\_token | An optional `id_token` value returned by an identity request (e.g. /me, Open ID Connect). | `string` | | refresh\_token | An optional `refresh_token` value returned by an OAuth token request. | `string` | | scope | The optional `scope` value returned by an OAuth token request. | `string` | --- # Source: https://developers.raycast.com/utilities/oauth/oauthservice.md # OAuthService The `OAuthService` class is designed to abstract the OAuth authorization process using the PKCE (Proof Key for Code Exchange) flow, simplifying the integration with various OAuth providers such as Asana, GitHub, and others. Use [OAuthServiceOptions](#oauthserviceoptions) to configure the `OAuthService` class. ## Example ```ts const client = new OAuth.PKCEClient({ redirectMethod: OAuth.RedirectMethod.Web, providerName: "GitHub", providerIcon: "extension_icon.png", providerId: "github", description: "Connect your GitHub account", }); const github = new OAuthService({ client, clientId: "7235fe8d42157f1f38c0", scope: "notifications repo read:org read:user read:project", authorizeUrl: "https://github.oauth.raycast.com/authorize", tokenUrl: "https://github.oauth.raycast.com/token", }); ``` ## Signature ```ts constructor(options: OAuthServiceOptions): OAuthService ``` ### Methods #### `authorize` Initiates the OAuth authorization process or refreshes existing tokens if necessary. Returns a promise that resolves with the access token from the authorization flow. **Signature** ```ts OAuthService.authorize(): Promise; ``` **Example** ```typescript const accessToken = await oauthService.authorize(); ``` ### Built-in Services Some services are exposed as static properties in `OAuthService` to make it easy to authenticate with them: * [Asana](#asana) * [GitHub](#github) * [Google](#google) * [Jira](#jira) * [Linear](#linear) * [Slack](#slack) * [Zoom](#zoom) Asana, GitHub, Linear, and Slack already have an OAuth app configured by Raycast so that you can use them right of the box by specifing only the permission scopes. You are still free to create an OAuth app for them if you want. Google, Jira and Zoom don't have an OAuth app configured by Raycast so you'll have to create one if you want to use them. Use [ProviderOptions](#provideroptions) or [ProviderWithDefaultClientOptions](#providerwithdefaultclientoptions) to configure these built-in services. #### Asana **Signature** ```ts OAuthService.asana: (options: ProviderWithDefaultClientOptions) => OAuthService ``` **Example** ```tsx const asana = OAuthService.asana({ scope: "default" }); ``` #### GitHub **Signature** ```ts OAuthService.github: (options: ProviderWithDefaultClientOptions) => OAuthService ``` **Example** ```tsx const github = OAuthService.github({ scope: "repo user" }); ``` #### Google Google has verification processes based on the required scopes for your extension. Therefore, you need to configure your own client for it. {% hint style="info" %} Creating your own Google client ID is more tedious than other processes, so we’ve created a page to assist you: [Getting a Google client ID](https://developers.raycast.com/utilities/oauth/getting-google-client-id) {% endhint %} **Signature** ```ts OAuthService.google: (options: ProviderOptions) => OAuthService ``` **Example** ```tsx const google = OAuthService.google({ clientId: "custom-client-id", scope: "https://www.googleapis.com/auth/drive.readonly", }); ``` #### Jira Jira requires scopes to be enabled manually in the OAuth app settings. Therefore, you need to configure your own client for it. **Signature** ```ts OAuthService.jira: (options: ProviderOptions) => OAuthService ``` **Example** ```tsx const jira = OAuthService.jira({ clientId: "custom-client-id", scope: "read:jira-user read:jira-work offline_access", }); ``` #### Linear **Signature** ```ts OAuthService.linear: (options: ProviderOptions) => OAuthService ``` **Example** ```tsx const linear = OAuthService.linear({ scope: "read write" }); ``` #### Slack **Signature** ```ts OAuthService.slack: (options: ProviderWithDefaultClientOptions) => OAuthService ``` **Example** ```tsx const slack = OAuthService.slack({ scope: "emoji:read" }); ``` #### Zoom Zoom requires scopes to be enabled manually in the OAuth app settings. Therefore, you need to configure your own client for it. **Signature** ```ts OAuthService.zoom: (options: ProviderOptions) => OAuthService ``` **Example** ```tsx const zoom = OAuthService.zoom({ clientId: "custom-client-id", scope: "meeting:write", }); ``` ## Types ### OAuthServiceOptions | Property Name | Description | Type | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | client\* | The PKCE Client defined using `OAuth.PKCEClient` from `@raycast/api` | `OAuth.PKCEClient` | | clientId\* | The app's client ID | `string` | | scope\* | The scope of the access requested from the provider | `string` \| `Array` | | authorizeUrl\* | The URL to start the OAuth flow | `string` | | tokenUrl\* | The URL to exchange the authorization code for an access token | `string` | | refreshTokenUrl | The URL to refresh the access token if applicable | `string` | | personalAccessToken | A personal token if the provider supports it | `string` | | onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` | | extraParameters | The extra parameters you may need for the authorization request | `Record` | | bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` | | tokenResponseParser | Some providers returns some non-standard token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` | | tokenRefreshResponseParser | Some providers returns some non-standard refresh token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` | ### ProviderOptions | Property Name | Description | Type | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | clientId\* | The app's client ID | `string` | | scope\* | The scope of the access requested from the provider | `string` \| `Array` | | authorizeUrl\* | The URL to start the OAuth flow | `string` | | tokenUrl\* | The URL to exchange the authorization code for an access token | `string` | | refreshTokenUrl | The URL to refresh the access token if applicable | `string` | | personalAccessToken | A personal token if the provider supports it | `string` | | onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` | | bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` | | tokenResponseParser | Some providers returns some non-standard token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` | | tokenRefreshResponseParser | Some providers returns some non-standard refresh token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` | ### ProviderWithDefaultClientOptions | Property Name | Description | Type | | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | scope\* | The scope of the access requested from the provider | `string` \| `Array` | | clientId | The app's client ID | `string` | | authorizeUrl | The URL to start the OAuth flow | `string` | | tokenUrl | The URL to exchange the authorization code for an access token | `string` | | refreshTokenUrl | The URL to refresh the access token if applicable | `string` | | personalAccessToken | A personal token if the provider supports it | `string` | | onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` | | bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` | | tokenResponseParser | Some providers returns some non-standard token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` | | tokenRefreshResponseParser | Some providers returns some non-standard refresh token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` | --- # Source: https://developers.raycast.com/api-reference/preferences.md # Preferences Use the Preferences API to make your extension configurable. Preferences are configured in the [manifest](https://developers.raycast.com/information/manifest#preference-properties) per command or shared in the context of an extension. Required preferences need to be set by the user before a command opens. They are a great way to make sure that the user of your extension has everything set up properly. ## API Reference ### getPreferenceValues A function to access the preference values that have been passed to the command. Each preference name is mapped to its value, and the defined default values are used as fallback values. #### Signature ```typescript function getPreferenceValues(): { [preferenceName: string]: any }; ``` {% hint style="info" %} You don't need to manually set preference types as an interface since it is autogenerated in `raycast-env.d.ts` when you run the extension. {% endhint %} #### Example ```typescript import { getPreferenceValues } from "@raycast/api"; export default async function Command() { const preferences = getPreferenceValues(); console.log(preferences); } ``` #### Return An object with the preference names as property key and the typed value as property value. Depending on the type of the preference, the type of its value will be different. | Preference type | Value type | | --------------- | --------------------------------------------------------------------- | | `textfield` | `string` | | `password` | `string` | | `checkbox` | `boolean` | | `dropdown` | `string` | | `appPicker` | [`Application`](https://developers.raycast.com/utilities#application) | | `file` | `string` | | `directory` | `string` | ### openExtensionPreferences Opens the extension's preferences screen. #### Signature ```typescript export declare function openExtensionPreferences(): Promise; ``` #### Example ```typescript import { ActionPanel, Action, Detail, openExtensionPreferences } from "@raycast/api"; export default function Command() { const markdown = "API key incorrect. Please update it in extension preferences and try again."; return ( } /> ); } ``` #### Return A Promise that resolves when the extensions preferences screen is opened. ### openCommandPreferences Opens the command's preferences screen. #### Signature ```typescript export declare function openCommandPreferences(): Promise; ``` #### Example ```typescript import { ActionPanel, Action, Detail, openCommandPreferences } from "@raycast/api"; export default function Command() { const markdown = "API key incorrect. Please update it in command preferences and try again."; return ( } /> ); } ``` #### Return A Promise that resolves when the command's preferences screen is opened. ## Types ### Preferences A command receives the values of its preferences via the [`getPreferenceValues`](#getpreferencevalues) function. It is an object with the preferences' `name` as keys and their values as the property's values. Depending on the type of the preference, the type of its value will be different. | Preference type | Value type | | --------------- | --------------------------------------------------------------------- | | `textfield` | `string` | | `password` | `string` | | `checkbox` | `boolean` | | `dropdown` | `string` | | `appPicker` | [`Application`](https://developers.raycast.com/utilities#application) | | `file` | `string` | | `directory` | `string` | {% hint style="info" %} Raycast provides a global TypeScript namespace called `Preferences` which contains the types of the preferences of all the commands of the extension. For example, if a command named `show-todos` has some preferences, its `getPreferenceValues`'s return type can be specified with `getPreferenceValues()`. This will make sure that the types used in the command stay in sync with the manifest. {% endhint %} --- # Source: https://developers.raycast.com/basics/prepare-an-extension-for-store.md # Prepare an Extension for Store Here you will find requirements and guidelines that you'll need to follow in order to get through the review before your extension becomes available in the Store. Please read it carefully because it will save time for you and for us. This document is constantly evolving so please do visit it from time to time. ## Metadata and Configuration * Things to double-check in your `package.json` * Ensure you use your **Raycast** account username in the `author` field * Ensure you use `MIT` in the `license` field * Ensure you are using the latest Raycast API version * Ensure the `platforms` field matching the requirement of your extension, eg. if you use platform-specific APIs, restrict the `platforms` field to the corresponding platform * Please use `npm` for installing dependencies and include `package-lock.json` in your pull request. We use `npm` on our Continuous Integration (CI) environment when building and publishing extensions so, by providing a `package-lock.json` file, we ensure that the dependencies on the server match the same versions as your local dependencies. * Please check the terms of service of third-party services that your extension uses. * Read the [Extension Guidelines](https://manual.raycast.com/extensions) and make sure that your Extension comply with it. * Make sure to **run a distribution build** with `npm run build` locally before submitting the extension for review. This will perform additional type checking and create an optimized build. Open the extension in Raycast to check whether everything works as expected with the distribution build. In addition, you can perform linting and code style checks by running `npm run lint`. (Those checks will later also run via automated GitHub checks.) ## Extensions and Commands Naming * Extension and command titles should follow [**Apple Style Guide**](https://help.apple.com/applestyleguide/#/apsgb744e4a3?sub=apdca93e113f1d64) convention * ✅ `Google Workplace`, `Doppler Share Secrets`, `Search in Database` * ❌ `Hacker news`, `my issues` * 🤔 It's okay to use lower case for names and trademarks that are canonically written with lower case letters. E.g. `iOS` , `macOS` , `npm`. * **Extension title** * It will be used only in the Store and in the preferences * Make it easy for people to understand what it does when they see it in the Store * ✅ `Emoji Search`, `Airport - Discover Testflight Apps`, `Hacker News` * ❌ `Converter`, `Images`, `Code Review`, `Utils` * 🤔 In some cases, you can add additional information to the title similar to the Airport example above. Only do so if it adds context. * 💡 You can use more creative titles to differentiate your extension from other extensions with similar names. * Aim to use nouns rather than verbs * `Emoji Search` is better than `Search Emoji` * Avoid generic names for an extension when your extension doesn't provide a lot of commands * E.g. if your extension can only search pages in Notion, name it `Notion Search` instead of just `Notion`. This will help users to form the right expectations about your extension. If your extension covers a lot of functionality, it's okay to use a generic name like `Notion`. Example: [GitLab](https://www.raycast.com/tonka3000/gitlab). * **Rule of thumb:** If your extension has only one command, you probably need to name the extension close to what this command does. Example: [Visual Studio Code Recent Projects](https://www.raycast.com/thomas/visual-studio-code) instead of just `Visual Studio Code`. * **Extension description** * In one sentence, what does your extension do? This will be shown in the list of extensions in the Store. Keep it short and descriptive. See how other approved extensions in the Store do it for inspiration. * **Command title** * Usually it's ` ` structure or just `` * The best approach is to see how other commands are named in Raycast to get inspiration * ✅ `Search Recent Projects`, `Translate`, `Open Issues`, `Create Task` * ❌ `Recent Projects Search`, `Translation`, `New Task` * Avoid articles * ✅ `Search Emoji`, `Create Issue` * ❌ `Search an Emoji`, `Create an Issue` * Avoid just giving it a service name, be more specific about what the command does * ✅ `Search Packages` * ❌ `NPM` * **Command subtitle** * Use subtitles to add context to your command. Usually, it's an app or service name that you integrate with. It makes command names more lightweight and removes the need to specify a service name in the command title. * The subtitle is indexed so you can still search using subtitle and title: `xcode recent projects` would return `Search Recent Projects` in the example above. * Don't use subtitles as descriptions for your command * ❌ `Quickly open Xcode recent projects` * Don't use a subtitle if it doesn't add context. Usually, this is the case with single command extensions. * There is no need for a subtitle for the `Search Emoji` command since it's self-explanatory * **Rule of thumb:** If your subtitle is almost a duplication of your command title, you probably don't need it ![Example of a good subtitle](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-d482752e8c76fe94fcdd4adf8d5b8ab0b94126d9%2Fgood-subtitle.webp?alt=media) ## Extension Icon {% hint style="info" %} We made a new icon generator tool to ease the process of creating icons for your extensions. You can find it [here](https://icon.ray.so/). {% endhint %} * The published extension in the Store should have a 512x512px icon in `png` format * The icon should look good in both light and dark themes (you can switch the theme in Raycast Preferences → Appearance) * If you have separate light and dark icons, refer to the `package.json` [manifest](https://developers.raycast.com/information/manifest#extension-properties) documentation on how to configure them * Extensions that use the default Raycast icon will be rejected * This [Icon Template](https://www.figma.com/community/file/1030764827259035122/Extensions-Icon-Template) can help you with making and exporting a proper icon * Make sure to remove unused assets and icons * 💡 If you feel like designing icons is not up to your alley, ask [community](https://raycast.com/community) for help (#extensions channel) ## Provide README if Additional Configuration Required * If your extension requires additional setup, such as getting an API access token, enabling some preferences in other applications, or has non-trivial use cases, please provide a README file at the root folder of your extension. When a README is provided, users will see the "About This Extension" button on the preferences onboarding screen. * Supporting README media: Put all linked media files in a top-level `media` folder inside your extension directory. (This is different from assets that are required at runtime in your extension: they go inside the assets folder and will be bundled into your extension.) ![Onboarding button linking to the README file](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8bc31854cf0c64fdff60dd1782a7cce0c6acb97b%2Frequired-preference.webp?alt=media) ## Categories ![Categories shown on an extension details screen](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-b96d3ca00c7089cee075510e1e4b13a60eddfe64%2Fcategories-focus.webp?alt=media) * All extensions should be published with at least one category * Categories are case-sensitive and should follow the [Title Case](https://titlecaseconverter.com/rules/) convention * Add categories in the `package.json` [manifest](https://developers.raycast.com/information/manifest) file or select the categories when you create a new extension using the **Create Extension** command ### All Categories | Category | Example | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Applications | [Cleanshot X](https://www.raycast.com/Aayush9029/cleanshotx) – Capture and record your screen | | Communication | [Slack Status](https://www.raycast.com/petr/slack-status) – Quickly change your Slack status. | | Data | [Random Data Generator](https://www.raycast.com/loris/random) – Generate random data using Faker library. | | Documentation | [Tailwind CSS Documentation](https://www.raycast.com/vimtor/tailwindcss) – Quickly search Tailwind CSS documentation and open it in the browser. | | Design Tools | [Figma File Search](https://www.raycast.com/michaelschultz/figma-files-raycast-extension) – Lists Figma files allowing you to search and navigate to them. | | Developer Tools | [Brew](https://www.raycast.com/nhojb/brew) – Search and install Homebrew formulae. | | Finance | [Coinbase Pro](https://www.raycast.com/farisaziz12/coinbase-pro) – View your Coinbase Pro portfolio. | | Fun | [8 Ball](https://www.raycast.com/rocksack/8-ball) – Returns an 8 ball like answer to questions. | | Media | [Unsplash](https://www.raycast.com/eggsy/unsplash) – Search images or collections on Unsplash, download, copy or set them as wallpaper without leaving Raycast. | | News | [Hacker News](https://www.raycast.com/thomas/hacker-news) – Read the latest stories of Hacker News. | | Productivity | [Todoist](https://www.raycast.com/thomaslombart/todoist) – Check your Todoist tasks and quickly create new ones. | | Security | [1Password 7](https://www.raycast.com/khasbilegt/1password7) – Search, open or edit your 1Password 7 passwords from Raycast. | | System | [Coffee](https://www.raycast.com/mooxl/coffee) – Prevent the sleep function on your mac. | | Web | [Wikipedia](https://www.raycast.com/vimtor/wikipedia) – Search Wikipedia articles and view them. | | Other | To be used if you think your extension doesn’t fit in any of the above categories. | ## Screenshots ![An example of an extension with screenshot metadata](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-fd3ba451deb85e08069572e61a3d138530f3e27a%2Fhn-store.webp?alt=media) * Screenshots are displayed in the metadata of an extension details screen, where users can click and browse through them to understand what your extension does in greater detail, before installing * You can add a maximum of six screenshots. We recommend adding at least three, so your extensions detail screen looks beautiful. ### Adding Screenshots In Raycast 1.37.0+ we made it easy for you to take beautiful pixel perfect screenshots of your extension with an ease. #### How to use it? 1. Set up Window Capture in Advanced Preferences (Hotkey e.g.: `⌘⇧⌥+M`) 2. Ensure your extension is opened in development mode (Window Capture eliminates dev-related menus/icons). 3. Open the command 4. Press the hotkey, remember to tick `Save to Metadata` {% hint style="info" %} This tool will use your current background. Choose a background image with a good contrast that makes it clear and easy to see the app and extension you've made. You can use [Raycast Wallpapers](https://www.raycast.com/wallpapers) to make your background look pretty {% endhint %} ### Specifications | Screenshot size | Aspect ratio | Format | Dark mode support | | ------------------------------ | ------------ | ------ | ----------------- | | 2000 x 1250 pixels (landscape) | 16:10 | PNG | No | ### Do's & Dont's * ✅ Choose a background with good contrast, that makes it clear and easy to see the app and extension you’ve made * ✅ Select the most informative commands to showcase what your extension does – focus on giving the user as much detail as possible * ❌ Do not use multiple backgrounds for different screenshots – be consistent and use the same across all screenshots * ❌ Do not share sensitive data in your screenshots – these will be visible in the Store, as well as the Extension repository on GitHub * ❌ Do not include screenshots of other applications - keep the focus entirely on your extension within Raycast * ❌ Avoid using screenshots in different themes (light and dark), unless it is to demonstrate what your extension does ## Version History ![A CHANGELOG.md file displayed in the app](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-4e39429865f934bd7684631765f8cbfa902750f9%2Fversion-history.webp?alt=media) * Make it easier for users to see exactly what notable changes have been made between each release of your extension with a `CHANGELOG.md` file in your extension metadata * To add Version History to your extension, add a `CHANGELOG.md` file to the root folder of your extension * See an extension files structure with [screenshots and a changelog file](#adding-screenshots) * With each modification, provide clear and descriptive details regarding the latest update, accompanied by a title formatted as an h2 header followed by `{PR_MERGE_DATE}`. This placeholder will be automatically replaced when the pull request is merged. While you may still use the date timestamp format YYYY-MM-DD, it is often more practical to use `{PR_MERGE_DATE}` since merging of a pull request can take several days (depending on the review comments, etc.). * Make sure your change title is within square brackets * Separate your title and date with a hyphen `-` and spaces either side of the hyphen * Below is an example of a changelog that follows the correct format ```markdown # Brew Changelog ## [Added a bunch of new feedback] - {PR_MERGE_DATE} - Improve reliability of `outdated` command - Add action to copy formula/cask name - Add cask name & tap to cask details - Add Toast action to cancel current action - Add Toast action to copy error log after failure ## [New Additions] - 2022-12-13 - Add greedy upgrade preference - Add `upgrade` command ## [Fixes & Bits] - 2021-11-19 - Improve discovery of brew prefix - Update Cask.installed correctly after installation - Fix installed state after uninstalling search result - Fix cache check after installing/uninstalling cask - Add uninstall action to outdated action panel ## [New Commands] - 2021-11-04 Add support for searching and managing casks ## [Added Brew] - 2021-10-26 Initial version code ``` ![An extensions version history on raycast.com/store](https://user-images.githubusercontent.com/17166544/159987128-1e9f22a6-506b-4edd-bb40-e121bfdc46f8.png) {% hint style="info" %} You can use [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) to help you format your changelog correctly {% endhint %} ## Contributing to Existing Extensions vs Creating a New One * **When you should contribute to an existing extension instead of creating a new one** * You want to make a small improvement to an extension that is already published, e.g. extra actions, new preference, UX improvements, etc.. Usually, it's a non-significant change. * You want to add a simple command that compliments an existing extension without changing the extension title or description, e.g. you want to add "Like Current Track" command for Spotify. It wouldn't make sense to create a whole new extension just for this when there is already the [Spotify Controls](https://www.raycast.com/thomas/spotify-controls) extension. * **Important:** If your change is significant, it makes sense to contact the author of the extension before you invest a lot of time into it. We cannot merge significant contributions without the author's sign-off. * **When you should consider creating a new extension instead of contributing to an existing one** * The changes to an existing extension would be significant and might break other people's workflows. Check with the author if you want to proceed with the collaboration path. * Your extension provides an integration with the same service but has a different configuration, e.g. one extension could be "GitHub Cloud", another "GitHub Enterprise". One extension could be "Spotify Controls" and only uses AppleScript to play/pause songs, while another extension can provide deeper integration via the API and require an access token setup. There is no reason to try to merge everything together as this would only make things more complicated. * **Multiple simple extensions vs one large one** * If your extension works standalone and brings something new to the Store, it's acceptable to create a new one instead of adding commands to an existing one. E.g. one extension could be "GitHub Repository Search", another one could be "GitHub Issue Search". It should not be the goal to merge all extensions connecting with one service into one mega extension. However, it's also acceptable to merge two extensions under one if the authors decide to do so. ## Binary Dependencies and Additional Configuration * Avoid asking users to perform additional downloads and try to automate as much as possible from the extension, especially if you are targeting non-developers. See the [Speedtest](https://github.com/raycast/extensions/pull/302) extension that downloads a CLI in the background and later uses it under the hood. * If you do end up downloading executable binaries in the background, please make sure it's done from a server that you don't have access to. Otherwise, we cannot guarantee that you won't replace the binary with malicious code after the review. E.g. downloading `speedtest-cli` from [`install.speedtest.net`](http://install.speedtest.net) is acceptable, but doing this from some custom AWS server would lead to a rejection. Add additional integrity checks through hashes. * Don't bundle opaque binaries where sources are unavailable or where it's unclear how they have been built. * Don't bundle heavy binary dependencies in the extension – this would lead to an increased extension download size. * **Examples for interacting with binaries** * ✅ Calling known system binaries * ✅ Binary downloaded or installed from a trusted location with additional integrity checking through hashes (that is, verify whether the downloaded binary really matches the expected binary) * ✅ Binary extracted from an npm package and copied to assets, with traceable sources how the binary is built; **note**: we have yet to integrate CI actions for copying and comparing the files; meanwhile, ask a member of the Raycast team to add the binary for you * ❌ Any binary with unavailable sources or unclear builds just added to the assets folder ## Keychain Access * Extensions requesting Keychain Access will be rejected due to security concerns. If you can't work around this limitation, reach out to us on [Slack](https://raycast.com/community) or via `feedback@raycast.com`. ## UI/UX Guidelines ### Preferences ![Required preferences will be shown when opening the command](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-a1ab8634fce260f92c072f77830f843cbd2a55a2%2Frequired-preferences-2.webp?alt=media) * Use the [preferences API](https://developers.raycast.com/api-reference/preferences) to let your users configure your extension or for providing credentials like API tokens * When using `required: true`, Raycast will ask the user to set preferences before continuing with an extension. See the example [here](https://github.com/raycast/extensions/blob/main/extensions/gitlab/package.json#L150). * You should not build separate commands for configuring your extension. If you miss some API to achieve the preferences setup you want, please file a [GitHub issue](https://github.com/raycast/extensions/issues) with a feature request. ### Action Panel ![Raycast Action Panel component](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-25a03b5271959426230e724a733f30e7597dd1bf%2Faction-panel.webp?alt=media) * Actions in the action panel should also follow the **Title Case** naming convention * ✅ `Open in Browser`, `Copy to Clipboard` * ❌ `Copy url`, `set project`, `Set priority` * Provide icons for actions if there are other actions with icons in the list * Avoid having a list of actions where some have icons and some don't * Add ellipses `…` for actions that will have a submenu. Don't repeat the parent action name in the submenu * ✅ `Set Priority…` and submenu would have `Low`, `Medium`, `High` * ❌ `Set Priority` and submenu would have `Set Priority Low`, `Set Priority Medium`, etc ### Navigation * Use the [Navigation API](https://developers.raycast.com/api-reference/user-interface/navigation) for pushing new screens. This will ensure that a user can navigate within your extension the same way as in the rest of the application. * Avoid introducing your own navigation stack. Extensions that just replace the view's content when it's expected to push a new screen will be rejected. ### Empty States * When you update lists with an empty array of elements, the "No results" view will be shown. You can customize this view by using the [List.EmptyView](https://developers.raycast.com/api-reference/user-interface/list#list.emptyview) or [Grid.EmptyView](https://developers.raycast.com/api-reference/user-interface/grid#grid.emptyview) components. * **Common mistake** - "flickering empty state view" on start * If you try rendering an empty list before real data arrives (e.g. from the network or disk), you might see a flickering "No results" view when opening the extension. To prevent this, make sure not to return an empty list of items before you get the data you want to display. In the meantime, you can show the loading indicator. See [this example](https://developers.raycast.com/information/best-practices#show-loading-indicator). ### Navigation Title * Don't change the `navigationTitle` in the root command - it will be automatically set to the command name. Use `navigationTitle` only in nested screens to provide additional context. See [Slack Status extension](https://github.com/raycast/extensions/blob/020f2232aa5579b5c63b4b3c08d23ad719bce1f8/extensions/slack-status/src/setStatusForm.tsx#L95) as an example of correct usage of the `navigationTitle` property. * Avoid long titles. If you can't predict how long the navigation title string will be, consider using something else. E.g. in the Jira extension, we use the issue key instead of the issue title to keep it short. * Avoid updating the navigation title multiple times on one screen depending on some state. If you find yourself doing it, there is a high chance you are misusing it. ### Placeholders in Text Fields * For a better visual experience, add placeholders in text field and text area components. This includes preferences. * Don't leave the search bar without a placeholder ### Analytics * It’s not allowed to include external analytics in extensions. Later on, we will add support to give developers more insights into how their extension is being used. ### Localization / Language * At the moment, Raycast doesn't support localization and only supports US English. Therefore, please avoid introducing your custom way to localize your extension. If the locale might affect functionality (e.g. using the correct unit of measurement), please use the preferences API. * Use US English spelling (not British) --- # Source: https://developers.raycast.com/teams/publish-a-private-extension.md # Publish a Private Extension To publish an extension, run `npm run publish` in the extension directory. The command verifies, builds and publishes the extension to the owner's store. The extension is only available to members of this organization. A link to your extension is copied to your clipboard to share it with your teammates. Happy publishing 🥳 To mark an extension as private, you need to set the `owner` field in your `package.json` to your organization handle. If you don't know your handle, open the Manage Organization command, select your organization in the dropdown on the top right and perform the Copy Organization Handle action (`⌘` `⇧` `.`). {% hint style="info" %} Use the Create Extension command to create a private extension for your organization. {% endhint %} To be able to publish a private extension to an organization, you need to be logged in. Raycast takes care of logging you in with the CLI as well. In case you aren't logged in or need to switch an account, you can run `npx ray login` and `npx ray logout`. --- # Source: https://developers.raycast.com/basics/publish-an-extension.md # Publish an Extension Before you publish your extension, take a look at [how to prepare your extension](https://developers.raycast.com/basics/prepare-an-extension-for-store) for the Store. Making sure you follow the guidelines is the best way to help your extension pass the review. ### Validate your extension Open your terminal, navigate to your extension directory, and run `npm run build` to verify your extension. The command should complete without any errors. {% hint style="info" %} `npm run build` validates your extension for distribution without publishing it to the store. Read more about it [here](https://developers.raycast.com/information/developer-tools/cli#build). {% endhint %} ### Publish your extension To share your extension with others, navigate to your extension directory, and run `npm run publish` to publish your extension. {% hint style="info" %} It is possible that the `publish` script doesn't exist (usually because the extension was created before the template was updated to include it). In that case, you can add the following line in the `scripts` object of the package.json `"publish": "npx @raycast/api@latest publish"` and then run `npm run publish` again. {% endhint %} You will be asked to authenticate with GitHub because the script will automatically open a pull request in our [`raycast/extensions`](https://github.com/raycast/extensions) repository. The command will squash commits and their commit messages. If you want more control, see the [alternative way](#alternative-way) below. {% hint style="info" %} If someone contributes to your extension, or you make edits directly on GitHub, running `npm run publish` will fail until you run ```bash npx @raycast/api@latest pull-contributions ``` in your git repository. This will merge the contributions with your code, asking you to fix the conflicts if any. {% endhint %} Once the pull request is opened, you can continue pushing more commits to it by running `npm run publish` again. #### Alternative way If you want more control over the publishing process, you can manually do what `npm run publish` does. You need to open a pull request in our [repository](https://github.com/raycast/extensions). For this, [fork our repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo), add your extension to your fork, push your changes, and open a pull request [via the GitHub web interface](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) into our `main` branch. ### Waiting for review After you opened a pull request, we'll review your extension and request changes when required. Once accepted, the pull request is merged and your extension will be automatically published to the [Raycast Store](https://raycast.com/store). {% hint style="info" %} We're still figuring things out and updating our guidelines. If something is unclear, please tell us in [our community](https://raycast.com/community). {% endhint %} ### Share your extension Once your extension is published in the Raycast Store, you can share it with our community. Open the Manage Extensions command, search for your extension and press `⌘` `⌥` `.` to copy the link. ![Manage your extensions](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-ce1f47d3a92c1377fa2cb9ace2eb4bee7cfaa58b%2Fbasics-manage-extensions.webp?alt=media) 🚀 Now it's time to share your work! Tweet about your extension, share it with our [Slack community](https://raycast.com/community) or send it to your teammates. --- # Source: https://developers.raycast.com/utilities/react-hooks.md # React hooks - [useCachedState](/utilities/react-hooks/usecachedstate.md) - [usePromise](/utilities/react-hooks/usepromise.md) - [useCachedPromise](/utilities/react-hooks/usecachedpromise.md) - [useFetch](/utilities/react-hooks/usefetch.md) - [useForm](/utilities/react-hooks/useform.md) - [useExec](/utilities/react-hooks/useexec.md) - [useSQL](/utilities/react-hooks/usesql.md) - [useAI](/utilities/react-hooks/useai.md) - [useFrecencySorting](/utilities/react-hooks/usefrecencysorting.md) - [useStreamJSON](/utilities/react-hooks/usestreamjson.md) - [useLocalStorage](/utilities/react-hooks/uselocalstorage.md) --- # Source: https://developers.raycast.com/readme.md # Introduction Welcome, developers! Our docs cover guides, examples, references, and more to help you build extensions and share them with [our community](https://raycast.com/community) and [your team](https://developers.raycast.com/teams/getting-started). ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8a4f81b2b9ccaf142983dad7a0c7a2ce953467d5%2Fintroduction-hello-world.webp?alt=media) The Raycast Platform consists of two parts: * **API:** This allows developers to build rich extensions with React, Node.js, and TypeScript. The docs explain how to use the API to build top-notch experiences. * **Store:** This lets developers share their extensions with all Raycast users. You'll learn how to [publish your extension](https://developers.raycast.com/basics/publish-an-extension). ## Key features Here are a few points that make our ecosystem special: * **Powerful and familiar tooling:** Extensions are built with TypeScript, React, and Node. Leverage npm's ecosystem to quickly build what you imagine. * **No-brainer to build UI:** You concentrate on the logic, we push the pixels. Use our built-in UI components to be consistent with all our extensions. * **Collaborate with our community:** Build your extension, share it with our community, and get inspired by others. * **Developer experience front and foremost:** A strongly typed API, hot-reloading, and modern tooling that makes it a blast to work with. * **Easy to start, flexible to scale:** Start with a simple script, add a static UI or use React to go wild. Anything goes. ## Overview A quick overview about where to find what in our docs: * [**Basics:**](https://developers.raycast.com/basics/getting-started) Go over this section to learn how to build extensions in our step-by-step guides. * [**Teams:**](https://developers.raycast.com/teams/getting-started) Build and share extensions with your teammates to speed up common workflows. * [**Examples:**](https://developers.raycast.com/examples/doppler) Kickstart your extension by using an open-source example and learn as you go. * [**Information:**](https://developers.raycast.com/information/best-practices) Get the background knowledge to master your understanding of our platform. * [**API Reference:**](https://developers.raycast.com/api-reference/ai) Go into details with the API reference that includes code snippets. * [**Utilities:**](https://developers.raycast.com/utilities/getting-started) A set of utilities to streamline common patterns and operations used in extensions. Now, let's build 💪 --- # Source: https://developers.raycast.com/basics/review-pullrequest.md # Review an Extension in a Pull Request All updates to an extension are made through a [Pull Request](https://github.com/raycast/extensions/pulls) - if you need to review whether the Pull Request works as expected, then you can checkout the fork within a few seconds. ## Steps 1. Open a terminal window 2. Navigate to a folder where you want the repository to land 3. Run the below commands *There are a few things you'll need to find and insert manually in the snippet below* **FORK\_URL** Open the PR and click on the incoming ref as shown below ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-1a66bb065c6f9ccb78f4a42eebdcd3c70837fb1d%2Fgo-to-ref.webp?alt=media) Now click the code button and copy the HTTPS path from the dropdown **BRANCH** You can see the branch on the above image (in this example it’s `notion-quicklinks`) **EXTENSION\_NAME** Click the `Files Changed` tab to see in which directory files have been changed (in this example it’s `notion`) ``` BRANCH="ext/soundboard" FORK_URL="https://github.com/pernielsentikaer/raycast-extensions.git" EXTENSION_NAME="soundboard" git clone -n --depth=1 --filter=tree:0 -b ${BRANCH} ${FORK_URL} cd raycast-extensions git sparse-checkout set --no-cone "extensions/${EXTENSION_NAME}" git checkout cd "extensions/${EXTENSION_NAME}" npm install && npm run dev ``` 4. That's it, the extension should now be attached in Raycast --- # Source: https://developers.raycast.com/utilities/functions/runapplescript.md # runAppleScript Function that executes an AppleScript script. {% hint style="info" %} Only available on macOS {% endhint %} ## Signature There are two ways to use the function. The first one should be preferred when executing a static script. ```ts function runAppleScript( script: string, options?: { humanReadableOutput?: boolean; language?: "AppleScript" | "JavaScript"; signal?: AbortSignal; timeout?: number; parseOutput?: ParseExecOutputHandler; }, ): Promise; ``` The second one can be used to pass arguments to a script. ```ts function runAppleScript( script: string, arguments: string[], options?: { humanReadableOutput?: boolean; language?: "AppleScript" | "JavaScript"; signal?: AbortSignal; timeout?: number; parseOutput?: ParseExecOutputHandler; }, ): Promise; ``` ### Arguments * `script` is the script to execute. * `arguments` is an array of strings to pass as arguments to the script. With a few options: * `options.humanReadableOutput` is a boolean to tell the script what form to output. By default, `runAppleScript` returns its results in human-readable form: strings do not have quotes around them, characters are not escaped, braces for lists and records are omitted, etc. This is generally more useful, but can introduce ambiguities. For example, the lists `{"foo", "bar"}` and `{{"foo", {"bar"}}}` would both be displayed as ‘foo, bar’. To see the results in an unambiguous form that could be recompiled into the same value, set `humanReadableOutput` to `false`. * `options.language` is a string to specify whether the script is using [`AppleScript`](https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/introduction/ASLR_intro.html#//apple_ref/doc/uid/TP40000983) or [`JavaScript`](https://developer.apple.com/library/archive/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/Introduction.html#//apple_ref/doc/uid/TP40014508-CH111-SW1). By default, it will assume that it's using `AppleScript`. * `options.signal` is a Signal object that allows you to abort the request if required via an AbortController object. * `options.timeout` is a number. If greater than `0`, the parent will send the signal `SIGTERM` if the script runs longer than timeout milliseconds. By default, the execution will timeout after 10000ms (eg. 10s). * `options.parseOutput` is a function that accepts the output of the script as an argument and returns the data the hooks will return - see [ParseExecOutputHandler](#parseexecoutputhandler). By default, the function will return `stdout` as a string. ### Return Returns a Promise which resolves to a string by default. You can control what it returns by passing `options.parseOutput`. ## Example ```tsx import { showHUD } from "@raycast/api"; import { runAppleScript } from "@raycast/utils"; export default async function () { const res = await runAppleScript( ` on run argv return "hello, " & item 1 of argv & "." end run `, ["world"], ); await showHUD(res); } ``` ## Types ### ParseExecOutputHandler A function that accepts the output of the script as an argument and returns the data the function will return. ```ts export type ParseExecOutputHandler = (args: { /** The output of the script on stdout. */ stdout: string; /** The output of the script on stderr. */ stderr: string; error?: Error | undefined; /** The numeric exit code of the process that was run. */ exitCode: number | null; /** The name of the signal that was used to terminate the process. For example, SIGFPE. */ signal: NodeJS.Signals | null; /** Whether the process timed out. */ timedOut: boolean; /** The command that was run, for logging purposes. */ command: string; /** The options passed to the script, for logging purposes. */ options?: ExecOptions | undefined; }) => T; ``` --- # Source: https://developers.raycast.com/utilities/functions/runpowershellscript.md # runPowerShellScript Function that executes an PowerShell script. {% hint style="info" %} Only available on Windows {% endhint %} ## Signature ```ts function runPowerShellScript( script: string, options?: { signal?: AbortSignal; timeout?: number; parseOutput?: ParseExecOutputHandler; }, ): Promise; ``` ### Arguments * `script` is the script to execute. With a few options: * `options.signal` is a Signal object that allows you to abort the request if required via an AbortController object. * `options.timeout` is a number. If greater than `0`, the parent will send the signal `SIGTERM` if the script runs longer than timeout milliseconds. By default, the execution will timeout after 10000ms (eg. 10s). * `options.parseOutput` is a function that accepts the output of the script as an argument and returns the data the hooks will return - see [ParseExecOutputHandler](#parseexecoutputhandler). By default, the function will return `stdout` as a string. ### Return Returns a Promise which resolves to a string by default. You can control what it returns by passing `options.parseOutput`. ## Example ```tsx import { showHUD } from "@raycast/api"; import { runPowerShellScript } from "@raycast/utils"; export default async function () { const res = await runPowerShellScript( ` Write-Host "hello, world." `, ); await showHUD(res); } ``` ## Types ### ParseExecOutputHandler A function that accepts the output of the script as an argument and returns the data the function will return. ```ts export type ParseExecOutputHandler = (args: { /** The output of the script on stdout. */ stdout: string; /** The output of the script on stderr. */ stderr: string; error?: Error | undefined; /** The numeric exit code of the process that was run. */ exitCode: number | null; /** The name of the signal that was used to terminate the process. For example, SIGFPE. */ signal: NodeJS.Signals | null; /** Whether the process timed out. */ timedOut: boolean; /** The command that was run, for logging purposes. */ command: string; /** The options passed to the script, for logging purposes. */ options?: ExecOptions | undefined; }) => T; ``` --- # Source: https://developers.raycast.com/information/security.md # Security {% hint style="info" %} Note that this is *not* a guide on how to create secure Raycast extensions but rather an overview of security-related aspects on how extensions are built, distributed and run. {% endhint %} ## Raycast Raycast itself runs outside of the App Store as "Developer ID Application", **signed** with the Raycast certificate and verified by Apple's **notarization service** before the app is distributed. Raycast provides various commands that interact with OS-level functionality, some of which prompt the user for granting **permissions** when required. The app is **automatically kept up-to-date** to minimize the risk of running heavily outdated versions and to ship hotfixes quickly. Raycast is a local-first application that stores user data in a local **encrypted database**, makes use of the system **Keychain** where secure data is stored, and generally connects to third-party APIs directly rather than proxying data through Raycast servers. ## Publishing Process All extensions are **open source** so the current source code can be inspected at all times. Before an extension gets merged into the **public repository**, members from Raycast and the community collaboratively **review** extensions, and follow our **store guidelines**. After the code review, the Continuous Integration system performs a set of **validations** to make sure that manifest conforms to the defined schema, required assets have the correct format, the author is valid, and no build and type errors are present. (More CI pipeline tooling for automated static security analysis is planned.) The built extension is then **archived and uploaded** to the Raycast Store, and eventually published for a registered user account. When an extension is installed or updated, the extension is downloaded from the store, unarchived to disk, and a record is updated in the local Raycast database. End-users install extensions through the built-in store or the web store. ## Runtime Model In order to run extensions, Raycast launches a **single child Node.js process** where extensions get loaded and unloaded as needed; inter-process communication with Raycast happens through standard file handles and a thin RPC protocol that only exposes a **defined set of APIs**, that is, an extension cannot just perform any Raycast operation. The **Node runtime is managed** by Raycast and automatically downloaded to the user's machine. We use an official version and **verify the Node binary** to ensure it has not been tampered with. An extension runs in its own **v8 isolate** (worker thread) and gets its own event loop, JavaScript engine and Node instance, and limited heap memory. That way, we ensure **isolation between extensions** when future Raycast versions may support background executions of multiple extensions running concurrently. ## Permissions Extensions are **not further sandboxed** as far as policies for file I/O, networking, or other features of the Node runtime are concerned; this might change in the future as we want to carefully balance user/developer experience and security needs. By default and similar to other macOS apps, accessing special directories such as the user Documents directory or performing screen recording first requires users to give **permissions** to Raycast (parent process) via the **macOS Security & Preferences** pane, otherwise programmatic access is not permitted. ## Data Storage While extensions can access the file system and use their own methods of storing and accessing data, Raycast provides **APIs for securely storing data**: *password* preferences can be used to ask users for values such as access tokens, and the local storage APIs provide methods for reading and writing data payloads. In both cases, the data is stored in the local encrypted database and can only be accessed by the corresponding extension. ## Automatic Updates Both Raycast itself and extensions are **automatically updated** and we think of this as a security feature since countless exploits have happened due to outdated and vulnerable software. Our goal is that neither developers nor end-users need to worry about versions, and we **minimize the time from update to distribution** to end-users. --- # Source: https://developers.raycast.com/utilities/functions/showfailuretoast.md # showFailureToast Function that shows a failure [Toast](https://developers.raycast.com/api-reference/feedback/toast) for a given Error. ## Signature ```ts function showFailureToast( error: unknown, options?: { title?: string; primaryAction?: Toast.ActionOptions; }, ): Promise; ``` ### Arguments * `error` is the error to report. With a few options: * `options.title` is a string describing the action that failed. By default, `"Something went wrong"` * `options.primaryAction` is a Toast [Action](https://developers.raycast.com/api-reference/feedback/toast#toast.actionoptions). ### Return Returns a [Toast](https://developers.raycast.com/api-reference/feedback/toast). ## Example ```tsx import { showHUD } from "@raycast/api"; import { runAppleScript, showFailureToast } from "@raycast/utils"; export default async function () { try { const res = await runAppleScript( ` on run argv return "hello, " & item 1 of argv & "." end run `, ["world"], ); await showHUD(res); } catch (error) { showFailureToast(error, { title: "Could not run AppleScript" }); } } ``` --- # Source: https://developers.raycast.com/examples/spotify-controls.md # Spotify Controls {% hint style="info" %} The source code of the example can be found [here](https://github.com/raycast/extensions/tree/main/extensions/spotify-controls#readme). You can install it [here](https://www.raycast.com/thomas/spotify-controls). {% endhint %} This example shows how to build commands that don't show a UI in Raycast. This type of command is useful for interactions with other apps such as skipping songs in Spotify or just simply running some scripts that don't need visual confirmation. ![Example: Control the Spotify macOS app from Raycast](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8a6b74f0afa581918036d18742c8c0e3baa4a37e%2Fexample-spotify-controls.webp?alt=media) ## Control Spotify macOS app Spotify's macOS app supports AppleScript. This is great to control the app without opening it. For this, we use the [`run-applescript`](https://www.npmjs.com/package/run-applescript) package. Let's start by toggling play pause: ```typescript import { runAppleScript } from "run-applescript"; export default async function Command() { await runAppleScript('tell application "Spotify" to playpause'); } ``` ## Close Raycast main window When performing this command, you'll notice that Raycast toggles the play pause state of the Spotify macOS app but the Raycast main window stays open. Ideally the window closes after you run the command. Then you can carry on with what you did before. Here is how you can close the main window: ```typescript import { closeMainWindow } from "@raycast/api"; import { runAppleScript } from "run-applescript"; export default async function Command() { await closeMainWindow(); await runAppleScript('tell application "Spotify" to playpause'); } ``` Notice that we call the `closeMainWindow` function before running the AppleScript. This makes the command feel snappier. With less than 10 lines of code, you executed a script and controlled the UI of Raycast. As a next step you could add more commands to skip a track. --- # Source: https://developers.raycast.com/api-reference/storage.md # Storage The storage APIs can be used to store data in Raycast's [local encrypted database](https://developers.raycast.com/information/security#data-storage). All commands in an extension have shared access to the stored data. Extensions can *not* access the storage of other extensions. Values can be managed through functions such as [`LocalStorage.getItem`](#localstorage.getitem), [`LocalStorage.setItem`](#localstorage.setitem), or [`LocalStorage.removeItem`](#localstorage.removeitem). A typical use case is storing user-related data, for example entered todos. {% hint style="info" %} The API is not meant to store large amounts of data. For this, use [Node's built-in APIs to write files](https://nodejs.org/en/learn/manipulating-files/writing-files-with-nodejs), e.g. to the extension's [support directory](https://developers.raycast.com/environment#environment). {% endhint %} ## API Reference ### LocalStorage.getItem Retrieve the stored value for the given key. #### Signature ```typescript async function getItem(key: string): Promise; ``` #### Example ```typescript import { LocalStorage } from "@raycast/api"; export default async function Command() { await LocalStorage.setItem("favorite-fruit", "apple"); const item = await LocalStorage.getItem("favorite-fruit"); console.log(item); } ``` #### Parameters | Name | Description | Type | | ------------------------------------- | ------------------------------------------ | -------- | | key\* | The key you want to retrieve the value of. | `string` | #### Return A Promise that resolves with the stored value for the given key. If the key does not exist, `undefined` is returned. ### LocalStorage.setItem Stores a value for the given key. #### Signature ```typescript async function setItem(key: string, value: Value): Promise; ``` #### Example ```typescript import { LocalStorage } from "@raycast/api"; export default async function Command() { await LocalStorage.setItem("favorite-fruit", "apple"); const item = await LocalStorage.getItem("favorite-fruit"); console.log(item); } ``` #### Parameters | Name | Description | Type | | --------------------------------------- | --------------------------------------------------------- | ------------------------------------------- | | key\* | The key you want to create or update the value of. | `string` | | value\* | The value you want to create or update for the given key. | [`LocalStorage.Value`](#localstorage.value) | #### Return A Promise that resolves when the value is stored. ### LocalStorage.removeItem Removes the stored value for the given key. #### Signature ```typescript async function removeItem(key: string): Promise; ``` #### Example ```typescript import { LocalStorage } from "@raycast/api"; export default async function Command() { await LocalStorage.setItem("favorite-fruit", "apple"); console.log(await LocalStorage.getItem("favorite-fruit")); await LocalStorage.removeItem("favorite-fruit"); console.log(await LocalStorage.getItem("favorite-fruit")); } ``` #### Parameters | Name | Description | Type | | ------------------------------------- | ---------------------------------------- | -------- | | key\* | The key you want to remove the value of. | `string` | #### Return A Promise that resolves when the value is removed. ### LocalStorage.allItems Retrieve all stored values in the local storage of an extension. #### Signature ```typescript async function allItems(): Promise; ``` #### Example ```typescript import { LocalStorage } from "@raycast/api"; interface Values { todo: string; priority: number; } export default async function Command() { const items = await LocalStorage.allItems(); console.log(`Local storage item count: ${Object.entries(items).length}`); } ``` #### Return A Promise that resolves with an object containing all [Values](#localstorage.values). ### LocalStorage.clear Removes all stored values of an extension. #### Signature ```typescript async function clear(): Promise; ``` #### Example ```typescript import { LocalStorage } from "@raycast/api"; export default async function Command() { await LocalStorage.clear(); } ``` #### Return A Promise that resolves when all values are removed. ## Types ### LocalStorage.Values Values of local storage items. For type-safe values, you can define your own interface. Use the keys of the local storage items as the property names. #### Properties | Name | Type | Description | | -------------- | ----- | --------------------------------------- | | \[key: string] | `any` | The local storage value of a given key. | ### LocalStorage.Value ```typescript Value: string | number | boolean; ``` Supported storage value types. #### Example ```typescript import { LocalStorage } from "@raycast/api"; export default async function Command() { // String await LocalStorage.setItem("favorite-fruit", "cherry"); // Number await LocalStorage.setItem("fruit-basket-count", 3); // Boolean await LocalStorage.setItem("fruit-eaten-today", true); } ``` --- # Source: https://developers.raycast.com/information/developer-tools/templates.md # Templates Raycast provides a variety of templates to kickstart your extension. Raycast provides 3 types of templates: * **Commands:** These are templates for [commands](https://developers.raycast.com/information/terminology). * **Tools:** These are templates for [tools](https://developers.raycast.com/terminology#tool). You can select a different one for each tool that you add to your extension. * **Extension Boilerplates:** These are fully built extensions designed to be tweaked by organizations for internal use. ## Commands ### Show Detail
Renders a simple Hello World from a markdown string. ![Detail Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-e9490e58b081fe70c0c380534a452f981ed4d786%2Fdetail-template.webp?alt=media) {% hint style="info" %} See the [API Reference](https://developers.raycast.com/api-reference/user-interface/detail) for more information about customization. {% endhint %}
### Submit Form
Renders a form that showcases all available form elements. ![Submit Form Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-ee2d5d4c3fa780b7f14309bf826073e9dde9a96e%2Fform-template.webp?alt=media) {% hint style="info" %} See the [API Reference](https://developers.raycast.com/api-reference/user-interface/form) for more information about customization. {% endhint %}
### Show Grid
Renders a grid of Icons available from Raycast. Defaults to a large grid, but provides a selection menu to change the size. ![Grid Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-585a703929e53a7f7f995ed2b6bf8e0a48f04faa%2Fgrid-template.webp?alt=media) {% hint style="info" %} See the [API Reference](https://developers.raycast.com/api-reference/user-interface/grid) for more information about customization. See here for information about [Icons](https://developers.raycast.com/api-reference/user-interface/icons-and-images). {% endhint %}
### Show List and Detail
Renders a list of options. When an option is selected, a Detail view is displayed. ![List and Detail Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-2b379c00faa242c756f1a2aaad0a054b1648f8d6%2Flist-detail-template.webp?alt=media) {% hint style="info" %} See the [API Reference](https://developers.raycast.com/api-reference/user-interface/list) for more information about customization. {% endhint %}
### Menu Bar Extra
Adds a simple Menu Bar Extra with a menu. ![Menu Bar Extra Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-7bd81f2bcee7918b6921e552dae157b7f9c21733%2Fmenu-bar-template.webp?alt=media) {% hint style="info" %} See the [API Reference](https://developers.raycast.com/api-reference/menu-bar-commands) for more information about customization. {% endhint %}
### Run Script A example of a no-view command which shows a simple [HUD](https://developers.raycast.com/api-reference/feedback/hud). ### Show List
Renders a static list with each entry containing an icon, title, subtitle, and accessory. ![List Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-c15c646bcffee77a3945ebb25a79a7f03f0e6e2f%2Flist-template.webp?alt=media) {% hint style="info" %} See the [API Reference](https://developers.raycast.com/api-reference/user-interface/list) for more information about customization. {% endhint %}
### Show Typeahead Results
Renders a dynamic and searchable list of NPM packages. The command fetches new items as the search is updated by the user. ![Typeahead Results Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-10b73f930dab0d742e677811c13b0b0c0706260b%2Ftypeahead-results-template.webp?alt=media)
### AI
Renders the output of an AI call in a Detail view. ![AI Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-8af8468d2f78f69d28e7da00ef75198fce867b3c%2Fai-template.webp?alt=media)
## Tools
A simple tool which asks for confirmation before executing. ![Tool with Confirmation Template Render](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-48fb942ab974ffd90cba6e3f0659fbd273a9a24f%2Ftool-with-confirmation-template.webp?alt=media) {% hint style="info" %} See the [API Reference](https://developers.raycast.com/api-reference/tool) for more information about customization. {% endhint %}
## Extension Boilerplates The Raycast Team has created high-quality templates to reinforce team experiences with the Raycast API. Run `npm init raycast-extension -t ` to get started with these extensions. All templates can be found on the [templates page](https://www.raycast.com/templates). Specific instructions about customizing the template can be found on the relevant [template page](https://www.raycast.com/templates). Simply customize the template as you see fit, then run `npm run publish` in the extension directory to allow your team to install the extension. --- # Source: https://developers.raycast.com/information/terminology.md # Terminology ## Action Actions are accessible via the [Action Panel](#action-panel) in a [command](#command). They are little functionality to control something; for example, to add a label to the selected GitHub issue, copy the link to a Linear issue, or anything else. Actions can have assigned keyboard shortcuts. ## Action Panel Action Panel is located on the bottom right and can be opened with `⌘` `K`. It contains all currently available [actions](#action) and makes them easily discoverable. ## AI Extensions AI Extensions are simply regular [extensions](#extension) that have [tools](#tool). Once an extension has some tools, a user can `@mention` the extension in Quick AI, or the AI Commands, or the AI Chat. When doing so, the AI will have the opportunity to call one or multiple tools of the extensions mentioned. ## Command Commands are a type of entry point for an extension. Commands are available in the root search of Raycast. They can be a simple script or lists, forms, and more complex UI. ## Extension Extensions add functionality to Raycast. They consist of one or many [commands](#command) and can be installed from the Store. ## Manifest Manifest is the `package.json` file of an [extension](#extension). It's an npm package mixed with Raycast specific metadata. The latter is necessary to identify the package for Raycast and publish it to the Store. ## Tool Tools are a type of entry point for an extension. As opposed to a [command](#command), they don’t show up in the root search and the user can’t directly interact with them. Instead, they are functionalities that the AI can use to interact with an extension. --- # Source: https://developers.raycast.com/api-reference/feedback/toast.md # Toast When an asynchronous operation is happening or when an error is thrown, it's usually a good idea to keep the user informed about it. Toasts are made for that. Additionally, Toasts can have some actions associated to the action they are about. For example, you could provide a way to cancel an asynchronous operation, undo an action, or copy the stack trace of an error. {% hint style="info" %} The `showToast()` will fallback to [showHUD()](https://developers.raycast.com/api-reference/hud#showhud) if the Raycast window is closed. {% endhint %} ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-607b51d1dc3e47c6cdff37be217a0e4d42368a57%2Ftoast.webp?alt=media) ## API Reference ### showToast Creates and shows a Toast with the given [options](#toast.options). #### Signature ```typescript async function showToast(options: Toast.Options): Promise; ``` #### Example ```typescript import { showToast, Toast } from "@raycast/api"; export default async function Command() { const success = false; if (success) { await showToast({ title: "Dinner is ready", message: "Pizza margherita" }); } else { await showToast({ style: Toast.Style.Failure, title: "Dinner isn't ready", message: "Pizza dropped on the floor", }); } } ``` When showing an animated Toast, you can later on update it: ```typescript import { showToast, Toast } from "@raycast/api"; import { setTimeout } from "timers/promises"; export default async function Command() { const toast = await showToast({ style: Toast.Style.Animated, title: "Uploading image", }); try { // upload the image await setTimeout(1000); toast.style = Toast.Style.Success; toast.title = "Uploaded image"; } catch (err) { toast.style = Toast.Style.Failure; toast.title = "Failed to upload image"; if (err instanceof Error) { toast.message = err.message; } } } ``` #### Parameters | Name | Description | Type | | ----------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------- | | options\* | The options to customize the Toast. | [`Alert.Options`](https://developers.raycast.com/api-reference/alert#alert.options) | #### Return A Promise that resolves with the shown Toast. The Toast can be used to change or hide it. ## Types ### Toast A Toast with a certain style, title, and message. Use [showToast](#showtoast) to create and show a Toast. #### Properties | Property | Description | Type | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | | message\* | An additional message for the Toast. Useful to show more information, e.g. an identifier of a newly created asset. | `string` | | primaryAction\* | The primary Action the user can take when hovering on the Toast. | [`Alert.ActionOptions`](https://developers.raycast.com/api-reference/alert#alert.actionoptions) | | secondaryAction\* | The secondary Action the user can take when hovering on the Toast. | [`Alert.ActionOptions`](https://developers.raycast.com/api-reference/alert#alert.actionoptions) | | style\* | The style of a Toast. | [`Action.Style`](https://developers.raycast.com/user-interface/actions#action.style) | | title\* | The title of a Toast. Displayed on the top. | `string` | #### Methods | Name | Type | Description | | ---- | --------------------- | ---------------- | | hide | `() => Promise` | Hides the Toast. | | show | `() => Promise` | Shows the Toast. | ### Toast.Options The options to create a [Toast](#toast). #### Example ```typescript import { showToast, Toast } from "@raycast/api"; export default async function Command() { const options: Toast.Options = { style: Toast.Style.Success, title: "Finished cooking", message: "Delicious pasta for lunch", primaryAction: { title: "Do something", onAction: (toast) => { console.log("The toast action has been triggered"); toast.hide(); }, }, }; await showToast(options); } ``` #### Properties | Property | Description | Type | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | | title\* | The title of a Toast. Displayed on the top. | `string` | | message | An additional message for the Toast. Useful to show more information, e.g. an identifier of a newly created asset. | `string` | | primaryAction | The primary Action the user can take when hovering on the Toast. | [`Alert.ActionOptions`](https://developers.raycast.com/api-reference/alert#alert.actionoptions) | | secondaryAction | The secondary Action the user can take when hovering on the Toast. | [`Alert.ActionOptions`](https://developers.raycast.com/api-reference/alert#alert.actionoptions) | | style | The style of a Toast. | [`Action.Style`](https://developers.raycast.com/user-interface/actions#action.style) | ### Toast.Style Defines the visual style of the Toast. Use [Toast.Style.Success](#toast.style) for confirmations and [Toast.Style.Failure](#toast.style) for displaying errors.\ Use [Toast.Style.Animated](#toast.style) when your Toast should be shown until a process is completed.\ You can hide it later by using [Toast.hide](#toast) or update the properties of an existing Toast. #### Enumeration members | Name | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Animated | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-7e62989ba2fea14db0967e09bb5bfaf84706e12d%2Ftoast-animated.webp?alt=media) | | Success | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-dce0b170c3e47fa4c5525e1a0a74600350006445%2Ftoast-success.webp?alt=media) | | Failure | ![](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-96a3cfd7aaec6933c18481f6228bb647093217ff%2Ftoast-failure.webp?alt=media) | ### Toast.ActionOptions The options to create a [Toast](#toast) Action. #### Properties | Property | Description | Type | | ------------------------------------------ | ----------------------------------------------- | -------------------------------------------------------------------------------- | | onAction\* | A callback called when the action is triggered. | `(toast:` [`Toast`](#toast)`) => void` | | title\* | The title of the action. | `string` | | shortcut | The keyboard shortcut for the action. | [`Keyboard.Shortcut`](https://developers.raycast.com/keyboard#keyboard.shortcut) | --- # Source: https://developers.raycast.com/examples/todo-list.md # Todo List {% hint style="info" %} The source code of the example can be found [here](https://github.com/raycast/extensions/tree/main/examples/todo-list#readme). {% endhint %} What's an example section without a todo list?! Let's put one together in Raycast. This example will show how to render a list, navigate to a form to create a new element and update the list. ![Example: A simple todo list](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-9aaa862f82624bd327d69b52bca9de6f6e12dd13%2Fexample-todo-list.webp?alt=media) ## Render todo list Let's start with a set of todos and simply render them as a list in Raycast: ```typescript import { List } from "@raycast/api"; import { useState } from "react"; interface Todo { title: string; isCompleted: boolean; } export default function Command() { const [todos, setTodos] = useState([ { title: "Write a todo list extension", isCompleted: false }, { title: "Explain it to others", isCompleted: false }, ]); return ( {todos.map((todo, index) => ( ))} ); } ``` For this we define a TypeScript interface to describe out Todo with a `title` and a `isCompleted` flag that we use later to complete the todo. We use [React's `useState` hook](https://reactjs.org/docs/hooks-state.html) to create a local state of our todos. This allows us to update them later and the list will get re-rendered. Lastly we render a list of all todos. ## Create a todo A static list of todos isn't that much fun. Let's create new ones with a form. For this, we create a new React component that renders the form: ```typescript function CreateTodoForm(props: { onCreate: (todo: Todo) => void }) { const { pop } = useNavigation(); function handleSubmit(values: { title: string }) { props.onCreate({ title: values.title, isCompleted: false }); pop(); } return (
} > ); } function CreateTodoAction(props: { onCreate: (todo: Todo) => void }) { return ( } /> ); } ``` The `` shows a single text field for the title. When the form is submitted, it calls the `onCreate` callback and closes itself. ![Create todo form](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-d4c1ff50bde2708dfcf9875a92ab4d0a2a41ef53%2Fexample-create-todo.webp?alt=media) To use the action, we add it to the `` component. This makes the action available when the list is empty which is exactly what we want to create our first todo. ```typescript export default function Command() { const [todos, setTodos] = useState([]); function handleCreate(todo: Todo) { const newTodos = [...todos, todo]; setTodos(newTodos); } return ( } > {todos.map((todo, index) => ( ))} ); } ``` ## Complete a todo Now that we can create new todos, we also want to make sure that we can tick off something on our todo list. For this, we create a `` that we assign to the ``: ```typescript export default function Command() { const [todos, setTodos] = useState([]); // ... function handleToggle(index: number) { const newTodos = [...todos]; newTodos[index].isCompleted = !newTodos[index].isCompleted; setTodos(newTodos); } return ( } > {todos.map((todo, index) => ( handleToggle(index)} /> } /> ))} ); } function ToggleTodoAction(props: { todo: Todo; onToggle: () => void }) { return ( ); } ``` In this case we added the `` to the list item. By doing this we can use the `index` to toggle the appropriate todo. We also added an icon to our todo that reflects the `isCompleted` state. ## Delete a todo Similar to toggling a todo, we also add the possibility to delete one. You can follow the same steps and create a new `` and add it to the ``. ```typescript export default function Command() { const [todos, setTodos] = useState([]); // ... function handleDelete(index: number) { const newTodos = [...todos]; newTodos.splice(index, 1); setTodos(newTodos); } return ( } > {todos.map((todo, index) => ( handleToggle(index)} /> handleDelete(index)} /> } /> ))} ); } // ... function DeleteTodoAction(props: { onDelete: () => void }) { return ( ); } ``` We also gave the `` a keyboard shortcut. This way users can delete todos quicker. Additionally, we also added the `` to the ``. This makes sure that users can also create new todos when there are some already. Finally, our command looks like this: ```typescript import { Action, ActionPanel, Form, Icon, List, useNavigation } from "@raycast/api"; import { useState } from "react"; interface Todo { title: string; isCompleted: boolean; } export default function Command() { const [todos, setTodos] = useState([ { title: "Write a todo list extension", isCompleted: false }, { title: "Explain it to others", isCompleted: false }, ]); function handleCreate(todo: Todo) { const newTodos = [...todos, todo]; setTodos(newTodos); } function handleToggle(index: number) { const newTodos = [...todos]; newTodos[index].isCompleted = !newTodos[index].isCompleted; setTodos(newTodos); } function handleDelete(index: number) { const newTodos = [...todos]; newTodos.splice(index, 1); setTodos(newTodos); } return ( } > {todos.map((todo, index) => ( handleToggle(index)} /> handleDelete(index)} /> } /> ))} ); } function CreateTodoForm(props: { onCreate: (todo: Todo) => void }) { const { pop } = useNavigation(); function handleSubmit(values: { title: string }) { props.onCreate({ title: values.title, isCompleted: false }); pop(); } return (
} > ); } function CreateTodoAction(props: { onCreate: (todo: Todo) => void }) { return ( } /> ); } function ToggleTodoAction(props: { todo: Todo; onToggle: () => void }) { return ( ); } function DeleteTodoAction(props: { onDelete: () => void }) { return ( ); } ``` And that's a wrap. You created a todo list in Raycast, it's that easy. As next steps, you could extract the `` into a separate command. Then you can create todos also from the root search of Raycast and can even assign a global hotkey to open the form. Also, the todos aren't persisted. If you close the command and reopen it, they are gone. To persist, you can use the [storage](https://developers.raycast.com/api-reference/storage) or [write it to disc](https://developers.raycast.com/api-reference/environment#environment). --- # Source: https://developers.raycast.com/api-reference/tool.md # Tool Tools are a type of entry point for an extension. As opposed to a [command](https://developers.raycast.com/information/terminology#command), they don’t show up in the root search and the user can’t directly interact with them. Instead, they are functionalities that the AI can use to interact with an extension. ## Types ### Tool.Confirmation A tool confirmation is used to ask the user to validate the side-effects of the tool. {% hint style="info" %} The tool confirmation is executed *before* the actual tool is executed and receives the same input as the tool. A confirmation returns an optional object that describes what the tool is about to do. It is important to be as clear as possible. If the user confirms the action, the tool will be executed afterwards. If the user cancels the action, the tool will not be executed. {% endhint %} ```ts type Confirmation = (input: T) => Promise< | undefined | { /** * Defines the visual style of the Confirmation. * * @remarks * Use {@link Action.Style.Regular} to display a regular action. * Use {@link Action.Style.Destructive} when your action performs something irreversible like deleting data. * * @defaultValue {@link Action.Style.Regular} */ style?: Action.Style; /** * Some name/value pairs that represents the side-effects of the tool. * * @remarks * Use it to provide more context about the tool to the user. For example, list the files that will be deleted. * * A name/value pair with an optional value won't be displayed if the value is `undefined`. */ info?: { name: string; value?: string; }[]; /** * A string that represents the side-effects of the tool. * * @remarks * Often times this is a question that the user needs to answer. For Example, "Are you sure you want to delete the file?" */ message?: string; /** * An image that visually represents the side-effects of the tool. * * @remarks * Use an image that is relevant to the side-effects of the tool. For example, a screenshot of the files that will be deleted. */ image?: Image.URL | FileIcon; } >; ``` You can return `undefined` to skip the confirmation. This is useful for tools that conditionally perform destructive actions. F.e. when moving a file, you don't need to confirm the action if the file doesn't overwrite another file. #### Example ```typescript import { Tool } from "@raycast/api"; type Input = { /** * The first name of the user to greet */ name: string; }; export const confirmation: Tool.Confirmation = (input) => { return { message: `Are you sure you want to greet ${input.name}?`, }; }; ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/useai.md # useAI Hook which asks the AI to answer a prompt and returns the [AsyncState](#asyncstate) corresponding to the execution of the query. ## Signature ```ts function useAI( prompt: string, options?: { creativity?: AI.Creativity; model?: AI.Model; stream?: boolean; execute?: boolean; onError?: (error: Error) => void; onData?: (data: T) => void; onWillExecute?: (args: Parameters) => void; failureToastOptions?: Partial>; } ): AsyncState & { revalidate: () => void; }; ``` ### Arguments * `prompt` is the prompt to ask the AI. With a few options: * `options.creativity` is a number between 0 and 2 to control the creativity of the answer. Concrete tasks, such as fixing grammar, require less creativity while open-ended questions, such as generating ideas, require more. * `options.model` is a string determining which AI model will be used to answer. * `options.stream` is a boolean controlling whether to stream the answer or only update the data when the entire answer has been received. By default, the `data` will be streamed. Including the [usePromise](https://developers.raycast.com/utilities/react-hooks/usepromise)'s options: * `options.execute` is a boolean to indicate whether to actually execute the function or not. This is useful for cases where one of the function's arguments depends on something that might not be available right away (for example, depends on some user inputs). Because React requires every hook to be defined on the render, this flag enables you to define the hook right away but wait until you have all the arguments ready to execute the function. * `options.onError` is a function called when an execution fails. By default, it will log the error and show a generic failure toast with an action to retry. * `options.onData` is a function called when an execution succeeds. * `options.onWillExecute` is a function called when an execution will start. * `options.failureToastOptions` are the options to customize the title, message, and primary action of the failure toast. ### Return Returns an object with the [AsyncState](#asyncstate) corresponding to the execution of the function as well as a couple of methods to manipulate it. * `data`, `error`, `isLoading` - see [AsyncState](#asyncstate). * `revalidate` is a method to manually call the function with the same arguments again. ## Example ```tsx import { Detail } from "@raycast/api"; import { useAI } from "@raycast/utils"; export default function Command() { const { data, isLoading } = useAI("Suggest 5 jazz songs"); return ; } ``` ## Types ### AsyncState An object corresponding to the execution state of the function. ```ts // Initial State { isLoading: true, // or `false` if `options.execute` is `false` data: undefined, error: undefined } // Success State { isLoading: false, data: string, error: undefined } // Error State { isLoading: false, data: undefined, error: Error } // Reloading State { isLoading: true, data: string | undefined, error: Error | undefined } ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/usecachedpromise.md # useCachedPromise Hook which wraps an asynchronous function or a function that returns a Promise and returns the [AsyncState](#asyncstate) corresponding to the execution of the function. It follows the `stale-while-revalidate` cache invalidation strategy popularized by [HTTP RFC 5861](https://tools.ietf.org/html/rfc5861). `useCachedPromise` first returns the data from cache (stale), then executes the promise (revalidate), and finally comes with the up-to-date data again. The last value will be kept between command runs. {% hint style="info" %} The value needs to be JSON serializable.\ The function is assumed to be constant (eg. changing it won't trigger a revalidation). {% endhint %} ## Signature ```ts type Result = `type of the returned value of the returned Promise`; function useCachedPromise( fn: T, args?: Parameters, options?: { initialData?: U; keepPreviousData?: boolean; abortable?: RefObject; execute?: boolean; onError?: (error: Error) => void; onData?: (data: Result) => void; onWillExecute?: (args: Parameters) => void; failureToastOptions?: Partial>; }, ): AsyncState> & { revalidate: () => void; mutate: MutatePromise | U>; }; ``` ### Arguments * `fn` is an asynchronous function or a function that returns a Promise. * `args` is the array of arguments to pass to the function. Every time they change, the function will be executed again. You can omit the array if the function doesn't require any argument. With a few options: * `options.keepPreviousData` is a boolean to tell the hook to keep the previous results instead of returning the initial value if there aren't any in the cache for the new arguments. This is particularly useful when used for data for a List to avoid flickering. See [Promise Argument dependent on List search text](#promise-argument-dependent-on-list-search-text) for more information. Including the [useCachedState](https://developers.raycast.com/utilities/react-hooks/usecachedstate)'s options: * `options.initialData` is the initial value of the state if there aren't any in the Cache yet. Including the [usePromise](https://developers.raycast.com/utilities/react-hooks/usepromise)'s options: * `options.abortable` is a reference to an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel a previous call when triggering a new one. * `options.execute` is a boolean to indicate whether to actually execute the function or not. This is useful for cases where one of the function's arguments depends on something that might not be available right away (for example, depends on some user inputs). Because React requires every hook to be defined on the render, this flag enables you to define the hook right away but wait until you have all the arguments ready to execute the function. * `options.onError` is a function called when an execution fails. By default, it will log the error and show a generic failure toast with an action to retry. * `options.onData` is a function called when an execution succeeds. * `options.onWillExecute` is a function called when an execution will start. * `options.failureToastOptions` are the options to customize the title, message, and primary action of the failure toast. ### Return Returns an object with the [AsyncState](#asyncstate) corresponding to the execution of the function as well as a couple of methods to manipulate it. * `data`, `error`, `isLoading` - see [AsyncState](#asyncstate). * `revalidate` is a method to manually call the function with the same arguments again. * `mutate` is a method to wrap an asynchronous update and gives some control over how the `useCachedPromise`'s data should be updated while the update is going through. By default, the data will be revalidated (eg. the function will be called again) after the update is done. See [Mutation and Optimistic Updates](#mutation-and-optimistic-updates) for more information. ## Example ```tsx import { Detail, ActionPanel, Action } from "@raycast/api"; import { useCachedPromise } from "@raycast/utils"; export default function Command() { const abortable = useRef(); const { isLoading, data, revalidate } = useCachedPromise( async (url: string) => { const response = await fetch(url, { signal: abortable.current?.signal }); const result = await response.text(); return result; }, ["https://api.example"], { initialData: "Some Text", abortable, }, ); return ( revalidate()} /> } /> ); } ``` ## Promise Argument dependent on List search text By default, when an argument passed to the hook changes, the function will be executed again and the cache's value for those arguments will be returned immediately. This means that in the case of new arguments that haven't been used yet, the initial data will be returned. This behaviour can cause some flickering (initial data -> fetched data -> arguments change -> initial data -> fetched data, etc.). To avoid that, we can set `keepPreviousData` to `true` and the hook will keep the latest fetched data if the cache is empty for the new arguments (initial data -> fetched data -> arguments change -> fetched data). ```tsx import { useState } from "react"; import { List, ActionPanel, Action } from "@raycast/api"; import { useCachedPromise } from "@raycast/utils"; export default function Command() { const [searchText, setSearchText] = useState(""); const { isLoading, data } = useCachedPromise( async (url: string) => { const response = await fetch(url); const result = await response.json(); return result; }, [`https://api.example?q=${searchText}`], { // to make sure the screen isn't flickering when the searchText changes keepPreviousData: true, }, ); return ( {(data || []).map((item) => ( ))} ); } ``` ## Mutation and Optimistic Updates In an optimistic update, the UI behaves as though a change was successfully completed before receiving confirmation from the server that it was - it is being optimistic that it will eventually get the confirmation rather than an error. This allows for a more responsive user experience. You can specify an `optimisticUpdate` function to mutate the data in order to reflect the change introduced by the asynchronous update. When doing so, you can specify a `rollbackOnError` function to mutate back the data if the asynchronous update fails. If not specified, the data will be automatically rolled back to its previous value (before the optimistic update). ```tsx import { Detail, ActionPanel, Action, showToast, Toast } from "@raycast/api"; import { useCachedPromise } from "@raycast/utils"; export default function Command() { const { isLoading, data, mutate } = useCachedPromise( async (url: string) => { const response = await fetch(url); const result = await response.text(); return result; }, ["https://api.example"], ); const appendFoo = async () => { const toast = await showToast({ style: Toast.Style.Animated, title: "Appending Foo" }); try { await mutate( // we are calling an API to do something fetch("https://api.example/append-foo"), { // but we are going to do it on our local data right away, // without waiting for the call to return optimisticUpdate(data) { return data + "foo"; }, }, ); // yay, the API call worked! toast.style = Toast.Style.Success; toast.title = "Foo appended"; } catch (err) { // oh, the API call didn't work :( // the data will automatically be rolled back to its previous value toast.style = Toast.Style.Failure; toast.title = "Could not append Foo"; toast.message = err.message; } }; return ( appendFoo()} /> } /> ); } ``` ## Pagination {% hint style="info" %} When paginating, the hook will only cache the result of the first page. {% endhint %} The hook has built-in support for pagination. In order to enable pagination, `fn`'s type needs to change from > an asynchronous function or a function that returns a Promise to > a function that returns an asynchronous function or a function that returns a Promise In practice, this means going from ```ts const { isLoading, data } = useCachedPromise( async (searchText: string) => { const response = await fetch(`https://api.example?q=${searchText}`); const data = await response.json(); return data; }, [searchText], { // to make sure the screen isn't flickering when the searchText changes keepPreviousData: true, }, ); ``` to ```ts const { isLoading, data, pagination } = useCachedPromise( (searchText: string) => async (options) => { const response = await fetch(`https://api.example?q=${searchText}&page=${options.page}`); const { data } = await response.json(); const hasMore = options.page < 50; return { data, hasMore }; }, [searchText], { // to make sure the screen isn't flickering when the searchText changes keepPreviousData: true, }, ); ``` or, if your data source uses cursor-based pagination, you can return a `cursor` alongside `data` and `hasMore`, and the cursor will be passed as an argument the next time the function gets called: ```ts const { isLoading, data, pagination } = useCachedPromise( (searchText: string) => async (options) => { const response = await fetch(`https://api.example?q=${searchText}&cursor=${options.cursor}`); const { data, nextCursor } = await response.json(); const hasMore = nextCursor !== undefined; return { data, hasMore, cursor: nextCursor }; }, [searchText], { // to make sure the screen isn't flickering when the searchText changes keepPreviousData: true, }, ); ``` You'll notice that, in the second case, the hook returns an additional item: `pagination`. This can be passed to Raycast's `List` or `Grid` components in order to enable pagination.\ Another thing to notice is that the async function receives a [PaginationOptions](#paginationoptions) argument, and returns a specific data format: ```ts { data: any[]; hasMore: boolean; cursor?: any; } ``` Every time the promise resolves, the hook needs to figure out if it should paginate further, or if it should stop, and it uses `hasMore` for this.\ In addition to this, the hook also needs `data`, and needs it to be an array, because internally it appends it to a list, thus making sure the `data` that the hook *returns* always contains the data for all of the pages that have been loaded so far. ### Full Example ```tsx import { setTimeout } from "node:timers/promises"; import { useState } from "react"; import { List } from "@raycast/api"; import { useCachedPromise } from "@raycast/utils"; export default function Command() { const [searchText, setSearchText] = useState(""); const { isLoading, data, pagination } = useCachedPromise( (searchText: string) => async (options: { page: number }) => { await setTimeout(200); const newData = Array.from({ length: 25 }, (_v, index) => ({ index, page: options.page, text: searchText, })); return { data: newData, hasMore: options.page < 10 }; }, [searchText], ); return ( {data?.map((item) => ( ))} ); } ``` ## Types ### AsyncState An object corresponding to the execution state of the function. ```ts // Initial State { isLoading: true, // or `false` if `options.execute` is `false` data: undefined, error: undefined } // Success State { isLoading: false, data: T, error: undefined } // Error State { isLoading: false, data: undefined, error: Error } // Reloading State { isLoading: true, data: T | undefined, error: Error | undefined } ``` ### MutatePromise A method to wrap an asynchronous update and gives some control about how the `useCachedPromise`'s data should be updated while the update is going through. ```ts export type MutatePromise = ( asyncUpdate?: Promise, options?: { optimisticUpdate?: (data: T) => T; rollbackOnError?: boolean | ((data: T) => T); shouldRevalidateAfter?: boolean; }, ) => Promise; ``` ### PaginationOptions An object passed to a `PaginatedPromise`, it has two properties: * `page`: 0-indexed, this it's incremented every time the promise resolves, and is reset whenever `revalidate()` is called. * `lastItem`: this is a copy of the last item in the `data` array from the last time the promise was executed. Provided for APIs that implement cursor-based pagination. * `cursor`: this is the `cursor` property returned after the previous execution of `PaginatedPromise`. Useful when working with APIs that provide the next cursor explicitly. ```ts export type PaginationOptions = { page: number; lastItem?: T; cursor?: any; }; ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/usecachedstate.md # useCachedState Hook which returns a stateful value, and a function to update it. It is similar to React's `useState` but the value will be kept between command runs. {% hint style="info" %} The value needs to be JSON serializable. {% endhint %} ## Signature ```ts function useCachedState( key: string, initialState?: T, config?: { cacheNamespace?: string; }, ): [T, (newState: T | ((prevState: T) => T)) => void]; ``` ### Arguments * `key` is the unique identifier of the state. This can be used to share the state across components and/or commands (hooks using the same key will share the same state, eg. updating one will update the others). With a few options: * `initialState` is the initial value of the state if there aren't any in the Cache yet. * `config.cacheNamespace` is a string that can be used to namespace the key. ## Example ```tsx import { List, ActionPanel, Action } from "@raycast/api"; import { useCachedState } from "@raycast/utils"; export default function Command() { const [showDetails, setShowDetails] = useCachedState("show-details", false); return ( setShowDetails((x) => !x)} /> } > ... ); } ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/useexec.md # useExec Hook that executes a command and returns the [AsyncState](#asyncstate) corresponding to the execution of the command. It follows the `stale-while-revalidate` cache invalidation strategy popularized by [HTTP RFC 5861](https://tools.ietf.org/html/rfc5861). `useExec` first returns the data from cache (stale), then executes the command (revalidate), and finally comes with the up-to-date data again. The last value will be kept between command runs. ## Signature There are two ways to use the hook. The first one should be preferred when executing a single file. The file and its arguments don't have to be escaped. ```ts function useExec( file: string, arguments: string[], options?: { shell?: boolean | string; stripFinalNewline?: boolean; cwd?: string; env?: NodeJS.ProcessEnv; encoding?: BufferEncoding | "buffer"; input?: string | Buffer; timeout?: number; parseOutput?: ParseExecOutputHandler; initialData?: U; keepPreviousData?: boolean; execute?: boolean; onError?: (error: Error) => void; onData?: (data: T) => void; onWillExecute?: (args: string[]) => void; failureToastOptions?: Partial>; } ): AsyncState & { revalidate: () => void; mutate: MutatePromise; }; ``` The second one can be used to execute more complex commands. The file and arguments are specified in a single `command` string. For example, `useExec('echo', ['Raycast'])` is the same as `useExec('echo Raycast')`. If the file or an argument contains spaces, they must be escaped with backslashes. This matters especially if `command` is not a constant but a variable, for example with `environment.supportPath` or `process.cwd()`. Except for spaces, no escaping/quoting is needed. The `shell` option must be used if the command uses shell-specific features (for example, `&&` or `||`), as opposed to being a simple file followed by its arguments. ```ts function useExec( command: string, options?: { shell?: boolean | string; stripFinalNewline?: boolean; cwd?: string; env?: NodeJS.ProcessEnv; encoding?: BufferEncoding | "buffer"; input?: string | Buffer; timeout?: number; parseOutput?: ParseExecOutputHandler; initialData?: U; keepPreviousData?: boolean; execute?: boolean; onError?: (error: Error) => void; onData?: (data: T) => void; onWillExecute?: (args: string[]) => void; failureToastOptions?: Partial>; } ): AsyncState & { revalidate: () => void; mutate: MutatePromise; }; ``` ### Arguments * `file` is the path to the file to execute. * `arguments` is an array of strings to pass as arguments to the file. or * `command` is the string to execute. With a few options: * `options.shell` is a boolean or a string to tell whether to run the command inside of a shell or not. If `true`, uses `/bin/sh`. A different shell can be specified as a string. The shell should understand the `-c` switch. We recommend against using this option since it is: * not cross-platform, encouraging shell-specific syntax. * slower, because of the additional shell interpretation. * unsafe, potentially allowing command injection. * `options.stripFinalNewline` is a boolean to tell the hook to strip the final newline character from the output. By default, it will. * `options.cwd` is a string to specify the current working directory of the child process. By default, it will be `process.cwd()`. * `options.env` is a key-value pairs to set as the environment of the child process. It will extend automatically from `process.env`. * `options.encoding` is a string to specify the character encoding used to decode the `stdout` and `stderr` output. If set to `"buffer"`, then `stdout` and `stderr` will be a `Buffer` instead of a string. * `options.input` is a string or a Buffer to write to the `stdin` of the file. * `options.timeout` is a number. If greater than `0`, the parent will send the signal `SIGTERM` if the child runs longer than timeout milliseconds. By default, the execution will timeout after 10000ms (eg. 10s). * `options.parseOutput` is a function that accepts the output of the child process as an argument and returns the data the hooks will return - see [ParseExecOutputHandler](#parseexecoutputhandler). By default, the hook will return `stdout`. Including the [useCachedPromise](https://developers.raycast.com/utilities/react-hooks/usecachedpromise)'s options: * `options.keepPreviousData` is a boolean to tell the hook to keep the previous results instead of returning the initial value if there aren't any in the cache for the new arguments. This is particularly useful when used for data for a List to avoid flickering. See [Argument dependent on user input](#argument-dependent-on-user-input) for more information. Including the [useCachedState](https://developers.raycast.com/utilities/react-hooks/usecachedstate)'s options: * `options.initialData` is the initial value of the state if there aren't any in the Cache yet. Including the [usePromise](https://developers.raycast.com/utilities/react-hooks/usepromise)'s options: * `options.execute` is a boolean to indicate whether to actually execute the function or not. This is useful for cases where one of the function's arguments depends on something that might not be available right away (for example, depends on some user inputs). Because React requires every hook to be defined on the render, this flag enables you to define the hook right away but wait until you have all the arguments ready to execute the function. * `options.onError` is a function called when an execution fails. By default, it will log the error and show a generic failure toast with an action to retry. * `options.onData` is a function called when an execution succeeds. * `options.onWillExecute` is a function called when an execution will start. * `options.failureToastOptions` are the options to customize the title, message, and primary action of the failure toast. ### Return Returns an object with the [AsyncState](#asyncstate) corresponding to the execution of the command as well as a couple of methods to manipulate it. * `data`, `error`, `isLoading` - see [AsyncState](#asyncstate). * `revalidate` is a method to manually call the function with the same arguments again. * `mutate` is a method to wrap an asynchronous update and gives some control over how the `useFetch`'s data should be updated while the update is going through. By default, the data will be revalidated (eg. the function will be called again) after the update is done. See [Mutation and Optimistic Updates](#mutation-and-optimistic-updates) for more information. ## Example ```tsx import { List } from "@raycast/api"; import { useExec } from "@raycast/utils"; import { cpus } from "os"; import { useMemo } from "react"; const brewPath = cpus()[0].model.includes("Apple") ? "/opt/homebrew/bin/brew" : "/usr/local/bin/brew"; export default function Command() { const { isLoading, data } = useExec(brewPath, ["info", "--json=v2", "--installed"]); const results = useMemo<{ id: string; name: string }[]>(() => JSON.parse(data || "{}").formulae || [], [data]); return ( {results.map((item) => ( ))} ); } ``` ## Argument dependent on user input By default, when an argument passed to the hook changes, the function will be executed again and the cache's value for those arguments will be returned immediately. This means that in the case of new arguments that haven't been used yet, the initial data will be returned. This behaviour can cause some flickering (initial data -> fetched data -> arguments change -> initial data -> fetched data, etc.). To avoid that, we can set `keepPreviousData` to `true` and the hook will keep the latest fetched data if the cache is empty for the new arguments (initial data -> fetched data -> arguments change -> fetched data). ```tsx import { useState } from "react"; import { Detail, ActionPanel, Action } from "@raycast/api"; import { useFetch } from "@raycast/utils"; export default function Command() { const [searchText, setSearchText] = useState(""); const { isLoading, data } = useExec("brew", ["info", searchText]); return ; } ``` {% hint style="info" %} When passing a user input to a command, be very careful about using the `shell` option as it could be potentially dangerous. {% endhint %} ## Mutation and Optimistic Updates In an optimistic update, the UI behaves as though a change was successfully completed before receiving confirmation from the server that it was - it is being optimistic that it will eventually get the confirmation rather than an error. This allows for a more responsive user experience. You can specify an `optimisticUpdate` function to mutate the data in order to reflect the change introduced by the asynchronous update. When doing so, you can specify a `rollbackOnError` function to mutate back the data if the asynchronous update fails. If not specified, the data will be automatically rolled back to its previous value (before the optimistic update). ```tsx import { Detail, ActionPanel, Action, showToast, Toast } from "@raycast/api"; import { useFetch } from "@raycast/utils"; export default function Command() { const { isLoading, data, revalidate } = useExec("brew", ["info", "--json=v2", "--installed"]); const results = useMemo<{}[]>(() => JSON.parse(data || "[]"), [data]); const installFoo = async () => { const toast = await showToast({ style: Toast.Style.Animated, title: "Installing Foo" }); try { await mutate( // we are calling an API to do something installBrewCask("foo"), { // but we are going to do it on our local data right away, // without waiting for the call to return optimisticUpdate(data) { return data?.concat({ name: "foo", id: "foo" }); }, }, ); // yay, the API call worked! toast.style = Toast.Style.Success; toast.title = "Foo installed"; } catch (err) { // oh, the API call didn't work :( // the data will automatically be rolled back to its previous value toast.style = Toast.Style.Failure; toast.title = "Could not install Foo"; toast.message = err.message; } }; return ( {(data || []).map((item) => ( installFoo()} /> } /> ))} ); } ``` ## Types ### AsyncState An object corresponding to the execution state of the function. ```ts // Initial State { isLoading: true, // or `false` if `options.execute` is `false` data: undefined, error: undefined } // Success State { isLoading: false, data: T, error: undefined } // Error State { isLoading: false, data: undefined, error: Error } // Reloading State { isLoading: true, data: T | undefined, error: Error | undefined } ``` ### MutatePromise A method to wrap an asynchronous update and gives some control about how the `useFetch`'s data should be updated while the update is going through. ```ts export type MutatePromise = ( asyncUpdate?: Promise, options?: { optimisticUpdate?: (data: T) => T; rollbackOnError?: boolean | ((data: T) => T); shouldRevalidateAfter?: boolean; }, ) => Promise; ``` ### ParseExecOutputHandler A function that accepts the output of the child process as an argument and returns the data the hooks will return. ```ts export type ParseExecOutputHandler = (args: { /** The output of the process on stdout. */ stdout: string | Buffer; // depends on the encoding option /** The output of the process on stderr. */ stderr: string | Buffer; // depends on the encoding option error?: Error | undefined; /** The numeric exit code of the process that was run. */ exitCode: number | null; /** The name of the signal that was used to terminate the process. For example, SIGFPE. */ signal: NodeJS.Signals | null; /** Whether the process timed out. */ timedOut: boolean; /** The command that was run, for logging purposes. */ command: string; /** The options passed to the child process, for logging purposes. */ options?: ExecOptions | undefined; }) => T; ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/usefetch.md # useFetch Hook which fetches the URL and returns the [AsyncState](#asyncstate) corresponding to the execution of the fetch. It follows the `stale-while-revalidate` cache invalidation strategy popularized by [HTTP RFC 5861](https://tools.ietf.org/html/rfc5861). `useFetch` first returns the data from cache (stale), then sends the request (revalidate), and finally comes with the up-to-date data again. The last value will be kept between command runs. ## Signature ```ts export function useFetch( url: RequestInfo, options?: RequestInit & { parseResponse?: (response: Response) => Promise; mapResult?: (result: V) => { data: T }; initialData?: U; keepPreviousData?: boolean; execute?: boolean; onError?: (error: Error) => void; onData?: (data: T) => void; onWillExecute?: (args: [string, RequestInit]) => void; failureToastOptions?: Partial>; }, ): AsyncState & { revalidate: () => void; mutate: MutatePromise; }; ``` ### Arguments * `url` is the string representation of the URL to fetch. With a few options: * `options` extends [`RequestInit`](https://github.com/nodejs/undici/blob/v5.7.0/types/fetch.d.ts#L103-L117) allowing you to specify a body, headers, etc. to apply to the request. * `options.parseResponse` is a function that accepts the Response as an argument and returns the data the hook will return. By default, the hook will return `response.json()` if the response has a JSON `Content-Type` header or `response.text()` otherwise. * `options.mapResult` is an optional function that accepts whatever `options.parseResponse` returns as an argument, processes the response, and returns an object wrapping the result, i.e. `(response) => { return { data: response> } };`. Including the [useCachedPromise](https://developers.raycast.com/utilities/react-hooks/usecachedpromise)'s options: * `options.keepPreviousData` is a boolean to tell the hook to keep the previous results instead of returning the initial value if there aren't any in the cache for the new arguments. This is particularly useful when used for data for a List to avoid flickering. See [Argument dependent on List search text](#argument-dependent-on-list-search-text) for more information. Including the [useCachedState](https://developers.raycast.com/utilities/react-hooks/usecachedstate)'s options: * `options.initialData` is the initial value of the state if there aren't any in the Cache yet. Including the [usePromise](https://developers.raycast.com/utilities/react-hooks/usepromise)'s options: * `options.execute` is a boolean to indicate whether to actually execute the function or not. This is useful for cases where one of the function's arguments depends on something that might not be available right away (for example, depends on some user inputs). Because React requires every hook to be defined on the render, this flag enables you to define the hook right away but wait until you have all the arguments ready to execute the function. * `options.onError` is a function called when an execution fails. By default, it will log the error and show a generic failure toast with an action to retry. * `options.onData` is a function called when an execution succeeds. * `options.onWillExecute` is a function called when an execution will start. * `options.failureToastOptions` are the options to customize the title, message, and primary action of the failure toast. ### Return Returns an object with the [AsyncState](#asyncstate) corresponding to the execution of the fetch as well as a couple of methods to manipulate it. * `data`, `error`, `isLoading` - see [AsyncState](#asyncstate). * `revalidate` is a method to manually call the function with the same arguments again. * `mutate` is a method to wrap an asynchronous update and gives some control over how the `useFetch`'s data should be updated while the update is going through. By default, the data will be revalidated (eg. the function will be called again) after the update is done. See [Mutation and Optimistic Updates](#mutation-and-optimistic-updates) for more information. ## Example ```tsx import { Detail, ActionPanel, Action } from "@raycast/api"; import { useFetch } from "@raycast/utils"; export default function Command() { const { isLoading, data, revalidate } = useFetch("https://api.example"); return ( revalidate()} /> } /> ); } ``` ## Argument dependent on List search text By default, when an argument passed to the hook changes, the function will be executed again and the cache's value for those arguments will be returned immediately. This means that in the case of new arguments that haven't been used yet, the initial data will be returned. This behaviour can cause some flickering (initial data -> fetched data -> arguments change -> initial data -> fetched data, etc.). To avoid that, we can set `keepPreviousData` to `true` and the hook will keep the latest fetched data if the cache is empty for the new arguments (initial data -> fetched data -> arguments change -> fetched data). ```tsx import { useState } from "react"; import { List, ActionPanel, Action } from "@raycast/api"; import { useFetch } from "@raycast/utils"; export default function Command() { const [searchText, setSearchText] = useState(""); const { isLoading, data } = useFetch(`https://api.example?q=${searchText}`, { // to make sure the screen isn't flickering when the searchText changes keepPreviousData: true, }); return ( {(data || []).map((item) => ( ))} ); } ``` ## Mutation and Optimistic Updates In an optimistic update, the UI behaves as though a change was successfully completed before receiving confirmation from the server that it was - it is being optimistic that it will eventually get the confirmation rather than an error. This allows for a more responsive user experience. You can specify an `optimisticUpdate` function to mutate the data in order to reflect the change introduced by the asynchronous update. When doing so, you can specify a `rollbackOnError` function to mutate back the data if the asynchronous update fails. If not specified, the data will be automatically rolled back to its previous value (before the optimistic update). ```tsx import { Detail, ActionPanel, Action, showToast, Toast } from "@raycast/api"; import { useFetch } from "@raycast/utils"; export default function Command() { const { isLoading, data, mutate } = useFetch("https://api.example"); const appendFoo = async () => { const toast = await showToast({ style: Toast.Style.Animated, title: "Appending Foo" }); try { await mutate( // we are calling an API to do something fetch("https://api.example/append-foo"), { // but we are going to do it on our local data right away, // without waiting for the call to return optimisticUpdate(data) { return data + "foo"; }, }, ); // yay, the API call worked! toast.style = Toast.Style.Success; toast.title = "Foo appended"; } catch (err) { // oh, the API call didn't work :( // the data will automatically be rolled back to its previous value toast.style = Toast.Style.Failure; toast.title = "Could not append Foo"; toast.message = err.message; } }; return ( appendFoo()} /> } /> ); } ``` ## Pagination {% hint style="info" %} When paginating, the hook will only cache the result of the first page. {% endhint %} The hook has built-in support for pagination. In order to enable pagination, `url`s type needs to change from `RequestInfo` to a function that receives a [PaginationOptions](#paginationoptions) argument, and returns a `RequestInfo`. In practice, this means going from ```ts const { isLoading, data } = useFetch( "https://api.ycombinator.com/v0.1/companies?" + new URLSearchParams({ q: searchText }).toString(), { mapResult(result: SearchResult) { return { data: result.companies, }; }, keepPreviousData: true, initialData: [], }, ); ``` to ```ts const { isLoading, data, pagination } = useFetch( (options) => "https://api.ycombinator.com/v0.1/companies?" + new URLSearchParams({ page: String(options.page + 1), q: searchText }).toString(), { mapResult(result: SearchResult) { return { data: result.companies, hasMore: result.page < result.totalPages, }; }, keepPreviousData: true, initialData: [], }, ); ``` or, if your data source uses cursor-based pagination, you can return a `cursor` alongside `data` and `hasMore`, and the cursor will be passed as an argument the next time the function gets called: ```ts const { isLoading, data, pagination } = useFetch( (options) => "https://api.ycombinator.com/v0.1/companies?" + new URLSearchParams({ cursor: options.cursor, q: searchText }).toString(), { mapResult(result: SearchResult) { const { companies, nextCursor } = result; const hasMore = nextCursor !== undefined; return { data: companies, hasMore, cursor: nextCursor, }; }, keepPreviousData: true, initialData: [], }, ); ``` You'll notice that, in the second case, the hook returns an additional item: `pagination`. This can be passed to Raycast's `List` or `Grid` components in order to enable pagination. Another thing to notice is that `mapResult`, which is normally optional, is actually required when using pagination. Furthermore, its return type is ```ts { data: any[], hasMore?: boolean; cursor?: any; } ``` Every time the URL is fetched, the hook needs to figure out if it should paginate further, or if it should stop, and it uses the `hasMore` for this. In addition to this, the hook also needs `data`, and needs it to be an array, because internally it appends it to a list, thus making sure the `data` that the hook *returns* always contains the data for all of the pages that have been fetched so far. ### Full Example ```tsx import { Icon, Image, List } from "@raycast/api"; import { useFetch } from "@raycast/utils"; import { useState } from "react"; type SearchResult = { companies: Company[]; page: number; totalPages: number }; type Company = { id: number; name: string; smallLogoUrl?: string }; export default function Command() { const [searchText, setSearchText] = useState(""); const { isLoading, data, pagination } = useFetch( (options) => "https://api.ycombinator.com/v0.1/companies?" + new URLSearchParams({ page: String(options.page + 1), q: searchText }).toString(), { mapResult(result: SearchResult) { return { data: result.companies, hasMore: result.page < result.totalPages, }; }, keepPreviousData: true, initialData: [], }, ); return ( {data.map((company) => ( ))} ); } ``` ## Types ### AsyncState An object corresponding to the execution state of the function. ```ts // Initial State { isLoading: true, // or `false` if `options.execute` is `false` data: undefined, error: undefined } // Success State { isLoading: false, data: T, error: undefined } // Error State { isLoading: false, data: undefined, error: Error } // Reloading State { isLoading: true, data: T | undefined, error: Error | undefined } ``` ### MutatePromise A method to wrap an asynchronous update and gives some control about how the `useFetch`'s data should be updated while the update is going through. ```ts export type MutatePromise = ( asyncUpdate?: Promise, options?: { optimisticUpdate?: (data: T) => T; rollbackOnError?: boolean | ((data: T) => T); shouldRevalidateAfter?: boolean; }, ) => Promise; ``` ### PaginationOptions An object passed to a `PaginatedRequestInfo`, it has two properties: * `page`: 0-indexed, this it's incremented every time the promise resolves, and is reset whenever `revalidate()` is called. * `lastItem`: this is a copy of the last item in the `data` array from the last time the promise was executed. Provided for APIs that implement cursor-based pagination. * `cursor`: this is the `cursor` property returned after the previous execution of `PaginatedPromise`. Useful when working with APIs that provide the next cursor explicitly. ```ts export type PaginationOptions = { page: number; lastItem?: T; cursor?: any; }; ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/useform.md # useForm Hook that provides a high-level interface to work with Forms, and more particularly, with Form validations. It incorporates all the good practices to provide a great User Experience for your Forms. ## Signature ```ts function useForm(props: { onSubmit: (values: T) => void | boolean | Promise; initialValues?: Partial; validation?: { [id in keyof T]?: ((value: T[id]) => string | undefined | null) | FormValidation; }; }): { handleSubmit: (values: T) => void | boolean | Promise; itemProps: { [id in keyof T]: Partial> & { id: string; }; }; setValidationError: (id: keyof T, error: ValidationError) => void; setValue: (id: K, value: T[K]) => void; values: T; focus: (id: keyof T) => void; reset: (initialValues?: Partial) => void; }; ``` ### Arguments * `onSubmit` is a callback that will be called when the form is submitted and all validations pass. With a few options: * `initialValues` are the initial values to set when the Form is first rendered. * `validation` are the validation rules for the Form. A validation for a Form item is a function that takes the current value of the item as an argument and must return a string when the validation is failing. We also provide some shorthands for common cases, see [FormValidation](#formvalidation). ### Return Returns an object which contains the necessary methods and props to provide a good User Experience in your Form. * `handleSubmit` is a function to pass to the `onSubmit` prop of the `` element. It wraps the initial `onSubmit` argument with some goodies related to the validation. * `itemProps` are the props that must be passed to the `` elements to handle the validations. It also contains some additions for easy manipulation of the Form's data. * `values` is the current values of the Form. * `setValue` is a function that can be used to programmatically set the value of a specific field. * `setValidationError` is a function that can be used to programmatically set the validation of a specific field. * `focus` is a function that can be used to programmatically focus a specific field. * `reset` is a function that can be used to reset the values of the Form. Optionally, you can specify the values to set when the Form is reset. ## Example ```tsx import { Action, ActionPanel, Form, showToast, Toast } from "@raycast/api"; import { useForm, FormValidation } from "@raycast/utils"; interface SignUpFormValues { firstName: string; lastName: string; birthday: Date | null; password: string; number: string; hobbies: string[]; } export default function Command() { const { handleSubmit, itemProps } = useForm({ onSubmit(values) { showToast({ style: Toast.Style.Success, title: "Yay!", message: `${values.firstName} ${values.lastName} account created`, }); }, validation: { firstName: FormValidation.Required, lastName: FormValidation.Required, password: (value) => { if (value && value.length < 8) { return "Password must be at least 8 symbols"; } else if (!value) { return "The item is required"; } }, number: (value) => { if (value && value !== "2") { return "Please select '2'"; } }, }, }); return (
} > {[1, 2, 3, 4, 5, 6, 7].map((num) => { return ; })} ); } ``` ## Types ### FormValidation Shorthands for common validation cases #### Enumeration members | Name | Description | | -------- | ------------------------------------------------- | | Required | Show an error when the value of the item is empty | --- # Source: https://developers.raycast.com/utilities/react-hooks/usefrecencysorting.md # useFrecencySorting Hook to sort an array by its frecency and provide methods to update the frecency of its items. Frecency is a measure that combines frequency and recency. The more often an item is visited, and the more recently an item is visited, the higher it will rank. ## Signature ```ts function useFrecencySorting( data?: T[], options?: { namespace?: string; key?: (item: T) => string; sortUnvisited?: (a: T, b: T) => number; }, ): { data: T[]; visitItem: (item: T) => Promise; resetRanking: (item: T) => Promise; }; ``` ### Arguments * `data` is the array to sort With a few options: * `options.namespace` is a string that can be used to namespace the frecency data (if you have multiple arrays that you want to sort in the same extension). * `options.key` is a function that should return a unique string for each item of the array to sort. By default, it will use `item.id`. If the items do not have an `id` field, this option is required. * `options.sortUnvisited` is a function to sort the items that have never been visited. By default, the order of the input will be preserved. ### Return Returns an object with the sorted array and some methods to update the frecency of the items. * `data` is the sorted array. The order will be preserved for items that have never been visited * `visitItem` is a method to use when an item is visited/used. It will increase its frecency. * `resetRanking` is a method that can be used to reset the frecency of an item. ## Example ```tsx import { List, ActionPanel, Action, Icon } from "@raycast/api"; import { useFetch, useFrecencySorting } from "@raycast/utils"; export default function Command() { const { isLoading, data } = useFetch("https://api.example"); const { data: sortedData, visitItem, resetRanking } = useFrecencySorting(data); return ( {sortedData.map((item) => ( visitItem(item)} /> visitItem(item)} /> resetRanking(item)} /> } /> ))} ); } ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/uselocalstorage.md # useLocalStorage A hook to manage a value in the local storage. ## Signature ```ts function useLocalStorage(key: string, initialValue?: T): { value: T | undefined; setValue: (value: T) => Promise; removeValue: () => Promise; isLoading: boolean; } ``` ### Arguments * `key` - The key to use for the value in the local storage. * `initialValue` - The initial value to use if the key doesn't exist in the local storage. ### Return Returns an object with the following properties: * `value` - The value from the local storage or the initial value if the key doesn't exist. * `setValue` - A function to update the value in the local storage. * `removeValue` - A function to remove the value from the local storage. * `isLoading` - A boolean indicating if the value is loading. ## Example ```tsx import { Action, ActionPanel, Color, Icon, List } from "@raycast/api"; import { useLocalStorage } from "@raycast/utils"; const exampleTodos = [ { id: "1", title: "Buy milk", done: false }, { id: "2", title: "Walk the dog", done: false }, { id: "3", title: "Call mom", done: false }, ]; export default function Command() { const { value: todos, setValue: setTodos, isLoading } = useLocalStorage("todos", exampleTodos); async function toggleTodo(id: string) { const newTodos = todos?.map((todo) => (todo.id === id ? { ...todo, done: !todo.done } : todo)) ?? []; await setTodos(newTodos); } return ( {todos?.map((todo) => ( toggleTodo(todo.id)} /> toggleTodo(todo.id)} /> } /> ))} ); } ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/usepromise.md # usePromise Hook which wraps an asynchronous function or a function that returns a Promise and returns the [AsyncState](#asyncstate) corresponding to the execution of the function. {% hint style="info" %} The function is assumed to be constant (eg. changing it won't trigger a revalidation). {% endhint %} ## Signature ```ts type Result = `type of the returned value of the returned Promise`; function usePromise( fn: T, args?: Parameters, options?: { abortable?: RefObject; execute?: boolean; onError?: (error: Error) => void; onData?: (data: Result) => void; onWillExecute?: (args: Parameters) => void; failureToastOptions?: Partial>; }, ): AsyncState> & { revalidate: () => void; mutate: MutatePromise | undefined>; }; ``` ### Arguments * `fn` is an asynchronous function or a function that returns a Promise. * `args` is the array of arguments to pass to the function. Every time they change, the function will be executed again. You can omit the array if the function doesn't require any argument. With a few options: * `options.abortable` is a reference to an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel a previous call when triggering a new one. * `options.execute` is a boolean to indicate whether to actually execute the function or not. This is useful for cases where one of the function's arguments depends on something that might not be available right away (for example, depends on some user inputs). Because React requires every hook to be defined on the render, this flag enables you to define the hook right away but wait until you have all the arguments ready to execute the function. * `options.onError` is a function called when an execution fails. By default, it will log the error and show a generic failure toast with an action to retry. * `options.onData` is a function called when an execution succeeds. * `options.onWillExecute` is a function called when an execution will start. * `options.failureToastOptions` are the options to customize the title, message, and primary action of the failure toast. ### Returns Returns an object with the [AsyncState](#asyncstate) corresponding to the execution of the function as well as a couple of methods to manipulate it. * `data`, `error`, `isLoading` - see [AsyncState](#asyncstate). * `revalidate` is a method to manually call the function with the same arguments again. * `mutate` is a method to wrap an asynchronous update and gives some control about how the `usePromise`'s data should be updated while the update is going through. By default, the data will be revalidated (eg. the function will be called again) after the update is done. See [Mutation and Optimistic Updates](#mutation-and-optimistic-updates) for more information. ## Example ```tsx import { Detail, ActionPanel, Action } from "@raycast/api"; import { usePromise } from "@raycast/utils"; export default function Command() { const abortable = useRef(); const { isLoading, data, revalidate } = usePromise( async (url: string) => { const response = await fetch(url, { signal: abortable.current?.signal }); const result = await response.text(); return result; }, ["https://api.example"], { abortable, }, ); return ( revalidate()} /> } /> ); } ``` ## Mutation and Optimistic Updates In an optimistic update, the UI behaves as though a change was successfully completed before receiving confirmation from the server that it was - it is being optimistic that it will eventually get the confirmation rather than an error. This allows for a more responsive user experience. You can specify an `optimisticUpdate` function to mutate the data in order to reflect the change introduced by the asynchronous update. When doing so, you can specify a `rollbackOnError` function to mutate back the data if the asynchronous update fails. If not specified, the data will be automatically rolled back to its previous value (before the optimistic update). ```tsx import { Detail, ActionPanel, Action, showToast, Toast } from "@raycast/api"; import { usePromise } from "@raycast/utils"; export default function Command() { const { isLoading, data, mutate } = usePromise( async (url: string) => { const response = await fetch(url); const result = await response.text(); return result; }, ["https://api.example"], ); const appendFoo = async () => { const toast = await showToast({ style: Toast.Style.Animated, title: "Appending Foo" }); try { await mutate( // we are calling an API to do something fetch("https://api.example/append-foo"), { // but we are going to do it on our local data right away, // without waiting for the call to return optimisticUpdate(data) { return data + "foo"; }, }, ); // yay, the API call worked! toast.style = Toast.Style.Success; toast.title = "Foo appended"; } catch (err) { // oh, the API call didn't work :( // the data will automatically be rolled back to its previous value toast.style = Toast.Style.Failure; toast.title = "Could not append Foo"; toast.message = err.message; } }; return ( appendFoo()} /> } /> ); } ``` ## Pagination The hook has built-in support for pagination. In order to enable pagination, `fn`'s type needs to change from > an asynchronous function or a function that returns a Promise to > a function that returns an asynchronous function or a function that returns a Promise In practice, this means going from ```ts const { isLoading, data } = usePromise( async (searchText: string) => { const data = await getUser(); // or any asynchronous logic you need to perform return data; }, [searchText], ); ``` to ```ts const { isLoading, data, pagination } = usePromise( (searchText: string) => async ({ page, lastItem, cursor }) => { const { data } = await getUsers(page); // or any other asynchronous logic you need to perform const hasMore = page < 50; return { data, hasMore }; }, [searchText], ); ``` or, if your data source uses cursor-based pagination, you can return a `cursor` alongside `data` and `hasMore`, and the cursor will be passed as an argument the next time the function gets called: ```ts const { isLoading, data, pagination } = usePromise( (searchText: string) => async ({ page, lastItem, cursor }) => { const { data, nextCursor } = await getUsers(cursor); // or any other asynchronous logic you need to perform const hasMore = nextCursor !== undefined; return { data, hasMore, cursor: nextCursor }; }, [searchText], ); ``` You'll notice that, in the second case, the hook returns an additional item: `pagination`. This can be passed to Raycast's `List` or `Grid` components in order to enable pagination.\ Another thing to notice is that the async function receives a [PaginationOptions](#paginationoptions) argument, and returns a specific data format: ```ts { data: any[]; hasMore: boolean; cursor?: any; } ``` Every time the promise resolves, the hook needs to figure out if it should paginate further, or if it should stop, and it uses `hasMore` for this.\ In addition to this, the hook also needs `data`, and needs it to be an array, because internally it appends it to a list, thus making sure the `data` that the hook *returns* always contains the data for all of the pages that have been loaded so far.\ Additionally, you can also pass a `cursor` property, which will be included along with `page` and `lastItem` in the next pagination call. ### Full Example ```tsx import { setTimeout } from "node:timers/promises"; import { useState } from "react"; import { List } from "@raycast/api"; import { usePromise } from "@raycast/utils"; export default function Command() { const [searchText, setSearchText] = useState(""); const { isLoading, data, pagination } = usePromise( (searchText: string) => async (options: { page: number }) => { await setTimeout(200); const newData = Array.from({ length: 25 }, (_v, index) => ({ index, page: options.page, text: searchText, })); return { data: newData, hasMore: options.page < 10 }; }, [searchText], ); return ( {data?.map((item) => ( ))} ); } ``` ## Types ### AsyncState An object corresponding to the execution state of the function. ```ts // Initial State { isLoading: true, // or `false` if `options.execute` is `false` data: undefined, error: undefined } // Success State { isLoading: false, data: T, error: undefined } // Error State { isLoading: false, data: undefined, error: Error } // Reloading State { isLoading: true, data: T | undefined, error: Error | undefined } ``` ### MutatePromise A method to wrap an asynchronous update and gives some control about how the `usePromise`'s data should be updated while the update is going through. ```ts export type MutatePromise = ( asyncUpdate?: Promise, options?: { optimisticUpdate?: (data: T) => T; rollbackOnError?: boolean | ((data: T) => T); shouldRevalidateAfter?: boolean; }, ) => Promise; ``` ### PaginationOptions An object passed to a `PaginatedPromise`, it has two properties: * `page`: 0-indexed, this it's incremented every time the promise resolves, and is reset whenever `revalidate()` is called. * `lastItem`: this is a copy of the last item in the `data` array from the last time the promise was executed. Provided for APIs that implement cursor-based pagination. * `cursor`: this is the `cursor` property returned after the previous execution of `PaginatedPromise`. Useful when working with APIs that provide the next cursor explicitly. ```ts export type PaginationOptions = { page: number; lastItem?: T; cursor?: any; }; ``` --- # Source: https://developers.raycast.com/api-reference/user-interface.md # User Interface Raycast uses React for its user interface declaration and renders the supported elements to our native UI. The API comes with a set of UI components that you can use to build your extensions. Think of it as a design system. The high-level components are the following: * [List](https://developers.raycast.com/api-reference/user-interface/list) to show multiple similar items, f.e. a list of your open todos. * [Grid](https://developers.raycast.com/api-reference/user-interface/grid) similar to a List but with more legroom to show an image for each item, f.e. a collection of icons. * [Detail](https://developers.raycast.com/api-reference/user-interface/detail) to present more information, f.e. the details of a GitHub pull request. * [Form](https://developers.raycast.com/api-reference/user-interface/form) to create new content, f.e. filing a bug report. Each component can provide interaction via an [ActionPanel](https://developers.raycast.com/api-reference/user-interface/action-panel). The panel has a list of [Actions](https://developers.raycast.com/api-reference/user-interface/actions) where each one can be associated with a [keyboard shortcut](https://developers.raycast.com/api-reference/keyboard). Shortcuts allow users to use Raycast without using their mouse. ## Rendering To render a user interface, you need to do the following: * Set the `mode` to `view` in the [`package.json` manifest file](https://developers.raycast.com/information/manifest#command-properties) * Export a React component from your command entry file As a general rule of thumb, you should render something as quickly as possible. This guarantees that your command feels responsive. If you don't have data available to show, you can set the `isLoading` prop to `true` on top-level components such as [``](https://developers.raycast.com/api-reference/user-interface/detail), [`
`](https://developers.raycast.com/api-reference/user-interface/form), or [``](https://developers.raycast.com/api-reference/user-interface/list). It shows a loading indicator at the top of Raycast. Here is an example that shows a loading indicator for 2 seconds after the command got launched: ```typescript import { List } from "@raycast/api"; import { useEffect, useState } from "react"; export default function Command() { const [isLoading, setIsLoading] = useState(true); useEffect(() => { setTimeout(() => setIsLoading(false), 2000); }, []); return {/* Render your data */}; } ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/usesql.md # useSQL Hook which executes a query on a local SQL database and returns the [AsyncState](#asyncstate) corresponding to the execution of the query. ## Signature ```ts function useSQL( databasePath: string, query: string, options?: { permissionPriming?: string; execute?: boolean; onError?: (error: Error) => void; onData?: (data: T) => void; onWillExecute?: (args: string[]) => void; failureToastOptions?: Partial>; } ): AsyncState & { revalidate: () => void; mutate: MutatePromise; permissionView: React.ReactNode | undefined; }; ``` ### Arguments * `databasePath` is the path to the local SQL database. * `query` is the SQL query to run on the database. With a few options: * `options.permissionPriming` is a string explaining why the extension needs full disk access. For example, the Apple Notes extension uses `"This is required to search your Apple Notes."`. While it is optional, we recommend setting it to help users understand. Including the [usePromise](https://developers.raycast.com/utilities/react-hooks/usepromise)'s options: * `options.execute` is a boolean to indicate whether to actually execute the function or not. This is useful for cases where one of the function's arguments depends on something that might not be available right away (for example, depends on some user inputs). Because React requires every hook to be defined on the render, this flag enables you to define the hook right away but wait until you have all the arguments ready to execute the function. * `options.onError` is a function called when an execution fails. By default, it will log the error and show a generic failure toast with an action to retry. * `options.onData` is a function called when an execution succeeds. * `options.onWillExecute` is a function called when an execution will start. * `options.failureToastOptions` are the options to customize the title, message, and primary action of the failure toast. ### Return Returns an object with the [AsyncState](#asyncstate) corresponding to the execution of the function as well as a couple of methods to manipulate it. * `data`, `error`, `isLoading` - see [AsyncState](#asyncstate). * `permissionView` is a React Node that should be returned when present. It will prompt users to grant full disk access (which is required for the hook to work). * `revalidate` is a method to manually call the function with the same arguments again. * `mutate` is a method to wrap an asynchronous update and gives some control over how the `useSQL`'s data should be updated while the update is going through. By default, the data will be revalidated (eg. the function will be called again) after the update is done. See [Mutation and Optimistic Updates](#mutation-and-optimistic-updates) for more information. ## Example ```tsx import { useSQL } from "@raycast/utils"; import { resolve } from "path"; import { homedir } from "os"; const NOTES_DB = resolve(homedir(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"); const notesQuery = `SELECT id, title FROM ...`; type NoteItem = { id: string; title: string; }; export default function Command() { const { isLoading, data, permissionView } = useSQL(NOTES_DB, notesQuery); if (permissionView) { return permissionView; } return ( {(data || []).map((item) => ( ))} ); } ``` ## Mutation and Optimistic Updates In an optimistic update, the UI behaves as though a change was successfully completed before receiving confirmation from the server that it was - it is being optimistic that it will eventually get the confirmation rather than an error. This allows for a more responsive user experience. You can specify an `optimisticUpdate` function to mutate the data in order to reflect the change introduced by the asynchronous update. When doing so, you can specify a `rollbackOnError` function to mutate back the data if the asynchronous update fails. If not specified, the data will be automatically rolled back to its previous value (before the optimistic update). ```tsx import { Detail, ActionPanel, Action, showToast, Toast } from "@raycast/api"; import { useSQL } from "@raycast/utils"; const NOTES_DB = resolve(homedir(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"); const notesQuery = `SELECT id, title FROM ...`; type NoteItem = { id: string; title: string; }; export default function Command() { const { isLoading, data, mutate, permissionView } = useFetch("https://api.example"); if (permissionView) { return permissionView; } const createNewNote = async () => { const toast = await showToast({ style: Toast.Style.Animated, title: "Creating new Note" }); try { await mutate( // we are calling an API to do something somehowCreateANewNote(), { // but we are going to do it on our local data right away, // without waiting for the call to return optimisticUpdate(data) { return data?.concat([{ id: "" + Math.random(), title: "New Title" }]); }, }, ); // yay, the API call worked! toast.style = Toast.Style.Success; toast.title = "Note created"; } catch (err) { // oh, the API call didn't work :( // the data will automatically be rolled back to its previous value toast.style = Toast.Style.Failure; toast.title = "Could not create Note"; toast.message = err.message; } }; return ( {(data || []).map((item) => ( createNewNote()} /> } /> ))} ); } ``` ## Types ### AsyncState An object corresponding to the execution state of the function. ```ts // Initial State { isLoading: true, // or `false` if `options.execute` is `false` data: undefined, error: undefined } // Success State { isLoading: false, data: T, error: undefined } // Error State { isLoading: false, data: undefined, error: Error } // Reloading State { isLoading: true, data: T | undefined, error: Error | undefined } ``` ### MutatePromise A method to wrap an asynchronous update and gives some control about how the `useSQL`'s data should be updated while the update is going through. ```ts export type MutatePromise = ( asyncUpdate?: Promise, options?: { optimisticUpdate?: (data: T) => T; rollbackOnError?: boolean | ((data: T) => T); shouldRevalidateAfter?: boolean; }, ) => Promise; ``` --- # Source: https://developers.raycast.com/utilities/react-hooks/usestreamjson.md # useStreamJSON Hook which takes a `http://`, `https://` or `file:///` URL pointing to a JSON resource, caches it to the command's support folder, and streams through its content. Useful when dealing with large JSON arrays which would be too big to fit in the command's memory. ## Signature ```ts export function useStreamJSON( url: RequestInfo, options: RequestInit & { filter?: (item: T) => boolean; transform?: (item: any) => T; pageSize?: number; initialData?: U; keepPreviousData?: boolean; execute?: boolean; onError?: (error: Error) => void; onData?: (data: T) => void; onWillExecute?: (args: [string, RequestInit]) => void; failureToastOptions?: Partial>; }, ): AsyncState> & { revalidate: () => void; }; ``` ### Arguments * `url` - The [`RequestInfo`](https://github.com/nodejs/undici/blob/v5.7.0/types/fetch.d.ts#L12) describing the resource that needs to be fetched. Strings starting with `http://`, `https://` and `Request` objects will use `fetch`, while strings starting with `file:///` will be copied to the cache folder. With a few options: * `options` extends [`RequestInit`](https://github.com/nodejs/undici/blob/v5.7.0/types/fetch.d.ts#L103-L117) allowing you to specify a body, headers, etc. to apply to the request. * `options.pageSize` the amount of items to fetch at a time. By default, 20 will be used * `options.dataPath` is a string or regular expression informing the hook that the array (or arrays) of data you want to stream through is wrapped inside one or multiple objects, and it indicates the path it needs to take to get to it. * `options.transform` is a function called with each top-level object encountered while streaming. If the function returns an array, the hook will end up streaming through its children, and each array item will be passed to `options.filter`. If the function returns something other than an array, *it* will be passed to `options.filter`. Note that the hook will revalidate every time the filter function changes, so you need to use [useCallback](https://react.dev/reference/react/useCallback) to make sure it only changes when it needs to. * `options.filter` is a function called with each object encountered while streaming. If it returns `true`, the object will be kept, otherwise it will be discarded. Note that the hook will revalidate every time the filter function changes, so you need to use [useCallback](https://react.dev/reference/react/useCallback) to make sure it only changes when it needs to. Including the [useCachedPromise](https://developers.raycast.com/utilities/react-hooks/usecachedpromise)'s options: * `options.keepPreviousData` is a boolean to tell the hook to keep the previous results instead of returning the initial value if there aren't any in the cache for the new arguments. This is particularly useful when used for data for a List to avoid flickering. Including the [useCachedState](https://developers.raycast.com/utilities/react-hooks/usecachedstate)'s options: * `options.initialData` is the initial value of the state if there aren't any in the Cache yet. Including the [usePromise](https://developers.raycast.com/utilities/react-hooks/usepromise)'s options: * `options.execute` is a boolean to indicate whether to actually execute the function or not. This is useful for cases where one of the function's arguments depends on something that might not be available right away (for example, depends on some user inputs). Because React requires every hook to be defined on the render, this flag enables you to define the hook right away but wait until you have all the arguments ready to execute the function. * `options.onError` is a function called when an execution fails. By default, it will log the error and show a generic failure toast with an action to retry. * `options.onData` is a function called when an execution succeeds. * `options.onWillExecute` is a function called when an execution will start. * `options.failureToastOptions` are the options to customize the title, message, and primary action of the failure toast. ### Return Returns an object with the [AsyncState](#asyncstate) corresponding to the execution of the fetch as well as a couple of methods to manipulate it. * `data`, `error`, `isLoading` - see [AsyncState](#asyncstate). * `pagination` - the pagination object that Raycast [`List`s](https://developers.raycast.com/api-reference/user-interface/list#props) and [`Grid`s](https://developers.raycast.com/api-reference/user-interface/grid#props) expect. * `revalidate` is a method to manually call the function with the same arguments again. * `mutate` is a method to wrap an asynchronous update and gives some control over how the hook's data should be updated while the update is going through. By default, the data will be revalidated (eg. the function will be called again) after the update is done. See [Mutation and Optimistic Updates](#mutation-and-optimistic-updates) for more information. ## Example ```ts import { Action, ActionPanel, List, environment } from "@raycast/api"; import { useStreamJSON } from "@raycast/utils"; import { join } from "path"; import { useCallback, useState } from "react"; type Formula = { name: string; desc?: string }; export default function Main(): React.JSX.Element { const [searchText, setSearchText] = useState(""); const formulaFilter = useCallback( (item: Formula) => { if (!searchText) return true; return item.name.toLocaleLowerCase().includes(searchText); }, [searchText], ); const formulaTransform = useCallback((item: any): Formula => { return { name: item.name, desc: item.desc }; }, []); const { data, isLoading, pagination } = useStreamJSON("https://formulae.brew.sh/api/formula.json", { initialData: [] as Formula[], pageSize: 20, filter: formulaFilter, transform: formulaTransform }); return ( {data.map((d) => ( ))} ); } ``` ## Mutation and Optimistic Updates In an optimistic update, the UI behaves as though a change was successfully completed before receiving confirmation from the server that it was - it is being optimistic that it will eventually get the confirmation rather than an error. This allows for a more responsive user experience. You can specify an `optimisticUpdate` function to mutate the data in order to reflect the change introduced by the asynchronous update. When doing so, you can specify a `rollbackOnError` function to mutate back the data if the asynchronous update fails. If not specified, the data will be automatically rolled back to its previous value (before the optimistic update). ```tsx import { Action, ActionPanel, List, environment } from "@raycast/api"; import { useStreamJSON } from "@raycast/utils"; import { join } from "path"; import { useCallback, useState } from "react"; import { setTimeout } from "timers/promises"; type Formula = { name: string; desc?: string }; export default function Main(): React.JSX.Element { const [searchText, setSearchText] = useState(""); const formulaFilter = useCallback( (item: Formula) => { if (!searchText) return true; return item.name.toLocaleLowerCase().includes(searchText); }, [searchText], ); const formulaTransform = useCallback((item: any): Formula => { return { name: item.name, desc: item.desc }; }, []); const { data, isLoading, mutate, pagination } = useStreamJSON("https://formulae.brew.sh/api/formula.json", { initialData: [] as Formula[], pageSize: 20, filter: formulaFilter, transform: formulaTransform, }); return ( {data.map((d) => ( { mutate(setTimeout(1000), { optimisticUpdate: () => { return [d]; }, }); }} /> } /> ))} ); } ``` ## Types ### AsyncState An object corresponding to the execution state of the function. ```ts // Initial State { isLoading: true, // or `false` if `options.execute` is `false` data: undefined, error: undefined } // Success State { isLoading: false, data: T, error: undefined } // Error State { isLoading: false, data: undefined, error: Error } // Reloading State { isLoading: true, data: T | undefined, error: Error | undefined } ``` --- # Source: https://developers.raycast.com/api-reference/utilities.md # System Utilities This set of utilities exposes some of Raycast's native functionality to allow deep integration into the user's setup. For example, you can use the Application APIs to check if a desktop application is installed and then provide an action to deep-link into it. ## API Reference ### getApplications Returns all applications that can open the file or URL. #### Signature ```typescript async function getApplications(path?: PathLike): Promise; ``` #### Example {% tabs %} {% tab title="Find Application" %} ```typescript import { getApplications, Application } from "@raycast/api"; // it is a lot more reliable to get an app by its bundle ID than its path async function findApplication(bundleId: string): Application | undefined { const installedApplications = await getApplications(); return installedApplications.filter((application) => application.bundleId == bundleId); } ``` {% endtab %} {% tab title="List Installed Applications" %} ```typescript import { getApplications } from "@raycast/api"; export default async function Command() { const installedApplications = await getApplications(); console.log("The following applications are installed on your Mac:"); console.log(installedApplications.map((a) => a.name).join(", ")); } ``` {% endtab %} {% endtabs %} #### Parameters | Name | Description | Type | | ---- | ----------------------------------------------------------------------------------------------------------------------------- | --------------- | | path | The path of the file or folder to get the applications for. If no path is specified, all installed applications are returned. | `"fs".PathLike` | #### Return An array of [Application](#application). ### getDefaultApplication Returns the default application that the file or URL would be opened with. #### Signature ```typescript async function getDefaultApplication(path: PathLike): Promise; ``` #### Example ```typescript import { getDefaultApplication } from "@raycast/api"; export default async function Command() { const defaultApplication = await getDefaultApplication(__filename); console.log(`Default application for JavaScript is: ${defaultApplication.name}`); } ``` #### Parameters | Name | Description | Type | | -------------------------------------- | ------------------------------------------------------------------ | --------------- | | path\* | The path of the file or folder to get the default application for. | `"fs".PathLike` | #### Return A Promise that resolves with the default [Application](#application) that would open the file or URL. If no application was found, the promise will be rejected. ### getFrontmostApplication Returns the frontmost application. #### Signature ```typescript async function getFrontmostApplication(): Promise; ``` #### Example ```typescript import { getFrontmostApplication } from "@raycast/api"; export default async function Command() { const frontmostApplication = await getFrontmostApplication(); console.log(`The frontmost application is: ${frontmostApplication.name}`); } ``` #### Return A Promise that resolves with the frontmost [Application](#application). If no application was found, the promise will be rejected. ### showInFinder Shows a file or directory in the Finder. #### Signature ```typescript async function showInFinder(path: PathLike): Promise; ``` #### Example ```typescript import { showInFinder } from "@raycast/api"; import { homedir } from "os"; import { join } from "path"; export default async function Command() { await showInFinder(join(homedir(), "Downloads")); } ``` #### Parameters | Name | Description | Type | | -------------------------------------- | ------------------------------- | --------------- | | path\* | The path to show in the Finder. | `"fs".PathLike` | #### Return A Promise that resolves when the item is revealed in the Finder. ### trash Moves a file or directory to the Trash. #### Signature ```typescript async function trash(path: PathLike | PathLike[]): Promise; ``` #### Example ```typescript import { trash } from "@raycast/api"; import { writeFile } from "fs/promises"; import { homedir } from "os"; import { join } from "path"; export default async function Command() { const file = join(homedir(), "Desktop", "yolo.txt"); await writeFile(file, "I will be deleted soon!"); await trash(file); } ``` #### Parameters | Name | Description | Type | | -------------------------------------- | --------------------------------------- | ------------------------------------ | | path\* | The item or items to move to the trash. | `"fs".PathLike` or `"fs".PathLike[]` | #### Return A Promise that resolves when all files are moved to the trash. ### open Opens a target with the default application or specified application. #### Signature ```typescript async function open(target: string, application?: Application | string): Promise; ``` #### Example ```typescript import { open } from "@raycast/api"; export default async function Command() { await open("https://www.raycast.com", "com.google.Chrome"); } ``` #### Parameters | Name | Description | Type | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- | | target\* | The file, folder or URL to open. | `string` | | application | The application name to use for opening the file. If no application is specified, the default application as determined by the system is used to open the specified file. Note that you can use the application name, app identifier, or absolute path to the app. | `string` or [`Application`](#application) | #### Return A Promise that resolves when the target has been opened. ### captureException Report the provided exception to the Developer Hub. This helps in handling failures gracefully while staying informed about the occurrence of the failure. #### Signature ```typescript function captureException(exception: unknown): void; ``` #### Example ```typescript import { open, captureException, showToast, Toast } from "@raycast/api"; export default async function Command() { const url = "https://www.raycast.com"; const app = "Google Chrome"; try { await open(url, app); } catch (e: unknown) { captureException(e); await showToast({ style: Toast.Style.Failure, title: `Could not open ${url} in ${app}.`, }); } } ``` #### Parameters | Name | Description | Type | | ------------------------------------------- | --------------------------------- | --------- | | exception\* | The exception you want to report. | `unknown` | ## Types ### Application An object that represents a locally installed application on the system. It can be used to open files or folders in a specific application. Use [getApplications](#getapplications) or [getDefaultApplication](#getdefaultapplication) to get applications that can open a specific file or folder. #### Properties | Property | Description | Type | | -------------------------------------- | ------------------------------------------------------------------------------ | -------- | | name\* | The display name of the application. | `string` | | path\* | The absolute path to the application bundle, e.g. `/Applications/Raycast.app`, | `string` | | bundleId | The macOS bundle identifier of the application, e.g. `com.raycast.macos`. | `string` | | localizedName | The localized name of the application. | `string` | | windowsAppId | The Windows App ID of the application. | `string` | ### PathLike ```typescript PathLike: string | Buffer | URL; ``` Supported path types. --- # Source: https://developers.raycast.com/misc/migration/v1.28.0.md # v1.28.0 This version contains an overhaul of the API surface to improve its discoverability and its usage in a code editor. The aim was to reduce the number of top-level exports to make it easier to find the ones that matter. It also aligns it with the structure of the documentation. {% hint style="info" %} The previous API surface is still there, only deprecated. All of your existing extensions will continue to work. You will get helpful hints in your code editor to migrate your extension. {% endhint %} ## Clipboard The methods related to the [Clipboard](https://developers.raycast.com/api-reference/clipboard) can now be found under the `Clipboard` namespace. ```js import { Clipboard } from "@raycast/api"; // deprecated copyTextToClipboard await Clipboard.copy("text"); // deprecated clearClipboard await Clipboard.clear(); // deprecated pasteText await Clipboard.paste("text"); ``` ## Storage The methods and interfaces related to the [Storage](https://developers.raycast.com/api-reference/storage) can now be found under the `LocalStorage` namespace. ```js import { LocalStorage } from "@raycast/api"; // deprecated allLocalStorageItems const items = await LocalStorage.allItems(); // deprecated getLocalStorageItem const item = await LocalStorage.getItem("key"); // deprecated setLocalStorageItem await LocalStorage.setItem("key", "value"); // deprecated removeLocalStorageItem await LocalStorage.removeItem("key"); // deprecated clearLocalStorage await LocalStorage.clear(); // we didn't expect you to use the Storage interfaces // but they are now also under LocalStorage // deprecated LocalStorageValue LocalStorage.Value; // deprecated LocalStorageValues LocalStorage.Values; ``` ## Feedback The main changes to the [Feedback](https://developers.raycast.com/api-reference/feedback) methods are related to the Toast: `showToast` now accepts a `Toast.Options` object as an argument and its style will default to `Toast.Style.Success`. ```js import { showToast, Toast } from "@raycast/api"; // deprecated new Toast() const toast = await showToast({ title: "Toast title" }); // Success by default // deprecated showToast(ToastStyle.Failure, 'Toast title') await showToast({ title: "Toast title", style: Toast.Style.Failure }); ``` The interfaces and enumerations of both the Toast and Alert can now be found under their respective namespaces. ```js import { Alert, Toast } from "@raycast/api"; // deprecated ToastOptions Toast.Options; // deprecated ToastActionOptions Toast.ActionOptions; // deprecated ToastStyle Toast.Style; // deprecated AlertOptions Alert.Options; // deprecated AlertActionOptions Alert.ActionOptions; // deprecated AlertActionStyle Alert.ActionStyle; ``` ## Keyboard The interfaces related to the [Keyboard](https://developers.raycast.com/api-reference/keyboard) can now be found under the `Keyboard` namespace. ```js import { Keyboard } from "@raycast/api"; // deprecated KeyboardShortcut Keyboard.Shortcut; // deprecated KeyModifier Keyboard.KeyModifier; // deprecated KeyEquivalent Keyboard.KeyEquivalent; ``` ## Preferences We are deprecating the `preferences` constant because we found it to be error-prone. Instead, you should always use `getPreferenceValues()` which allows for a type-safe access with fallback to the defaults. ## User Interface There are two important changes related to the React components: * `ActionPanel.Item` has been renamed to `Action`. All the specific actions are now nested under `Action`. This will make it easier to introduce and teach the concept of Action. * All the props interfaces are now accessible under their respective components ```jsx import { Action, List } from '@raycast/api' // deprecated ActionPanel.Item {}}> // deprecated CopyToClipboardAction // deprecated ListProps List.Props ``` ### Color The interfaces related to the [Color](https://developers.raycast.com/api-reference/user-interface/colors) can now be found under the `Color` namespace. ```js import { Color } from "@raycast/api"; // deprecated DynamicColor Color.Dynamic; // deprecated ColorLike Color.ColorLike; ``` ### Image The interfaces and enumerations related to the [Image](https://developers.raycast.com/api-reference/user-interface/icons-and-images) can now be found under the `Image` namespace. `Icon` is still a top-level export. ```js import { Image } from "@raycast/api"; // deprecated ImageLike Image.ImageLike; // deprecated ImageSource Image.Source; // deprecated ImageMask Image.Mask; ``` ## Misc * We are deprecating the `randomId` utility. It wasn't related to Raycast. Instead, you can use the [`nanoid`](https://github.com/ai/nanoid#readme) dependency. * We are deprecating the `useId` hook. It was used internally but there shouldn't be a use-case for it in your extensions. * We are deprecating the `useActionPanel` hook. Use the `ActionPanel` component instead. * We are deprecating the `render` method. You should `export default` your root component instead. --- # Source: https://developers.raycast.com/misc/migration/v1.31.0.md # v1.31.0 This version introduces support for multiple `` accessories via a new [`accessories` prop](https://developers.raycast.com/api-reference/user-interface/list#list.item.accessory). {% hint style="info" %} The `accessoryTitle` and `accessoryIcon` props still work, but are now marked as deprecated and may be removed in a future version. You will get helpful hints in your code editor to migrate your extension, and as usual we provide automated [migrations](https://developers.raycast.com/misc/migration) to help with the transition. {% endhint %} To migrate your extension manually, you need to ensure that all `List.Items` that specify `accessoryTitle` and/or `accessoryIcon` are updated as follows: ## `List.Item` with `accessoryTitle` ```typescript // becomes // becomes // becomes { try { await twitter.authorize(); return await twitter.fetchItems(search); } catch (error) { console.error(error); showToast({ style: Toast.Style.Failure, title: String(error) }); return []; } }; export default function Command() { const [search, setSearch] = useState(""); const items = usePromise(promise, [search]); const logout = useCallback(() => { twitter.logout(); popToRoot(); }, []); const actionPanel = ( ); // no need to set the `isLoading` prop, Raycast will set it automatically // until the React application isn't suspended anymore return ( {items.map((item) => { return ( ); })} ); } ``` --- # Source: https://developers.raycast.com/misc/migration/v1.42.0.md # v1.42.0 This version changed the `enableFiltering` option of `Grid` to `filtering`. {% hint style="info" %} The `enableFiltering` prop still work, but is now marked as deprecated and may be removed in a future version. You will get helpful hints in your code editor to migrate your extension, and as usual we provide automated [migrations](https://developers.raycast.com/misc/migration) to help with the transition. {% endhint %} To migrate your extension manually, you need to ensure that all `Grid` that specify `enableFiltering` are updated as follows: ```typescript ... // becomes ... ``` --- # Source: https://developers.raycast.com/misc/migration/v1.48.8.md # v1.48.8 This version introduces a new ESLint configuration file when creating new extensions with the `Create Extension` command. However, extensions prior to v1.48.8 still use the deprecated configuration. You can use `npx ray migrate` to use the new ESLint configuration and your `.eslintrc.json` file as well as your dependencies should be updated. If the migration didn't go well or if you defined the ESLint configuration in another place, please follow the steps below. First, install `@raycast/eslint-config` as part of your dev dependencies: ```sh npm install @raycast/eslint-config --save-dev ``` The previous default configuration looks like this: ```json { "root": true, "env": { "es2020": true, "node": true }, "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"] } ``` With `@raycast/eslint-config`, you can replace the whole file with only the following: ```json { "root": true, "extends": [ "@raycast" ] } ``` If you added plugins, turned on/off rules, or anything else modifying the ESLint configuration, it's up to you to merge it with the new configuration. Finally, you can remove these ESLint dependencies: ```sh npm uninstall @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier ``` --- # Source: https://developers.raycast.com/misc/migration/v1.50.0.md # v1.50.0 This version introduces an automated generation of typescript definitions for the preferences and arguments of your extension's commands. After updating the API version, you will notice a new file at the root of the extension folder called `raycast-env.d.ts`. * You shouldn't add this file to git so you have to update your `.gitignore` file: ```diff + raycast-env.d.ts ``` * You have to tell TypeScript to pick up this file to get access to its type definitions. To do so, update the `tsconfig.json` file: ```diff - "include": ["src/**/*"], + "include": ["src/**/*", "raycast-env.d.ts"], ``` * You can now update your code to leverage the automated types: ```diff ... - export default function Command(props: LaunchProps<{ arguments: { input: string } }>) { + export default function Command(props: LaunchProps<{ arguments: Arguments.CommandName }>) { ... ... - const prefs: { apiKey: string } = getPreferenceValues(); + const prefs: Preferences.CommandName = getPreferenceValues(); ... ``` --- # Source: https://developers.raycast.com/misc/migration/v1.51.0.md # v1.51.0 This version changed `environment.theme` to `environment.appearance`. {% hint style="info" %} `environment.theme` still works, but is now marked as deprecated and may be removed in a future version. You will get helpful hints in your code editor to migrate your extension, and as usual we provide automated [migrations](https://developers.raycast.com/misc/migration) to help with the transition. {% endhint %} To migrate your extension manually, you need to ensure that all `environment.theme` are updated to `environment.appearance`. --- # Source: https://developers.raycast.com/misc/migration/v1.59.0.md # v1.59.0 This version changed the `transient` option of `Clipboard` to `concealed`. {% hint style="info" %} The `transient` option still work, but is now marked as deprecated and may be removed in a future version. You will get helpful hints in your code editor to migrate your extension, and as usual we provide automated [migrations](https://developers.raycast.com/misc/migration) to help with the transition. {% endhint %} To migrate your extension manually, you need to ensure that all `Clipboard.copy` that specify `transient` are updated as follows: ## `Clipboard.copy` with `transient` option ```typescript Clipboard.copy(content, { transient: true }); // becomes Clipboard.copy(content, { concealed: true }); ``` ## `Action.CopyToClipboard` with `transient` prop ```typescript // becomes ``` --- # Source: https://developers.raycast.com/information/versioning.md # Versioning Versioning your extensions is straightforward since we've designed the system in a way that **frees you from having to deal with versioning schemes and compatibility**. The model is similar to that of app stores where there's only one implicit *latest* version that will be updated when the extension is published in the store. Extensions are automatically updated for end users. ## Development For **development**, this means that you do *not* declare a version property in the manifest. If you wish to use API features that were added in a later version, you just update your `@raycast/api` npm dependency, start using the feature, and submit an extension update to the store. ## End Users For **end-users** installing or updating your extension, Raycast automatically checks the compatibility between the API version that the extension actually uses and the user's current Raycast app version (which contains the API runtime and also manages the Node version). If there's a compatibility mismatch such as the user not having the required latest Raycast app version, we show a hint and prompt the user to update Raycast so that the next compatibility check succeeds. ## Version History Optionally, you can provide a `changelog.md` file in your extension, and give detailed changes with every update. These changes can be viewed by the user on the extension details screen, under Version History, as well as on the [raycast.com/store](https://raycast.com/store). You can learn more about Version History [here](https://developers.raycast.com/basics/prepare-an-extension-for-store#version-history), how to add it to your extension, and the required format for the best appearance. ## API Evolution Generally, we follow an **API evolution** process, meaning that we stay backward-compatible and do not introduce breaking changes within the same major API version. We'll 1) add new functionality and 2) we'll mark certain API methods and components as *deprecated* over time, which signals to you that you should stop using those features and migrate to the new recommended alternatives. At some point in the future, we may introduce a new breaking major release; however, at this time, you will be notified, and there will be a transition period for migrating extensions. --- # Source: https://developers.raycast.com/information/developer-tools/vscode.md # VS Code (community tool) You can enhance your VS Code development experience by installing the [Raycast extension in the marketplace](https://marketplace.visualstudio.com/items?itemName=tonka3000.raycast). Here's a list of features provided by the extension: * IntelliSense for image assets * A tree view for easier navigation (commands and preferences) * VS Code commands for creating new commands and preferences * The possibility to attach a Node.js debugger * VS Code commands for `ray` operations like `build`, `dev`, `lint`, or `fix-lint` --- # Source: https://developers.raycast.com/api-reference/window-and-search-bar.md # Raycast Window & Search Bar ## API Reference ### clearSearchBar Clear the text in the search bar. #### Signature ```typescript async function clearSearchBar(options?: { forceScrollToTop?: boolean }): Promise; ``` #### Parameters | Name | Description | Type | | ------------------------ | ----------------------------------------------------------------------------------------------------------------- | --------- | | options | Can be used to control the behaviour after the search bar is cleared. | `Object` | | options.forceScrollToTop | Can be used to force scrolling to the top. Defaults to scrolling to the top after the the search bar was cleared. | `boolean` | #### Return A Promise that resolves when the search bar is cleared. ### closeMainWindow Closes the main Raycast window. #### Signature ```typescript async function closeMainWindow(options?: { clearRootSearch?: boolean; popToRootType?: PopToRootType }): Promise; ``` #### Example ```typescript import { closeMainWindow } from "@raycast/api"; import { setTimeout } from "timers/promises"; export default async function Command() { await setTimeout(1000); await closeMainWindow({ clearRootSearch: true }); } ``` You can use the `popToRootType` parameter to temporarily prevent Raycast from applying the user's "Pop to Root Search" preference in Raycast; for example, when you need to interact with an external system utility and then allow the user to return back to the view command: ```typescript import { closeMainWindow, PopToRootType } from "@raycast/api"; export default async () => { await closeMainWindow({ popToRootType: PopToRootType.Suspended }); }; ``` #### Parameters | Name | Description | Type | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- | | options | Can be used to control the behaviour after closing the main window. | `Object` | | options.clearRootSearch | Clears the text in the root search bar and scrolls to the top | `boolean` | | options.popToRootType | Defines the pop to root behavior (PopToRootType); the default is to to respect the user's "Pop to Root Search" preference in Raycast | [`PopToRootType`](#poptoroottype) | #### Return A Promise that resolves when the main window is closed. ### popToRoot Pops the navigation stack back to root search. #### Signature ```typescript async function popToRoot(options?: { clearSearchBar?: boolean }): Promise; ``` #### Example ```typescript import { Detail, popToRoot } from "@raycast/api"; import { useEffect } from "react"; import { setTimeout } from "timers"; export default function Command() { useEffect(() => { setTimeout(() => { popToRoot({ clearSearchBar: true }); }, 3000); }, []); return ; } ``` #### Parameters | Name | Description | Type | | ---------------------- | ------------------------------------------------------------------------- | --------- | | options | Can be used to control the behaviour after going back to the root search. | `Object` | | options.clearSearchBar | | `boolean` | #### Return A Promise that resolves when Raycast popped to root. ## Types ### PopToRootType Defines the pop to root behavior when the main window is closed. #### Enumeration members | Name | Description | | --------- | -------------------------------------------------------------- | | Default | Respects the user's "Pop to Root Search" preference in Raycast | | Immediate | Immediately pops back to root | | Suspended | Prevents Raycast from popping back to root | --- # Source: https://developers.raycast.com/api-reference/window-management.md # Window Management The Window Management API provides developers with some functions to create commands with some advanced logic to move [Window](#windowmanagement.window)s around. {% hint style="info" %} Some users might not have access to this API. If a user doesn't have access to Raycast Pro, they will be asked if they want to get access when your extension calls the Window Management API. If the user doesn't wish to get access, the API call will throw an error. You can check if a user has access to the API using [`environment.canAccess(WindowManagement)`](https://developers.raycast.com/api-reference/environment). The API is not accessible on Windows for now. {% endhint %} ## API Reference ### WindowManagement.getActiveWindow Gets the active [Window](#windowmanagement.window). #### Signature ```typescript async function getActiveWindow(): Promise; ``` #### Example ```typescript import { WindowManagement, showToast } from "@raycast/api"; export default async function Command() { try { const window = await WindowManagement.getActiveWindow(); if (window.positionable) { await WindowManagement.setWindowBounds({ id: window.id, bounds: { position: { x: 100 } } }); } } catch (error) { showToast({ title: `Could not move window: ${error.message}`, style: Toast.Style.Failure }); } } ``` #### Return A Promise that resolves with the active [Window](#windowmanagement.window). If no window is active, the promise will be rejected. ### WindowManagement.getWindowsOnActiveDesktop Gets the list of [Window](#windowmanagement.window)s on the active [Desktop](#windowmanagement.desktop). #### Signature ```typescript async function getWindowsOnActiveDesktop(): Promise; ``` #### Example ```typescript import { WindowManagement, showToast } from "@raycast/api"; export default async function Command() { const windows = await WindowManagement.getWindowsOnActiveDesktop(); const chrome = windows.find((x) => x.application?.bundleId === "com.google.Chrome"); if (!chrome) { showToast({ title: "Couldn't find chrome", style: Toast.Style.Failure }); return; } WindowManagement.setWindowBounds({ id: chrome.id, bounds: { position: { x: 100 } } }); } ``` #### Return A Promise that resolves with an array of Windows. ### WindowManagement.getDesktops Gets the list of [Desktop](#windowmanagement.desktop)s available across all screens. #### Signature ```typescript async function getDesktops(): Promise; ``` #### Example ```typescript import { WindowManagement, showToast } from "@raycast/api"; export default function Command() { const desktops = await WindowManagement.getDesktops(); const screens = Set(desktops.map((desktop) => desktop.screenId)); showToast({ title: `Found ${desktops.length} desktops on ${screens.size} screens.` }); } ``` #### Return A Promise that resolves with the desktops. ### WindowManagement.setWindowBounds Move a [Window](#windowmanagement.window) or make it fullscreen. #### Signature ```typescript async function setWindowBounds( options: { id: string } & ( | { bounds: { position?: { x?: number; y?: number }; size?: { width?: number; height?: number }; }; desktopId?: string; } | { bounds: "fullscreen"; } ) ): Promise; ``` #### Example ```typescript import { WindowManagement, showToast } from "@raycast/api"; export default async function Command() { try { const window = await WindowManagement.getActiveWindow(); if (window.positionable) { await WindowManagement.setWindowBounds({ id: window.id, bounds: { position: { x: 100 } } }); } } catch (error) { showToast({ title: `Could not move window: ${error.message}`, style: Toast.Style.Failure }); } } ``` #### Parameters | Name | Description | Type | | ----------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | options\* | | `{ id: string }` or `{ bounds: { position?: { x?: number; y?: number }; size?: { height?: number; width?: number } }; desktopId?: string }` or `{ bounds: "fullscreen" }` | #### Return A Promise that resolves with the window was moved. If the move isn't possible (for example trying to make a window fullscreen that doesn't support it), the promise will be rejected. ## Types ### WindowManagement.Window A Window from an [Application](https://developers.raycast.com/utilities#application) on a [Desktop](#windowmanagement.desktop). #### Properties | Property | Description | Type | | ---------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------- | | active\* | | `boolean` | | bounds\* | | `{ position: { x: number; y: number }; size: { height: number; width: number } }` or `"fullscreen"` | | desktopId\* | | `string` | | fullScreenSettable\* | | `boolean` | | id\* | | `string` | | positionable\* | | `boolean` | | resizable\* | | `boolean` | | application | | [`Application`](https://developers.raycast.com/utilities#application) | ### WindowManagement.Desktop A Desktop represents a virtual desktop on a Screen. #### Properties | Property | Description | Type | | ------------------------------------------ | ----------- | --------------------------------------------------------------- | | active\* | | `boolean` | | id\* | | `string` | | screenId\* | | `string` | | size\* | | `{ height: number; width: number }` | | type\* | | [`WindowManagement.DesktopType`](#windowmanagement.desktoptype) | ### WindowManagement.DesktopType The type of a [Desktop](#windowmanagement.desktop). #### Enumeration members | Name | Description | | ---------- | ----------------------------------------------------------------------------------------- | | User | The default Desktop type. It can contain any number of [Window](#windowmanagement.window) | | FullScreen | A Desktop made of a single fullscreen window | --- # Source: https://developers.raycast.com/utilities/oauth/withaccesstoken.md # withAccessToken Higher-order function fetching an authorization token to then access it. This makes it easier to handle OAuth in your different commands whether they're `view` commands, `no-view` commands, or `menu-bar` commands. ## Signature ```tsx function withAccessToken( options: WithAccessTokenParameters, ): >( fnOrComponent: U, ) => U extends (props: T) => Promise | void ? Promise : React.FunctionComponent; ``` ### Arguments `options` is an object containing: * `options.authorize`: a function that initiates the OAuth token retrieval process. It returns a promise that resolves to an access token. * `options.personalAccessToken`: an optional string that represents an already obtained personal access token. When `options.personalAccessToken` is provided, it uses that token. Otherwise, it calls `options.authorize` to fetch an OAuth token asynchronously. * `options.client`: an optional instance of a PKCE Client that you can create using Raycast API. This client is used to return the `idToken` as part of the `onAuthorize` callback below. * `options.onAuthorize`: an optional callback function that is called once the user has been properly logged in through OAuth. This function is called with the `token`, its type (`oauth` if it comes from an OAuth flow or `personal` if it's a personal access token), and `idToken` if it's returned from `options.client`'s initial token set. ### Return Returns the wrapped component if used in a `view` command or the wrapped function if used in a `no-view` command. {% hint style="info" %} Note that the access token isn't injected into the wrapped component props. Instead, it's been set as a global variable that you can get with [getAccessToken](https://developers.raycast.com/utilities/oauth/getaccesstoken). {% endhint %} ## Example {% tabs %} {% tab title="view\.tsx" %} ```tsx import { List } from "@raycast/api"; import { withAccessToken } from "@raycast/utils"; import { authorize } from "./oauth"; function AuthorizedComponent(props) { return; // ... } export default withAccessToken({ authorize })(AuthorizedComponent); ``` {% endtab %} {% tab title="no-view\.tsx" %} ```tsx import { showHUD } from "@raycast/api"; import { withAccessToken } from "@raycast/utils"; import { authorize } from "./oauth"; async function AuthorizedCommand() { await showHUD("Authorized"); } export default withAccessToken({ authorize })(AuthorizedCommand); ``` {% endtab %} {% tab title="onAuthorize.tsx" %} ```tsx import { OAuthService } from "@raycast/utils"; import { LinearClient, LinearGraphQLClient } from "@linear/sdk"; let linearClient: LinearClient | null = null; const linear = OAuthService.linear({ scope: "read write", onAuthorize({ token }) { linearClient = new LinearClient({ accessToken: token }); }, }); function MyIssues() { return; // ... } export default withAccessToken(linear)(View); ``` {% endtab %} {% endtabs %} ## Types ### WithAccessTokenParameters ```ts type OAuthType = "oauth" | "personal"; type OnAuthorizeParams = { token: string; type: OAuthType; idToken: string | null; // only present if `options.client` has been provided }; type WithAccessTokenParameters = { client?: OAuth.PKCEClient; authorize: () => Promise; personalAccessToken?: string; onAuthorize?: (params: OnAuthorizeParams) => void; }; ``` ### WithAccessTokenComponentOrFn ```ts type WithAccessTokenComponentOrFn = ((params: T) => Promise | void) | React.ComponentType; ``` --- # Source: https://developers.raycast.com/utilities/functions/withcache.md # withCache Higher-order function which wraps a function with caching functionality using Raycast's Cache API. Allows for caching of expensive functions like paginated API calls that rarely change. ## Signature ```tsx function withCache Promise>( fn: Fn, options?: { validate?: (data: Awaited>) => boolean; maxAge?: number; }, ): Fn & { clearCache: () => void }; ``` ### Arguments `options` is an object containing: * `options.validate`: an optional function that receives the cached data and returns a boolean depending on whether the data is still valid or not. * `options.maxAge`: Maximum age of cached data in milliseconds after which the data will be considered invalid ### Return Returns the wrapped function ## Example ```tsx import { withCache } from "@raycast/utils"; function fetchExpensiveData(query) { // ... } const cachedFunction = withCache(fetchExpensiveData, { maxAge: 5 * 60 * 1000, // Cache for 5 minutes }); const result = await cachedFunction(query); ``` --- # Source: https://developers.raycast.com/ai/write-evals-for-your-ai-extension.md # Write Evals for Your AI Extension We all know that AI is not always reliable. This is why it's important to write evals for your AI Extension. Evals allow you to test your AI Extension and make sure it behaves as expected. ## Add an Eval The easiest way to add an eval is to first use your AI Extension. Then, once Raycast AI used your tools to finish its response, you can use the Copy Eval action to copy the eval to your clipboard. ![Copy Eval](https://2922539984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Me_8A39tFhZg3UaVoSN%2Fuploads%2Fgit-blob-b91e1651293f819adc528349025ebe722373f66e%2Fcopy-eval.webp?alt=media) You can then paste the eval into the `evals` array in the [`package.json` file](https://developers.raycast.com/information/manifest). ```json { "ai": { "evals": [ { "input": "@todo-list what are my open todos", "mocks": { "get-todos": [ { "id": "Z5lF8F-lSvGCD6e3uZwkL", "isCompleted": false, "title": "Buy oat milk" }, { "id": "69Ag2mfaDakC3IP8XxpXU", "isCompleted": false, "title": "Play with my cat" } ] }, "expected": [ { "callsTool": "get-todos" } ] } ] } } ``` ## Run Your Evals To run your evals, you can use the `npx ray evals` command. This will run the evals and print the results to the console. You get an overview of the evals that failed and the ones that passed. From here you can start improving the names and descriptions of your tools. Visit [Learn Core Concepts of AI Extensions](https://developers.raycast.com/learn-core-concepts-of-ai-extensions#evals) to learn more about the different types of evals you can write.