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 that is marked with the [Search] attribute. 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.

public class MyPlugin : ExtendedPlugin
{
[Search]
public string MyQuery()
{
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 an ExtendedQuery parameter to your method. ExtendedQuery has two properties:

  • string Search — the text that the user entered after the action keyword
  • string[] SearchTerms — the text that the user entered after the action keyword, split into an array of words
[Search]
public string MyQuery(ExtendedQuery query)
{
return $"Hello, {query.Search}!";
}

Returning Multiple Results

If you want to return multiple results, you can return List, IEnumerable, or array instead:

[Search]
public List<string> MyQuery(ExtendedQuery query)
{
return new List<string>
{
$"Hey, {query.Search}!",
$"Hello, {query.Search}!",
};
}

Returning More Complex Results

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

[Search]
public ExtendedResult MyQuery(ExtendedQuery query)
{
return new ExtendedResult
{
Title = $"Hello, {query.Search}!",
Subtitle = "Welcome to Flow Launcher!",
};
}

This works with List, IEnumerable, and arrays as well:

[Search]
public List<ExtendedResult> MyQuery(ExtendedQuery query)
{
return new List<ExtendedResult>
{
new ExtendedResult
{
Title = $"Hey, {query.Search}!",
Subtitle = "Welcome to Flow Launcher!",
},
new ExtendedResult
{
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 with the [Search] attribute.

[Search]
public string EmptyQuery()
{
return "Type 'name' or 'weather'"
}
[Search(equalTo: "name")]
public string EmptyNameQuery()
{
return "Type a name after the word 'name'";
}
[Search(startsWith: "name ")]
public ExtendedResult NameQuery(ExtendedQuery query)
{
return new ExtendedResult
{
Title = $"Hello, {query.Search}!",
Subtitle = "Welcome to Flow Launcher!",
};
}
[Search(equalTo: "weather")]
public string EmptyWeatherQuery()
{
return "Type a city name after the word 'weather'";
}
[Search(startsWith: "weather ")]
public string WeatherQuery(ExtendedQuery query)
{
return $"Probably sunny in {query.Search}, idk";
}

Case Sensitivity

By default, the [Search] attribute is case-insensitive. If you want to make it case-sensitive, you can set the stringComparison property to something else, like this:

[Search(startsWith: "name ", stringComparison: StringComparison.CurrentCulture)]
public ExtendedResult NameQuery(ExtendedQuery query)
{
return new ExtendedResult
{
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:

[Search(startsWith: "weather ")]
public async Task<string> WeatherQuery(ExtendedQuery query)
{
await Task.Delay(1000); // Simulate downloading data
return $"Probably sunny in {query.Search}, idk";
}

Regex 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. To do this, give your method parameters appropriate types (int and string in this case) and name them _N where N is the index of the capturing group. The first capturing group is available as the parameter called _1, the second group as _2, etc.

[Search(regex: @"^(\d+)\s*([+\-*/])\s*(\d+)$")]
public string? MyRegexQuery(string _2, int _1, int _3)
{
return _2 switch
{
"+" => $"= {_1 + _3}",
"-" => $"= {_1 - _3}",
"*" => $"= {_1 * _3}",
"/" => $"= {_1 / _3}",
_ => null,
};
}

While this does work, it’s not the most readable code when you have to use capturing group indexes as variable names. Let’s improve this by using named capturing groups.

[Search(regex: @"^(?<a>\d+)\s*(?<op>[+\-*/])\s*(?<b>\d+)$")]
public string? MyRegexQuery(string op, int a, int b)
{
return op switch
{
"+" => $"= {a + b}",
"-" => $"= {a - b}",
"*" => $"= {a * b}",
"/" => $"= {a / b}",
_ => null,
};
}

Functionally, it works exactly the same way, but now the code is much more readable.

Regex Options

Just like the [Search] attribute with text matching, regular expressions in this attribute are case-insensitive by default. You can change this behavior by specifying regexOptions parameter.

[Search(
regex: @"^My name is (?<name>\w+) and I'm (?<age>\d+) years old$",
regexOptions: RegexOptions.None
)]
public string[] MyNumberQuery(string name, int age)
{
return new string[]
{
$"Name: {name}",
$"Age: {age}",
};
}

Performing Actions

Often, when the user selects a result, you want to perform an action. You can do that by returning an ExtendedResult with the Action or ActionAsync property set. This property must be a function that returns bool or an async function that returns bool. If the return value is true, Flow Launcher will close the search window. If it’s false, the search window will stay open after performing the action.

[Search]
public List<ExtendedResult> MyQuery(ExtendedQuery query)
{
return new List<ExtendedResult>
{
new ExtendedResult
{
Title = $"Hey, {query.Search}!",
Subtitle = "Welcome to Flow Launcher!",
Action = ctx =>
{
Api.ShowMsg("Hey, you clicked me!");
return true;
}
},
new ExtendedResult
{
Title = $"Hello, {query.Search}!",
Subtitle = "Welcome to Flow Launcher!",
ActionAsync = async ctx =>
{
await Task.Delay(1000); // Simulate async action
Api.ShowMsg("Hello, you clicked me!");
return true;
}
},
};
}

Nothing changed visually, but now, when you select a result, a message will appear.

Common Action Shortcuts

There are some common actions that you can perform with a single line of code. ExtendedResult has special properties for these actions:

  • string TextToCopyOnCtrlC — when the user presses Ctrl+C, this text will be copied to the clipboard
  • string TextToCopyOnEnter — when the user selects this result, this text will be copied to the clipboard
  • string UrlToOpen — when the user selects this result, this URL will be opened
  • string UrlToOpenIncognito — when the user selects this result, this URL will be opened in incognito mode

Only one of these properties can be set at a time.

return new ExtendedResult[]
{
new ExtendedResult
{
Title = "Press Ctrl+C to copy the URL",
Subtitle = "https://example.com/",
TextToCopyOnCtrlC = "https://example.com/",
},
new ExtendedResult
{
Title = "Press Enter to copy the URL",
Subtitle = "https://example.com/",
TextToCopyOnEnter = "https://example.com/",
},
new ExtendedResult
{
Title = "Press Enter to open the URL",
Subtitle = "https://example.com/",
UrlToOpen = "https://example.com/",
},
new ExtendedResult
{
Title = "Press Enter to open the URL in incognito mode",
Subtitle = "https://example.com/",
UrlToOpenIncognito = "https://example.com/",
},
};

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.

If you want to use context menus, you need to do four things:

  1. Mark your class as partial.
  2. Define a method that returns your context menu. Its return type is similar to the return type of the [Search] method, i.e., you can return a string, a list of strings, an array, or anything else described above on this page.
  3. Mark this method with the [ContextMenu] attribute.
  4. Use this method prefixed with Create. If this method’s name doesn’t include the words “context” or “menu”, you will also have to add ContextMenu to the end of the method name.

Let’s go over the code now.

Context Menu Example

Let’s create a plugin that can show a context menu with two options: with the search text uppercased and lowercased.

public partial class MyPlugin : ExtendedPlugin
{
[Search(startsWith: "text ")]
public ExtendedResult MyQuery(ExtendedQuery query)
{
return new ExtendedResult {
Title = $"Text: {query.Search}",
ContextMenu = CreateMyQueryContextMenu(query.Search),
};
}
[ContextMenu]
public ExtendedResult[] MyQueryContextMenu(string str)
{
return new ExtendedResult[] {
new ExtendedResult {
Title = str.ToUpper(),
Subtitle = "Uppercase",
},
new ExtendedResult {
Title = str.ToLower(),
Subtitle = "Lowercase",
},
};
}
}

Multiple Context Menus

You can have multiple different context menus:

[Search(startsWith: "text ")]
public ExtendedResult MyQuery(ExtendedQuery query)
{
return new ExtendedResult {
Title = $"Text: {query.Search}",
ContextMenu = CreateMyQueryContextMenu(query.Search),
};
}
[ContextMenu]
public ExtendedResult[] MyQueryContextMenu(string str)
{
return new ExtendedResult[] {
new ExtendedResult {
Title = str.ToUpper(),
Subtitle = "Uppercased",
},
new ExtendedResult {
Title = str.ToLower(),
Subtitle = "Lowercased",
},
};
}
[Search(regex: @"^(?<number>\d+)$")]
public ExtendedResult MyNumberQuery(int number)
{
return new ExtendedResult
{
Title = $"Number: {number}",
ContextMenu = CreateMyNumberQueryContextMenu(number),
};
}
[ContextMenu]
public ExtendedResult[] MyNumberQueryContextMenu(int number)
{
return new ExtendedResult[] {
new ExtendedResult {
Title = $"Square: {number * number}",
Subtitle = "Number squared",
},
new ExtendedResult {
Title = $"Cube: {number * number * number}",
Subtitle = "Number cubed",
},
};
}

Different Context Menus In the Same Query

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

[Search(regex: @"^My name is (?<name>\w+) and I'm (?<age>\d+) years old$")]
public ExtendedResult[] MyQuery(string name, int age)
{
return new ExtendedResult[]
{
new ExtendedResult
{
Title = $"Name: {name}",
ContextMenu = CreateNameContextMenu(name),
},
new ExtendedResult {
Title = $"Age: {age}",
ContextMenu = CreateAgeContextMenu(age),
}
};
}
[ContextMenu]
public string AgeContextMenu(int number)
{
return $"In 5 years you will be {number + 5} years old";
}
[ContextMenu]
public string NameContextMenu(string name)
{
return $"Hello, {name}!";
}