Commands
A command is an entry in the command palette (Ctrl K, or ⌘K on a Mac), listed in a Plugins group under your plugin's name. Flare can ask for arguments in a form it draws. The command answers with a message, or with a Vue view that Flare opens in a dialog.
Declare it
{
"permissions": { "data": "read" },
"contributes": {
"commands": [
{
"id": "count",
"title": "Count a table",
"description": "How many rows a table holds, and its first few.",
"icon": "hash",
"keywords": ["size", "rows", "documents"],
"arguments": [
{
"name": "container",
"title": "Table or collection",
"type": "container",
"required": true
}
],
"view": true
}
]
}
}keywords are extra words that find the command, up to ten. arguments are form fields, like a widget's settings, so a container argument picks from the open database. view means the command answers with a component.
Answer with a message
The command gets the arguments by name, plus ctx. Return a message and a tone, and Flare shows them as a toast:
import { definePlugin } from '@flare/plugin';
export default definePlugin({
commands: {
count: async ({ arguments: args }, ctx) => {
const container = String(args.container);
const count = await ctx.data.aggregate({ container, aggregate: 'count' });
return { message: `${container} holds ${count ?? 0}`, tone: 'info' };
},
},
});Answer with a view
With "view": true, return { props }. Flare opens the component registered under the same id in commandViews, in a dialog, and passes those props:
import { definePlugin } from '@flare/plugin';
import CountView from './views/CountView.vue';
export default definePlugin({
commands: {
count: async ({ arguments: args }, ctx) => {
const container = String(args.container);
const [count, first] = await Promise.all([
ctx.data.aggregate({ container, aggregate: 'count' }),
ctx.data.read({ container, limit: 5 }),
]);
return { props: { container, count, first } };
},
},
commandViews: {
count: CountView,
},
});Read the props with defineProps or useViewProps():
<script lang="ts" setup>
import type { DataRecord } from '@flare/plugin';
import { Button, DataGrid, Stat, useFlare, type GridColumn } from '@flare/plugin/vue';
import { computed } from 'vue';
const props = defineProps<{
container: string;
count: number | null;
first: DataRecord[];
}>();
const flare = useFlare();
const columns = computed<GridColumn[]>(() =>
[...new Set(props.first.flatMap((record) => Object.keys(record.values)))]
.slice(0, 5)
.map((name) => ({ name }))
);
const rows = computed(() => props.first.map(({ id, values }) => ({ id, values })));
async function copy(): Promise<void> {
await flare.copy(String(props.count ?? 0));
flare.toast(`Copied the count of ${props.container}`, { tone: 'positive' });
}
</script>
<template>
<div class="flex flex-col gap-3 p-4">
<div class="flex items-end justify-between gap-4">
<Stat :value="count" :label="`in ${container}`" :compact="false" :inset="false" />
<Button variant="secondary" size="sm" @click="copy">Copy the count</Button>
</div>
<div
v-if="rows.length"
class="h-56 overflow-hidden rounded-lg border border-flare-border"
>
<DataGrid :columns="columns" :rows="rows" />
</div>
</div>
</template>The dialog grows to fit the view, up to a limit, then scrolls. Props travel from the plugin's logic frame to its view frame as messages, so keep them plain data.
The open database
A command runs against whatever is open when the user picks it. ctx.target tells you what that is. It's null when no database is open, and ctx.data calls then reject with NO_DATABASE.