Plugins and the toolkit
A plugin is an integration, the same shape as the built-in ones, packaged as a single file.
Install one
Section titled “Install one”- Put the file in
data/integrations/. With Docker, that’s the./datafolder you mounted. - Add its block under
integrations:, with thetype:the plugin declares. Place its widgets in the layout as usual. Secrets go in.env. - Restart Valet. Editing the file doesn’t reload it. The log lists what loaded, and why anything was skipped.
Every .js and .mjs file in that folder loads, in name order.
From source, you can also install from npm and list the package:
plugins: [valet-integration-something]A built-in type can’t be replaced unless the config says so:
plugins: packages: [valet-integration-something] replace: [tautulli]The toolkit
Section titled “The toolkit”A plugin’s default export is an integration, or a function that receives Valet’s toolkit and returns one. The function form needs no imports.
export default ({ z, defineIntegration }) => defineIntegration({ type: 'hello', name: 'Hello', config: z.object({ who: z.string().default('world') }), create: cfg => ({ widgets: { greeting: { title: 'Hello', fetch: async () => ({ type: 'text', text: `Hello, ${cfg.who}!`, summary: { label: 'Hello', value: cfg.who } }), }, }, }),});| In the toolkit | What it is |
|---|---|
z | Zod, the schema library. |
fmt | The formatters. |
defineIntegration | The full form. |
service | The short form. |
checkCard | Checks a card against the catalog, for your own tests. |
The export can also be a list, for several integrations in one file.
tally, the recap helper, isn’t in the toolkit. In a plugin, write the slide out.
What’s checked
Section titled “What’s checked”- A plugin must have
type,configandcreate, or it’s skipped with a message. - A widget card that doesn’t match the catalog becomes that widget’s error, naming the field. The page doesn’t break.
- A tool’s bad card is dropped, the reason is logged, and the model still gets its
data.
checkCard is strict: unknown fields are errors.
Rules of the road
Section titled “Rules of the road”- Return cards from the catalog.
- Give every card a
summary. - Tools say what they need. Pick the narrowest
access. - Say what you’re about to do, with
confirm. - Pictures go through
ctx.image(ref)and yourimage().
Visuals of your own
Section titled “Visuals of your own”The catalog covers most things, but a plugin can draw anything. Return a showcase card whose visual.kind is your plugin’s name, and put a browser module beside it as data/integrations/<kind>.web.js.
export default function mount(box, data, { run }) { // draw into box: plain DOM, canvas, SVG // run(action, args) calls one of your actions return { update(data) { /* new data arrived */ }, destroy() { /* the card went away */ }, };}It’s the same contract as Valet’s own 3D models. Use the page’s CSS variables, such as var(--foreground), var(--good), var(--primary) and var(--muted-foreground), so it fits light and dark.
In chat, the assistant shows your visual whenever one of your tools returns that card.
new-integration my-thing --full writes a working one to start from.
Cards for the house
Section titled “Cards for the house”Three cards draw their own glass and fill whatever row they land in. See forecast, calendar and thermostat.
A thermostat’s buttons are your own actions, run like any other, so the same permissions apply. set names the action for a new target, and it’s called with { temperature }. The dial moves at once and sends one request when you let go, then waits for your next card to agree. So return the new target on the next fetch.
Sharing one
Section titled “Sharing one”Publish the file anywhere. If it’s a good fit for everyone, move a service() into a family file in src/integrations/services/ and open a pull request.