Introduction

Vicinae is typically extended using its React/TypeScript API, which allows you to focus on your extension while offloading all the complex UI rendering to the C++ core.

This page explains this design choice and gives a sneak peek at what extension code looks like. For a step-by-step guide on creating an extension, see the create extension page.

Why TypeScript?

TypeScript is a well-established scripting language with clean syntax and, most importantly, a thriving ecosystem around it. It's very easy to interface with almost any external API by installing a TypeScript SDK they provide.

Since we use NodeJS, we also support all the regular node APIs you might want to use. JavaScript is actually much faster than most people think (unlike certain other popular scripting languages) and can be very efficient when used for the right things.

The asynchronous model also makes it well-suited for fetching and transforming external data, presenting it, and so on—which is typically what most extensions will do.

Why React?

Since we're dealing with UI most of the time, we'd need a state management library anyway. React is a pure JavaScript library that allows developers to write declarative UI code, which is dramatically easier than having to update elements imperatively.

When a piece of state changes, the component rerenders, and it's up to the renderer (in this case, the Vicinae process) to ensure everything is handled gracefully and efficiently. As an extension developer, you don't have to deal with any of that. You simply use and update the provided UI components as needed, and the rest is handled for you.

React is also, like TypeScript, very well-established and widely known among developers.

Where's the browser at?

There is no browser backing the Vicinae extension ecosystem. No browser, no Electron, no HTML, no CSS...

It's simply pure JavaScript producing a serialized representation of the UI tree, which is then sent to the C++ Vicinae process and rendered as native UI from there. Conceptually, this is very similar to what React Native does.

All you need is a JavaScript runtime to execute JavaScript on the server-side (we use node), and everything works smoothly and efficiently.

Code example

If you write this:

import { ActionPanel, Action, List, Icon } from '@vicinae/api';
import { fruits } from './data';

export default function FruitList() {
	return (
		<List isShowingDetail searchBarPlaceholder={'Search fruits...'}>
			<List.Section title="Fruits">
				{fruits.map(fruit => (
					<List.Item 
						key={fruit.emoji}
						title={fruit.name} 
						icon={fruit.emoji} 
						detail={<List.Item.Detail markdown={fruit.description} />}
						actions={
							<ActionPanel>
								<Action.CopyToClipboard 
									title="Copy emoji" 
									content={fruit.emoji} 
								/>
							</ActionPanel>
						}
					/>
				))}
			</List.Section>
		</List>
	);
}

You get this:

Search automatically works, markdown is automatically formatted, life's great :)

Raycast compatibility

Most of the extension stuff is inspired by the way Raycast does it, and our long term goal is to be compatible with most existing Raycast extensions.

For this reason, our API follows the Raycast API very closely but also offers exclusive APIs. Our goal is to be our own thing, not copy everything Raycast is doing.

Was this page helpful?