Paperless-ngx extension for Raycast

Developing a Raycast Plug-In to search my Paperless-instance

Since I have switched to Mac I have been using Raycast as my default application launcher, replacing the stock Spotlight solution. What is great about Raycast is the possibility to extend it with free plug-ins; this makes it easy to e.g. translate text using Deepl directly from within the launcher or view deployment status on Render, and is less disruptive to my workflow than searching the web or using additional 3rd-party applications. Another Spotlight replacement with similar functionality is Alfred.

Raycast offers many extensions I heavily use, and also offers the possibility to create so-called Quicklinks. Creating a quicklink https://www.hellofresh.de/recipes/search?q={Query} allows me to quickly initiate a search for recipes in the HelloFresh database. I have created similar quicklinks to easily search SAP product documentation or support notes. One solution where this approach did not work, however, is my self-hosted instance of the document management solution Paperless-ngx. Paperless is built with Python and Django and allows to search my database of documents from the UI. It hides the search parameters from the URL however, so I could not create a Quicklink to the search results.

The Paperless API can be directly queried however, and returns search results in JSON-format. Since my use case was simple enough, I decided to dive into React and TypeScript (which is what is used to create Raycast extensions) and write my first custom extension!

Getting Started with TypeScript and React

The best place to get started creating extensions for Raycast might be the developer guide on their homepage: A lot of valuable information can be found there, including but not limited to the React Getting Started guide and the TypeScript handbook. Even better though: While Raycast is closed-source, its extensions are not. I therefore took great inspiration from some already existing extensions on how to structure my extension (which was really simple, in the end).

Calling the API

What makes creating Raycast extensions relatively simple is that a lot of features are provided out of the box by the Raycast API. Integrated functionality can be imported to offer configuration features to users (which I needed to allow users to manually specify custom domains and access tokens for their own Paperless instance). These configuration variables are instantiated in a package.json-file and can then easily be consumed as variables in the application scripts:

import { getPreferenceValues } from '@raycast/api'

export interface Preferences {
	paperlessURL: string;
	apiToken: string;
}

const { paperlessURL }: Preferences = getPreferenceValues();
const { apiToken }: Preferences = getPreferenceValues();

Presenting the Results in the Frontend

To properly read the JSON response from my API calls, I needed to parse the object into its different properties. Luckily, this was rather easy by modeling the response model in a separate TypeScript file as such:

export type paperlessFetchResponse = paperlessResultModel;

export interface paperlessResultModel {
	count: number
	next?: string
	previous?: string
	results: paperlessResults[]
}

export interface paperlessResults {
	id: number
	correspondent?: string
	document_type?: string
	title: string
	...
}

The response model was then used as an interface in my React view to display the search results natively within Raycast:

interface DocListItemProps {
	result: paperlessResults
}
export const DocListItem = ({
	result,
}: DocListItemProps): JSX.Element => {
	return (
		<List.Item
			title={result.title}
			icon={Icon.TextDocument}
			actions={
				<ActionPanel>
					<Action.OpenInBrowser
					url={`http://${paperlessURL}/documents/${result.id}`}
					title="Open in Browser"
					/>
				</ActionPanel>
			}
		/>
	)
}

That was more or less all I had to change from the template I had used (npm-search). Now my search queries are instantly populating within Raycast!

Example

Searching the Plug-In for scanned documents concerning my JobRad: /images/jobrad.png

Further ideas / backlog

There is much more that could be added to the extension, like showing and updating correspondents, showing thumbnail previews, directly downloading documents or have a detail view for each document. This could also be published to the Raycast store. For now, it is working for me however.

The extension can be found on Github - to use it yourself simply create an empty extension using the Raycast “Create extension” option and copy the Github contents into the newly created extension folder.