Skip to content

Queries

What Are Queries?

Queries are a way to search for information in your plugin. They are the main way users interact with your plugin, and they are the main way your plugin can provide information to the user.

Responding to Queries

To respond to a query, you need to define a method in your plugin class that is decorated with the @FlowPlugin.Search decorator. That method must return data to display. For now, we’ll just return a string to ensure that everything’s working. With this code your plugin will always return one result with the title “Hello, world!” and the icon that is specified in your plugin.json file.

@FlowPlugin.Class
export class MyPlugin extends FlowPlugin {
@FlowPlugin.Search
myFirstQuery() {
return "Hello, world!";
}
}

Using Search Text

Let’s say you want to return a different result based on the search text. You can do that by adding a parameter of type Query to your method. Query has several properties:

  • raw: string — the full text that the user entered
  • isReQuery: boolean — whether the query is a re-query
  • search: string — the text that the user entered after the action keyword and after the startsWith string from the @FlowPlugin.Search decorator.
  • terms: string[] — same as search, but split into an array of words
  • actionKeyword: string — the action keyword that the user entered
  • regexpMatches: RegExpMatchArray — the result of the regular expression match. If you didn’t use the regex parameter in the @FlowPlugin.Search decorator, this will be undefined
/** @param {Query} query */
@FlowPlugin.Search
myQuery(query) {
return `Hello, ${query.search}!`;
}

Returning Multiple Results

If you want to return multiple results, you can return an array:

return [
{title: `Hey, ${query.search}`},
{title: `Hello, ${query.search}`}
];

Returning More Complex Results

If you want to return more complex results, you can return an object instead:

return {
title: `Hello, ${query.search}`,
subtitle: "This is a subtitle",
};

This works with arrays as well:

return [
{
title: `Hey, ${query.search}!`,
subtitle: "Welcome to Flow Launcher!",
},
{
title: `Hello, ${query.search}!`,
subtitle: "Welcome to Flow Launcher!",
},
];

Different Commands

If you want to return different results based on the search text, you can define multiple methods decorated with the @FlowPlugin.Search decorator:

@FlowPlugin.Search
emptyQuery() {
return "Type 'name' or 'weather'";
}
@FlowPlugin.Search({ equalTo: "name" })
emptyNameQuery() {
return "Type a name after the word 'name'";
}
/** @param {Query} query */
@FlowPlugin.Search({ startsWith: "name " })
nameQuery(query) {
return {
title: `Hello, ${query.search}!`,
subtitle: "Welcome to Flow Launcher!",
};
}
@FlowPlugin.Search({ equalTo: "weather" })
emptyWeatherQuery() {
return "Type a city after the word 'weather'";
}
/** @param {Query} query */
@FlowPlugin.Search({ startsWith: "weather " })
weatherQuery(query) {
return `Probably sunny in ${query.search}, idk`;
}

Case Sensitivity

By default, the @FlowPlugin.Search decorator is case-insensitive. If you want to make it case-sensitive, you can set the caseSensitive property to true:

/** @param {Query} query */
@FlowPlugin.Search({ startsWith: "name ", caseSensitive: true })
nameQuery(query) {
return {
title: `Hello, ${query.search}!`,
subtitle: "Welcome to Flow Launcher!",
};
}

Async Queries

If you need to perform an async operation (such as fetching data from the internet), you can define an async method:

/** @param {Query} query */
@FlowPlugin.Search({ startsWith: "weather " })
async myQuery(query) {
await doSomething(); // some async operation
return `Probably sunny in ${query.search}, idk`;
}

Regexp Queries

Sometimes simple text matching is not enough. In this case, you can use regular expressions to match the search text. Let’s write a plugin that will accept two integers and perform a simple operation on them. You can use the matched groups to extract the numbers from the search text. If the user input matches your regular expression, the query object will have a regexpMatches property that contains the result of the calling userInput.match(yourRegex). Please note that if you’re using regular expressions, the type of the first argument your method accepts must be RegexpQuery instead of Query.

/** @param {RegexpQuery} query */
@FlowPlugin.Search({ regexp: /^(\d+)\s*([+\-*/])\s*(\d+)$/ })
calculate(query) {
const a = parseInt(query.regexpMatches[1]);
const op = query.regexpMatches[2];
const b = parseInt(query.regexpMatches[3]);
switch (op) {
case "+": return `= ${a + b}`;
case "-": return `= ${a - b}`;
case "*": return `= ${a * b}`;
case "/": return `= ${a / b}`;
}
}

While this does work, having to use array indexes to access the matched groups is not very convenient. Let’s use named capture groups instead:

