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.Classexport class MyPlugin extends FlowPlugin { @FlowPlugin.Search myFirstQuery() { return "Hello, world!"; }}
@FlowPlugin.Classexport 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 enteredisReQuery: boolean
— whether the query is a re-querysearch: string
— the text that the user entered after the action keyword and after thestartsWith
string from the@FlowPlugin.Search
decorator.terms: string[]
— same assearch
, but split into an array of wordsactionKeyword: string
— the action keyword that the user enteredregexpMatches: RegExpMatchArray
— the result of the regular expression match. If you didn’t use theregex
parameter in the@FlowPlugin.Search
decorator, this will be undefined
/** @param {Query} query */@FlowPlugin.SearchmyQuery(query) { return `Hello, ${query.search}!`;}
@FlowPlugin.SearchmyQuery(query: 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}`}];
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",};
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!", },];
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.SearchemptyQuery() { 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`;}
@FlowPlugin.SearchemptyQuery() { return "Type 'name' or 'weather'";}
@FlowPlugin.Search({ equalTo: "name" })emptyNameQuery() { return "Type a name after the word 'name'";}
@FlowPlugin.Search({ startsWith: "name " })nameQuery(query: Query) { return { title: `Hello, ${query.search}!`, subtitle: "Welcome to Flow Launcher!", };}
@FlowPlugin.Search({ equalTo: "weather" })emptyWeatherQuery() { return "Type a city after the word 'weather'";}
@FlowPlugin.Search({ startsWith: "weather " })weatherQuery(query: 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!", };}
@FlowPlugin.Search({ startsWith: "name ", caseSensitive: true })nameQuery(query: 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`;}
@FlowPlugin.Search({ startsWith: "weather " })async myQuery(query: 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}`; }}
@FlowPlugin.Search({ regexp: /^(\d+)\s*([+\-*/])\s*(\d+)$/ })calculate(query: RegexpQuery) { 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}`; }}
@FlowPlugin.Search({ regexp: /^(?<a>\d+)\s*(?<op>[+\-*/])\s*(?<b>\d+)$/ })calculate(query: RegexpQuery) { 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.SearchmyQuery() { return { title: "Click me!", action: this.actions.openUrl("https://example.com"), };}
@FlowPlugin.SearchmyQuery() { 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.SearchmyQuery() { return { title: "Click me!", action: this.actions.openUrl("https://example.com").dontHide(), };}
@FlowPlugin.SearchmyQuery() { 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.SearchmyQuery(query) { return { title: "Say hello to me, but only if game mode is off", action: this.myCustomAction(query.search), };}
/** @param {string} name */@FlowPlugin.Actionasync myCustomAction(name) { const gameMode = await this.api.isGameModeOn(); if (!gameMode) { await this.api.showMessage(`Hello, ${name}!`); } return true;}
@FlowPlugin.SearchmyQuery(query: Query) { return { title: "Say hello to me, but only if game mode is off", action: this.myCustomAction(query.search), };}
@FlowPlugin.Actionasync myCustomAction(name: string) { 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.SearchmyQuery(query) { return { title: `Hello, ${query.search}!`, subtitle: "This is a subtitle", contextMenu: this.myContextMenu(query.search), };}
/** @param {string} text */@FlowPlugin.ContextMenumyContextMenu(text) { return [ { title: text.toUpperCase(), subtitle: "Uppercase" }, { title: text.toLowerCase(), subtitle: "Lowercase" }, ];}
@FlowPlugin.SearchmyQuery(query: Query) { return { title: `Hello, ${query.search}!`, subtitle: "This is a subtitle", contextMenu: this.myContextMenu(query.search), };}
@FlowPlugin.ContextMenumyContextMenu(text: string) { 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.ContextMenumyQueryContextMenu(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.ContextMenumyNumberContextMenu(number) { return [ { title: `Square: ${number ** 2}` }, { title: `Cube: ${number ** 3}` }, ];}
@FlowPlugin.Search({ startsWith: "text " })myQuery(query: Query) { return { title: `Hello, ${query.search}!`, contextMenu: this.myQueryContextMenu(query.search), };}
@FlowPlugin.ContextMenumyQueryContextMenu(text: string) { return [ { title: text.toUpperCase(), subtitle: "Uppercase" }, { title: text.toLowerCase(), subtitle: "Lowercase" }, ];}
@FlowPlugin.Search({ regexp: /(?<number>\d+)/ })myNumberQuery(query: RegexpQuery) { const number = parseInt(query.regexpMatches.groups.number); return { title: query.regexpMatches.groups.number, contextMenu: this.myNumberContextMenu(number), };}
@FlowPlugin.ContextMenumyNumberContextMenu(number: 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.ContextMenunameContextMenu(name) { return `Hello, ${name}!`;}
/** @param {number} age */@FlowPlugin.ContextMenuageContextMenu(age) { return `In 5 years you'll be ${age + 5} years old`;}
@FlowPlugin.Search({ regexp: /^My name is (?<name>\w+) and I'm (?<age>\d+) years old$/i })myQuery(query: RegexpQuery) { 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), }, ];}
@FlowPlugin.ContextMenunameContextMenu(name: string) { return `Hello, ${name}!`;}
@FlowPlugin.ContextMenuageContextMenu(age: number) { return `In 5 years you'll be ${age + 5} years old`;}