/** @param {RegexpQuery} query */
@FlowPlugin.Search({ regexp: /^(?<a>\d+)\s*(?<op>[+\-*/])\s*(?<b>\d+)$/ })
calculate(query) {
const a = parseInt(query.regexpMatches.groups.a);
const op = query.regexpMatches.groups.op;
const b = parseInt(query.regexpMatches.groups.b);
switch (op) {
case "+": return `= ${a + b}`;
case "-": return `= ${a - b}`;
case "*": return `= ${a * b}`;
case "/": return `= ${a / b}`;
}
}

Perfect! The result is the same, but now we can refer to the matched groups by their names.

Often, when the user selects a result, you want to perform an action. You can do that by returning an object with the action property set. The value of the property must be either the return value of one of the methods in this.actions, or the result of running a custom action defined by you. Let’s take a look at both:

@FlowPlugin.Search
myQuery() {
return {
title: "Click me!",
action: this.actions.openUrl("https://example.com"),
};
}

When selecting this result, the user will have example.com opened in the browser they have specified in Flow Launcher’s settings. If you don’t want Flow Launcher to hide after invoking one of the built-in actions, you can call the .dontHide() method on the return result of this method:

@FlowPlugin.Search
myQuery() {
return {
title: "Click me!",
action: this.actions.openUrl("https://example.com").dontHide(),
};
}

Sometimes you need to perform an action that is not built into Flow Launcher or several built-in actions in a row. To do this, you can define your own method in your class and decorate it with the @FlowPlugin.Action decorator. Inside this method, you can also call the built-in actions using this.api. They’re always async, even if they don’t return any value, so you need to mark your method as async and await them. Your custom action must always return a boolean value indicating whether Flow Launcher window should be hidden after the action is performed. true will hide it, false will keep it open.

/** @param {Query} query */
@FlowPlugin.Search
myQuery(query) {
return {
title: "Say hello to me, but only if game mode is off",
action: this.myCustomAction(query.search),
};
}
/** @param {string} name */
@FlowPlugin.Action
async myCustomAction(name) {
const gameMode = await this.api.isGameModeOn();
if (!gameMode) {
await this.api.showMessage(`Hello, ${name}!`);
}
return true;
}

Context Menus

Context menus are a way to provide additional actions for a result. In Flow Launcher, you can call a context menu on a result by pressing Shift + Enter. To define a context menu, you need to define a method in your plugin class and decorate it with the @FlowPlugin.ContextMenu decorator. This method must return the same type of data as methods decorated with the @FlowPlugin.Search decorator do: a string, a number, an object, or an array of strings, numbers, or objects.

Context Menu Example

/** @param {Query} query */
@FlowPlugin.Search
myQuery(query) {
return {
title: `Hello, ${query.search}!`,
subtitle: "This is a subtitle",
contextMenu: this.myContextMenu(query.search),
};
}
/** @param {string} text */
@FlowPlugin.ContextMenu
myContextMenu(text) {
return [
{ title: text.toUpperCase(), subtitle: "Uppercase" },
{ title: text.toLowerCase(), subtitle: "Lowercase" },
];
}

Multiple Context Menus

You can have multiple different context menus:

/** @param {Query} query */
@FlowPlugin.Search({ startsWith: "text " })
myQuery(query) {
return {
title: `Hello, ${query.search}!`,
contextMenu: this.myQueryContextMenu(query.search),
};
}
/** @param {string} text */
@FlowPlugin.ContextMenu
myQueryContextMenu(text) {
return [
{ title: text.toUpperCase(), subtitle: "Uppercase" },
{ title: text.toLowerCase(), subtitle: "Lowercase" },
];
}
/** @param {RegexpQuery} query */
@FlowPlugin.Search({ regexp: /(?<number>\d+)/ })
myNumberQuery(query) {
const number = parseInt(query.regexpMatches.groups.number);
return {
title: query.regexpMatches.groups.number,
contextMenu: this.myNumberContextMenu(number),
};
}
/** @param {number} number */
@FlowPlugin.ContextMenu
myNumberContextMenu(number) {
return [
{ title: `Square: ${number ** 2}` },
{ title: `Cube: ${number ** 3}` },
];
}

Multiple Context Menus In the Same Query

You can even specify different context menus for different results in the same query:

/** @param {RegexpQuery} query */
@FlowPlugin.Search({ regexp: /^My name is (?<name>\w+) and I'm (?<age>\d+) years old$/i })
myQuery(query) {
const name = query.regexpMatches.groups.name;
const age = parseInt(query.regexpMatches.groups.age);
return [
{
title: `Name: ${name}`,
contextMenu: this.myNameContextMenu(name),
},
{
title: `Age: ${age}`,
contextMenu: this.myAgeContextMenu(age),
},
];
}
/** @param {string} name */
@FlowPlugin.ContextMenu
nameContextMenu(name) {
return `Hello, ${name}!`;
}
/** @param {number} age */
@FlowPlugin.ContextMenu
ageContextMenu(age) {
return `In 5 years you'll be ${age + 5} years old`;
}