> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumovi.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin format

> Every field a Lumovi plugin can have: its place in the sidebar, and its views' kinds, columns, status rules, details, links, related lists and actions, with the paths, conditions and templates they use.

A plugin is a YAML file with one document of kind `Plugin`. Its `spec` can hold a place in the sidebar, for a tool whose kinds belong together on one page, and views: under `views:`, a view tells Lumovi how to show a kind. This page lists everything a plugin can say. For a guided introduction, see [Write a plugin](/custom-resources/write-a-plugin) and [Plugins](/custom-resources/plugins).

```yaml certificates.yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
apiVersion: lumovi.dev/v1beta1
kind: Plugin
metadata:
  name: certificates
spec:
  version: 1.0.0
  needs:
    lumovi: 1.22.0
  groups: [pki.example.com]
  views:
    - kinds:
        - { group: pki.example.com, kind: Certificate }
      icon: shield-check
      columns:
        - { name: Hosts, path: '.spec.dnsNames[*]' }
        - { name: Secret, path: .spec.secretName }
        - { name: Expires, path: .status.notAfter, type: date }
      status:
        - when: { path: '.status.conditions[?(@.type=="Ready")].status', equals: 'True' }
          health: healthy
          label: Ready
        - when: { path: '.status.conditions[?(@.type=="Ready")].status', equals: 'False' }
          health: critical
          label: '{{ .status.conditions[?(@.type=="Ready")].reason ?? "Not ready" }}'
          detail: '{{ .status.conditions[?(@.type=="Ready")].message }}'
      details:
        - { name: Issuer, path: .spec.issuerRef.name }
        - { name: Renews, path: .status.renewalTime, type: date }
      links:
        - name: Secret
          kind: Secret
          objectName: '{{ .spec.secretName }}'
        - name: Issuer
          kind: '{{ .spec.issuerRef.kind ?? "Issuer" }}.pki.example.com'
          objectName: '{{ .spec.issuerRef.name }}'
```

## Files

| | Desktop app | In your cluster |
| - | - | - |
| **Where** | `~/.lumovi/plugins`, or the folder in `LUMOVI_PLUGINS_DIR` | The chart's `plugins` value, mounted at `/etc/lumovi/plugins` |
| **Which files** | Files and links whose names end in `.yaml` or `.yml`, in the folder itself, not in subfolders, except, in the desktop app, a [plugin with code](/custom-resources/plugins#your-own-plugins-with-code) in a folder of its own. A link is followed to the file it points to. Lumovi reads them in order of their names, up to 200, each up to 256 KB. | The same. Kubernetes mounts each entry as a link, which Lumovi follows. |
| **Reloading** | Press <kbd>⌘</kbd><kbd>R</kbd> (Ctrl+R on Windows and Linux) | Change the chart's `plugins` value and upgrade the release |

A file is one plugin: one document. It's used whole or not at all: if anything in it is wrong, none of it is used, and everything wrong with it is listed as a [problem](#problems). Files are read on their own: one with a problem, or one that can't be read, like a link to nothing, doesn't stop the others loading.

## Whose a kind is

A kind is one plugin's. Each kind gets one view, from the one plugin that shows it:

* **Among Lumovi's plugins**, no two show the same kind.
* **A plugin of yours shows the kinds no other plugin has.** If it names a kind one of Lumovi's plugins shows, it isn't used, and the problem says whose kind it is and what to do.
* **To show one of Lumovi's kinds your way, take its plugin's place.** A plugin of yours with the same `metadata.name` as one of Lumovi's, like `flux` or `cert-manager`, is used in its place, all of it: Lumovi's place in the sidebar and every one of its views go, for the kinds yours doesn't show as well. Those get the API server's columns. [Included plugins](/custom-resources/included-plugins) gives each plugin's name.
* **Among your own**, the first by file name is used. A second file with the same name isn't, whatever becomes of the first. Of two that show the same kind, the later isn't: files with the name of one of Lumovi's plugins are settled first, among themselves and against Lumovi's others, and then the rest, each once, in order of their file names.

A group can be shared: three of Lumovi's plugins have kinds in `argoproj.io`. What is one plugin's is the kind. A plugin that shows every kind of a group (`kind: '*'`) has that group to itself, and within a plugin a view for a kind itself wins over one for its whole group.

**API resources** says what shows each kind, under **Shown by**: the plugin's name as the sidebar has it, the name of your file, **Lumovi**, or a dash for a kind nothing shows specially. [All plugins](/custom-resources/plugins#every-plugin-on-one-page) lists every plugin, and says on its row when one of yours is in use in place of one of Lumovi's: **In use in place of Lumovi’s Flux**.

### Kubernetes’ own kinds

A plugin is for a tool's kinds, not Kubernetes' own:

* **A kind Lumovi has a page for**, like Pods, Deployments, Secrets or Roles (see [Resource kinds](/reference/resource-kinds)), can't be in a plugin at all.
* **A kind Lumovi shows itself**, like CustomResourceDefinitions, can't either. Lumovi has views of its own for a few of Kubernetes' kinds that have no page. They aren't plugins and aren't among the [included ones](/custom-resources/included-plugins); **Shown by** says **Lumovi** for them.
* **Any other kind of Kubernetes'**, like a Lease or a LimitRange, a plugin can show, and only show. It names the kind under [`shows`](#spec), and its view of it has columns, status, details, links and related lists, but no actions. Such a kind can't be under the plugin's place in the sidebar.

```yaml limits.yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
apiVersion: lumovi.dev/v1beta1
kind: Plugin
metadata:
  name: limits
spec:
  version: 1.0.0
  needs:
    lumovi: 1.22.0
  groups: []
  shows:
    - { kind: LimitRange }
  views:
    - kinds:
        - { kind: LimitRange }
      columns:
        - { name: Limits, path: '.spec.limits[*].type' }
```

## The document

<ResponseField name="apiVersion" type="string" required>
  Always `lumovi.dev/v1beta1`.
</ResponseField>

<ResponseField name="kind" type="string" required>
  Always `Plugin`.
</ResponseField>

<ResponseField name="metadata.name" type="string" required>
  The plugin's name: lowercase letters, digits and hyphens, starting with a letter and ending with a letter or a digit, at most 63 characters. It's the address of its page (`plugins/platform`). A name is one plugin's: of two files of yours with the same name, the first by file name is used, and a plugin of yours with the name of one of Lumovi's is used [in its place](#whose-a-kind-is). `lumovi`, a name starting with `lumovi-`, and `kubernetes` can't be a plugin's.
</ResponseField>

<ResponseField name="spec" type="object" required>
  What it says: see [spec](#spec). Any field it doesn't know is a problem, so typos don't go unnoticed.
</ResponseField>

These four are all a document has, and `name` is all its `metadata` has. A key Lumovi doesn't know is a problem wherever it is in the file, so a `metadata.labels` or a stray key at the top isn't passed over in silence.

## spec

A plugin's `spec` holds a place in the sidebar, views, or both. A file with one view and nothing else is a plugin too, the smallest one.

```yaml platform.yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
apiVersion: lumovi.dev/v1beta1
kind: Plugin
metadata:
  name: platform
spec:
  version: 1.0.0
  needs:
    lumovi: 1.22.0
  groups: [platform.example.com]
  label: Platform
  icon: database
  kinds:
    - { group: platform.example.com, kind: Database }
    - { group: platform.example.com, kind: DatabaseBackup }
  views:
    - kinds:
        - { group: platform.example.com, kind: Database }
      columns:
        - { name: Engine, path: .spec.engine }
```

<ResponseField name="version" type="string" required>
  The plugin's own version: three numbers, like `1.0.0`, with no leading zeros and nothing before or after them. `1.0` and `v1.0.0` are problems.
</ResponseField>

<ResponseField name="needs.lumovi" type="string" required>
  The oldest Lumovi the plugin works with, as three numbers, like `1.22.0`. A plugin that needs a newer Lumovi than the one reading it isn't used, and the problem says both versions.
</ResponseField>

<ResponseField name="groups" type="list of strings" required>
  The API groups its kinds are in, like `[platform.example.com]`. Every kind of a tool's that the plugin names, in its place in the sidebar or in a view, is in one of them. A group is written as Kubernetes writes it: lowercase letters, digits, hyphens and dots. Kubernetes' own groups, like `apps`, and `lumovi.dev` can't be among them. A plugin that only [shows Kubernetes' kinds](#kubernetes’-own-kinds) has an empty list.
</ResponseField>

<ResponseField name="shows" type="list of kinds">
  Kinds of [Kubernetes' own](#kubernetes’-own-kinds) that Lumovi has no page for, which the plugin shows and only shows. Each is written out as a view's [kinds](#a-view) are, with its `group` unless it's of the core group, and each is shown by one of the plugin's views.
</ResponseField>

<ResponseField name="kinds" type="list">
  The kinds of its place in the sidebar, in the order of their tabs: an entry in the sidebar leads to a page with all of them, everything in one list and a tab for each. Written as a view's [kinds](#a-view): `kind: '*'` is every kind of a group, each with a tab, in order of their names. Kubernetes' own kinds can't be among them. See [Plugins](/custom-resources/plugins).
</ResponseField>

<ResponseField name="label" type="string">
  Its name in the sidebar, on its page and in the command palette. Required with `kinds`.
</ResponseField>

<ResponseField name="icon" type="string">
  One of the [icons](#icons). Without one, it gets the puzzle piece of custom resources.
</ResponseField>

<ResponseField name="category" type="string">
  A section of the sidebar for Kubernetes' own kinds to sit in, at its end: `cluster` (**Cluster**), `network` (**Network**), `config` (**Configuration**) or `storage` (**Storage**). Without one, it's under **Plugins**.
</ResponseField>

<ResponseField name="views" type="list of views">
  How its kinds are shown: see [A view](#a-view). A plugin needs views, or `kinds` for a place in the sidebar.
</ResponseField>

A label, an icon and a category are for a place in the sidebar, so they need `kinds`. A place only groups kinds: how each kind looks, its columns, status and actions, comes from a view of this plugin's.

One of yours with the same `metadata.name` as one of Lumovi's is used in its place, all of it: see [Whose a kind is](#whose-a-kind-is).

A few fields are only for plugins whose [code draws pages](/custom-resources/plugins#pages-drawn-by-a-plugin’s-code): the ones from Lumovi-plugins, and a plugin of yours in a folder of its own, for [Developer mode](/custom-resources/plugins#your-own-plugins-with-code). A file of yours on its own, or a server's, with any of them isn't used, and its [problem](#problems) says which. Each is shown in Lumovi's words on the plugin's entry under **All plugins**, and is all its code may do:

| Field | What it says |
| - | - |
| `pages` | Pages of its own, each an `id` and a `title`, as tabs on its page: `[{ id: overview, title: Overview }]`. A page can say it `takes` one of the plugin's own kinds: among the plugin's tabs it stands where that kind's list was, and may be titled as the kind. Lumovi's own list of the kind stays where Lumovi lists every kind. One page takes a kind |
| `tabs` | Tabs it adds to its own kinds' details: `[{ id: application, title: Argo CD, kinds: [{ group: argoproj.io, kind: Application }] }]` |
| `commands` | Commands in the palette, each opening one of its pages: `[{ name: Open the rollouts’ history, page: history }]`. A command's name can't be one the palette already has, Lumovi's own like **Alerts**, **New window** or **Create…**, or a kind's list like **Pods**, since a person pressing it would expect Lumovi's. Nor can it start with "Lumovi", or be named for a change, like *Delete…*: it only opens a page, so it's named after the page. |
| `reads` | Of Kubernetes' own kinds, what its code is handed: a kind, and the `fields` of each object, like `[metadata.name, spec.nodeName]`, and with `status: true`, `events: true` or `usage: true`, its status as Lumovi shows it, its events, or its usage. Its own kinds it's handed whole. |
| `showsSecrets` | The keys of a Secret its pages may show, and where its own kind names that Secret: `[{ group: example.com, kind: Widget, path: spec.secretName, keys: [token] }]`. Lumovi reads them itself when the person presses **Reveal** or **Copy**: the code never sees them. |
| `changes` | What its pages may propose, of its own kinds only: `[{ group: argoproj.io, kind: Application, actions: [patch, delete] }]`, of `patch`, `create`, `delete` and `scale`. Each kind may say what in a change of it does `harm`: see [What a change harms](#what-a-change-harms). See [Changes a page proposes](/custom-resources/plugins#changes-a-page-proposes). |
| `opens` | Which of Lumovi's own lists its pages may open, at one object: `[{ group: apps, kind: Deployment }]`. Opening reads nothing for the plugin. |
| `logs` | `shown`: its pages may show a pod's logs, which Lumovi reads, as the person, and draws in its own log view: its code never sees a line. `read`: its code may also read a pod's last lines itself, once, of pods it was handed, which needs Pods in `reads`. See [Logs on a plugin's page](/custom-resources/plugins#logs-on-a-plugin’s-page). |
| `server` | Its tool's own server in the cluster, which its code may ask: `{ service: { name: prometheus-operated }, in: { group: monitoring.coreos.com, kind: Prometheus }, port: web, paths: [/api/v1/targets] }`. The Service by its `name`, or by up to 8 `labels` exactly one Service has; `in`, one of its own kinds, in whose objects' namespace the Service is; a `port` the Service declares, by name or number; and up to 20 `paths`, each up to 200 characters, written as they read, with a whole segment like `{name}` that Lumovi fills with the name of an object the page shows. A path may end in a fixed query, like `/api/v1/targets?state=active`, asked exactly as listed: Lumovi fills nothing in it, and a page can neither add one nor change it. See [A plugin's own server](/custom-resources/plugins#a-plugin’s-own-server). |
| `links` | Which of Lumovi's own places its pages may [link to](/custom-resources/plugins#links-to-lumovi’s-own-places), for an object it was handed: `logs` and `shell` for a pod, `port-forward` for a pod or a service. |

## A view

Each item under `views:`:

<ResponseField name="name" type="string">
  A name of its own, if you like. Nothing depends on it.
</ResponseField>

<ResponseField name="kinds" type="list" required>
  The kinds it's for, at least one. Each is a `kind` and its API `group`, and the group is one of the plugin's [`groups`](#spec). A kind's name is its own, like `GitRepository`: letters and digits, starting with a capital, with its group beside it and never in it.

  ```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
  kinds:
    - { group: source.toolkit.fluxcd.io, kind: GitRepository }
    - { group: source.toolkit.fluxcd.io, kind: HelmRepository }
  ```

  `kind: '*'` is every kind of a group, like the constraint kinds Gatekeeper makes, one for each of its templates. It needs a `group`. A view for a kind itself wins over one for its whole group.

  ```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
  kinds:
    - { group: constraints.gatekeeper.sh, kind: '*' }
  ```

  A kind of Kubernetes' own is left without `group` if it's of the core group, and has to be under the plugin's `shows`: see [Kubernetes' own kinds](#kubernetes’-own-kinds).
</ResponseField>

<ResponseField name="icon" type="string">
  The kind's icon, one of the [icons](#icons) below.
</ResponseField>

<ResponseField name="columns" type="list of fields">
  List columns, shown after the name and status. They replace the columns the API server prints for the kind. See [Fields](#fields).
</ResponseField>

<ResponseField name="status" type="list of rules">
  How to tell whether an object is healthy. The first rule that applies decides. With no rule that applies, Lumovi reads the status from [the usual conventions](/custom-resources/overview#status). See [Status rules](#status-rules).
</ResponseField>

<ResponseField name="details" type="list of fields">
  Facts in the detail panel's **Details** section. See [Fields](#fields).
</ResponseField>

<ResponseField name="links" type="list of links">
  Single objects this one points to, listed under **Related** in the detail panel's **Overview**. See [Links](#links).
</ResponseField>

<ResponseField name="related" type="list of related lists">
  Objects of other kinds that belong to this one, a tab each in the detail panel. See [Related](#related).
</ResponseField>

<ResponseField name="actions" type="list of actions">
  Changes people can make: a patch of the object, or a new object to create. See [Actions](#actions).
</ResponseField>

<ResponseField name="manages" type="list">
  How an object of this kind marks the workloads it manages, so a workload says what manages it. See [What it manages](#what-it-manages).
</ResponseField>

## Fields

Columns and details are both fields.

<ResponseField name="name" type="string" required>
  The column's header, or the fact's name.
</ResponseField>

<ResponseField name="path" type="path" required>
  Where its value is. See [Paths](#paths).
</ResponseField>

<ResponseField name="type" type="string" default="string">
  How it's shown and sorted:

  | Type | Shown as | Sorted |
  | - | - | - |
  | `string` | Text | Alphabetically |
  | `number` | Right-aligned | As numbers |
  | `date` | “in 30d”, "2h ago" | By time |
  | `boolean` | Yes or No | No before Yes |
  | `count` | How many values the path finds, or how many items a list has | As numbers |
</ResponseField>

<ResponseField name="default" type="string">
  What to show when the path finds nothing.
</ResponseField>

## Paths

Paths are the part of kubectl's JSONPath that CRD printer columns use, starting with a dot.

| Path | Finds |
| - | - |
| `.spec.secretName` | A field |
| `.metadata.labels['app.kubernetes.io/name']` | A key with dots or slashes (or `labels.app\.kubernetes\.io/name`) |
| `.spec.containers[0].image`, `[-1]` | An item of a list, counting from the end when negative |
| `.spec.hosts[*]`, `.metadata.labels.*` | Every item of a list, or every value of a map |
| `.status.conditions[?(@.type=="Ready")].status` | Items that match: `==` or `!=` a string, number, `true`, `false` or `null` |
| `.spec.parts[?(@.spare)]` | Items where a field is set (and not false) |

A path can also be written `{.spec.x}` or `$.spec.x`. When a path finds several values, they're shown joined with commas.

<Tip>
  In YAML, quote paths that contain brackets or double quotes, and anything that starts with `{`: `'.status.conditions[?(@.type=="Ready")].status'`.
</Tip>

## Status rules

<ResponseField name="when" type="condition">
  When the rule applies. See [Conditions](#conditions). A rule without one always applies, which makes it a good last rule.
</ResponseField>

<ResponseField name="health" type="string" required>
  One of `healthy`, `progressing`, `warning`, `critical` or `neutral`. It decides the color, the icon, where the object sorts, and which filter chip counts it.
</ResponseField>

<ResponseField name="label" type="template" required>
  What the status says, like `Ready` or `'{{ .status.phase }}'`.
</ResponseField>

<ResponseField name="detail" type="template">
  More about it, shown on hover, and in a plugin's **Message** column.
</ResponseField>

## Conditions

A condition reads one value at `path`, and compares it:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
when: { path: .status.phase, equals: Running }             # or notEquals
when: { path: .status.phase, in: [Failed, Error] }
when: { path: .status.message, matches: 'timeout|refused' } # a regular expression, ignoring case
when: { path: .spec.suspend, exists: true }                 # or false
when: { path: .spec.suspend }                               # set, and not false, 0 or empty
```

| Operator | Holds when the value |
| - | - |
| `equals` | Is this string, number, boolean or null |
| `notEquals` | Isn't |
| `in` | Is any of these (a list, not empty) |
| `matches` | Matches this regular expression, ignoring case |
| `exists` | Is set (`true`), or isn't (`false`) |
| (none) | Is set, and not false, 0 or empty |

Comparisons ignore case, so `equals: True` matches Kubernetes' `"True"` even though YAML reads an unquoted `True` as a boolean. When a path finds several values, the first is compared. A condition with more than one operator uses the first of `exists`, `equals`, `notEquals`, `in` and `matches` that it has.

Conditions combine with `all` and `any`, nested as deep as you need:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
when:
  all:
    - { path: .spec.suspend, notEquals: true }
    - any:
        - { path: .status.phase, equals: Running }
        - { path: .status.phase, equals: Succeeded }
```

A condition has exactly one of `path`, `all` or `any`.

## Templates

A status rule's `label` and `detail`, links, related lists, and actions' texts, patches and new objects can include values from the object.

| Template | Gives |
| - | - |
| `{{ .path }}` | The value at a path; several are joined with commas |
| `{{ .spec.issuerRef.kind ?? "Issuer" }}` | The value, or this text when the path finds nothing |
| `{{ .spec.target.name ?? .metadata.name }}` | The value, or another path's |
| `{{ now }}` | The current time, as Kubernetes writes times (most useful in patches) |
| `{{ input.replicas }}` | What was given for one of an action's [inputs](#inputs). One left empty counts as nothing, so `??` works on it. |

### In patches and new objects

In a `patch`, an `undo` or an object to `create`, a value that's a template and nothing else stands for the value itself, not its text:

* A number stays a number, and a list or an object is copied whole: `params: '{{ .spec.params }}'` copies the object's parameters.
* When the template finds nothing, the field is left out (and a list item is dropped), rather than set to an empty string.
* `{{ now }}` is always text, and so is everything under `labels` and `annotations`, which Kubernetes keeps as text.

A value with other text around its template, like `'{{ .metadata.name }}-copy'`, is always text.

Templates in `undo` are filled in from the object as it was before the action ran. So an undo can put back the value an action replaces: `undo: { spec: { mode: '{{ .spec.mode }}' } }`.

## Links

Links are listed under **Related** in the detail panel's **Overview**. Click one to open the object.

<ResponseField name="name" type="string" required>
  What the object is to this one, like `Secret` or `Issuer`.
</ResponseField>

<ResponseField name="kind" type="template" required>
  How Lumovi names its kind: a built-in kind (`Secret`, `Pod`, `Node`…), or a kind and its API group (`ClusterIssuer.cert-manager.io`).
</ResponseField>

<ResponseField name="objectName" type="template" required>
  Its name.
</ResponseField>

<ResponseField name="namespace" type="template">
  Its namespace. This object's unless set, and none for cluster-wide kinds.
</ResponseField>

A link whose kind or name comes out empty isn't shown.

## Related

Related lists are the objects of another kind that belong to this one: a node pool's nodes, a database's instances, a pipeline's runs. Each is a tab of the detail panel, after **Metrics**, listing them with their kind's usual columns. They're also on the object's **Map**.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
related:
  - name: Instances
    kind: Pod
    labels: { cnpg.io/cluster: '{{ .metadata.name }}', cnpg.io/podRole: instance }
  - name: Nodes
    kind: Node
    labels: { karpenter.sh/nodepool: '{{ .metadata.name }}' }
```

<ResponseField name="name" type="string" required>
  The tab's name, like `Instances` or `Nodes`.
</ResponseField>

<ResponseField name="kind" type="template" required>
  Their kind, named as for [links](#links): `Pod`, `NodeClaim.karpenter.sh`.
</ResponseField>

<ResponseField name="labels" type="map of templates">
  The labels they have, each value a template: `cnpg.io/cluster: '{{ .metadata.name }}'`.
</ResponseField>

<ResponseField name="fieldSelector" type="template">
  A field selector they match, like `'spec.nodeName={{ .status.nodeName }}'`.
</ResponseField>

<ResponseField name="namespace" type="template">
  Where to look. This object's namespace unless set. Kinds without namespaces are looked for across the cluster, and so is everything related to an object that has no namespace.
</ResponseField>

A related list needs `labels` or a `fieldSelector`: without one, it would be every object of the kind. A list whose kind, label values or field selector values come out empty isn't shown, since it would find the wrong objects. Like any list, it needs `list` on the kind.

The first list of `Pod`s is special: it becomes the panel's **Pods** tab, under the list's name (CloudNativePG's **Instances**). It brings a **Logs** tab with their logs, merged, and a **Metrics** tab with their [usage history](/metrics/usage-history).

## What it manages

A tool that deploys workloads, like a GitOps tool, marks each one it deployed with labels or annotations of its own. A view's `manages` says which, so Lumovi can tell a workload's manager: workloads' lists get a **Managed by** column, and a workload's **Overview** starts with a line for each manager, like "Kustomization flux-system/apps manages this deployment, and may undo changes made to it any other way.", which opens it.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
views:
  - kinds: [{ group: kustomize.toolkit.fluxcd.io, kind: Kustomization }]
    manages:
      - labels:
          kustomize.toolkit.fluxcd.io/name: '{{ .metadata.name }}'
          kustomize.toolkit.fluxcd.io/namespace: '{{ .metadata.namespace }}'
```

Each item holds `labels`, `annotations` or both: the keys a managed workload carries, each with a [template](#templates) of the manager's. A workload is managed by an object of this kind when every key of one item is on it, with exactly the value the template gives for that object. A template may also name the managed workload itself, as `{{ object.group }}`, `{{ object.kind }}`, `{{ object.namespace }}` and `{{ object.name }}`, for a tool that writes a workload's own name into its mark:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
manages:
  - annotations:
      argocd.argoproj.io/tracking-id: '{{ .metadata.name }}:{{ object.group }}/{{ object.kind }}:{{ object.namespace }}/{{ object.name }}'
```

* **At most 4 items**, and 8 keys in one.
* **Each key is under the group of the view's kinds**, or one under it: `kustomize.toolkit.fluxcd.io/name` for a Kustomization, `argocd.argoproj.io/tracking-id` for an Argo CD Application (`argoproj.io`). Never one of Kubernetes' own (`kubernetes.io`, `k8s.io` and every name under them), nor a key with no group: any chart can set those, so they can't tell one tool's workloads from another's.
* **Only for kinds of its own**, named one by one: a view of one of Kubernetes' own kinds only shows it, and `kind: '*'` can't say what it manages.
* The workloads are Deployments, StatefulSets, DaemonSets, Jobs and CronJobs.

To find managers, Lumovi lists each such kind in the whole cluster where you may list it, or else in the namespaces you're looking at, as you. The [Plugins page](/custom-resources/plugins#what-a-plugin-asks-for) says so for each plugin. Where no plugin says what it manages, nothing more is read.

## Actions

Actions change the object with a patch, like `kubectl patch`, or create another object, like `kubectl create`. Each has a `patch` or a `create`, not both.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
actions:
  - name: Reconcile
    icon: refresh-cw
    primary: true                                  # a button in the detail panel, not only in its menu
    when: { path: .spec.suspend, notEquals: true } # only offered when this holds
    runs: at-once                                  # it asks nothing first, and says so
    patch:
      metadata:
        annotations: { reconcile.fluxcd.io/requestedAt: '{{ now }}' }
    done: Asked Flux to reconcile {{ .metadata.name }} # the notification

  - name: Suspend
    runs: at-once
    patch: { spec: { suspend: true } }
    undo: { spec: { suspend: null } }              # offered as Undo in the notification
    done: Suspended {{ .metadata.name }}

  - name: Abort
    danger: true
    confirm: The rollout stops and traffic goes back to the stable version. # asks first
    subresource: status                            # patches the status subresource
    patch: { status: { abort: true } }
```

<ResponseField name="name" type="string" required>
  The action's name, in menus and on its button.
</ResponseField>

<ResponseField name="patch" type="object or list">
  A merge patch (an object), or, with `type: json`, a list of JSON patch operations. Templates work inside it, within [what a patch can't change](#what-a-patch-can’t-change).
</ResponseField>

<ResponseField name="create" type="object">
  An object to create instead, with templates. See [Creating objects](#creating-objects).
</ResponseField>

<ResponseField name="inputs" type="list of inputs">
  Values to ask for before it runs, at least one. See [Inputs](#inputs).
</ResponseField>

<ResponseField name="type" type="string" default="merge">
  `merge` or `json`. A merge patch is an object, and a JSON patch is a list; anything else is a problem.
</ResponseField>

<ResponseField name="subresource" type="string">
  `status`, to patch the object's status subresource.
</ResponseField>

<ResponseField name="undo" type="object or list">
  A patch that takes the change back, offered as **Undo** in the notification. Same type as `patch`, and filled in from the object as it was before. Actions that create have no undo.
</ResponseField>

<ResponseField name="confirm" type="template">
  Asks first, with this text.
</ResponseField>

<ResponseField name="confirmTitle" type="template">
  The dialog's question, in place of Lumovi's "{action} {name}?": `Terminate {{ .metadata.name }}’s sync?`. One line, 80 characters at most, and only on an action that asks first. Lumovi still names the object under it.
</ResponseField>

<ResponseField name="confirmButton" type="template">
  The dialog's button, in place of the action's name. One line, 24 characters at most, and only on an action that asks first.
</ResponseField>

<ResponseField name="runs" type="string">
  `at-once`, on an action that asks nothing first. See [When an action asks first](#when-an-action-asks-first).
</ResponseField>

<ResponseField name="done" type="template">
  The notification once it's done. Without it, the notification is the action's name and the object's.
</ResponseField>

<ResponseField name="when" type="condition">
  Offer the action only when this holds. Just before it's sent, Lumovi reads the object again and judges this on it again, and sends what you read with the version it read then (`metadata.resourceVersion`, or a JSON patch's first `test`), so a change in between is refused by the cluster. Where it no longer holds, nothing is sent: "storefront has changed since you looked at it, so Terminate sync wasn’t sent and nothing changed. Look at it again." Where what's sent would come out different from what you read, or the cluster refuses the version: "storefront changed while you looked at it, so nothing was changed. Look at it again."
</ResponseField>

<ResponseField name="same" type="list of paths">
  Up to four paths that must hold, as it's sent, what they held in the object you were shown, or nothing is sent, with the line above. Argo CD's Terminate sync names `.status.operationState.startedAt`, so it never stops a sync other than the one you saw.
</ResponseField>

<ResponseField name="busy" type="list">
  Up to four `{ when, word, since }`: while one holds (the first that does), the action is drawn busy where it was, in place of being offered or hidden, with Lumovi's turning mark, `word` (24 characters at most), and how long since the date at the path `since`, where it's given. It can't be pressed. Argo CD's Sync says `Syncing` while a sync runs.
</ResponseField>

<ResponseField name="primary" type="boolean">
  Also show it as a button in the detail panel, not only in its menu.
</ResponseField>

<ResponseField name="danger" type="boolean">
  It's destructive: it's shown in red, and its confirmation starts on **Cancel**.
</ResponseField>

<ResponseField name="icon" type="string">
  One of the [icons](#icons). Without one, an action gets a lightning bolt.
</ResponseField>

<ResponseField name="bulk" type="boolean">
  Offered in Lumovi's own bar for rows a person selects on a plugin's page, a patch done to each of them. Its inputs are asked once for all, those that say `bulk: false` filled in with their defaults. Lumovi reads each object again as it's sent, judges `when` and `same` on it, and sends it with the version it read; what's left out is said, and why. Where the patch reads anything of the object, the dialog says the command is the first's and "each is filled in from its own object". Only a patch: never a `create`.
</ResponseField>

<ResponseField name="ends" type="object">
  How its work ends, once its `busy` no longer holds: a `path` on its own object, the `values` it ends on, each with a `word` and a `level` (`Failed`, `critical`), and where a `message` beside it is. Lumovi follows each change it sent until then, says how it ended in these words, and nothing else of the object, the message cut at 500 characters.
</ResponseField>

### When an action asks first

An action with a `confirm`, `inputs` or a `create` opens a dialog first, and its name ends in "…" in menus, like **Back up now…**. The dialog shows the `confirm` text, the inputs, what it would create, and the equivalent command. Its button has the action's name.

An action with none of the three runs at once, and has to say so: `runs: at-once`. That way nobody reading the file takes a change that asks nothing for one that asks. An action that asks first can't say it. One done to several (`bulk`) may still say `runs: at-once` with inputs: pressed for one object it runs at once, filled in with its inputs' defaults (each needs one), and for several its inputs are asked once.

### What a change harms

A kind under `changes` may list what in a change of it does harm, up to 8. Lumovi checks every change of that kind against it as it's sent, the page's proposals and the file's own actions alike, and for several objects each one's own: each harm that holds is drawn in red in Lumovi's dialog, and its verb is named on the button, like **Sync, prune and force**, whatever the plugin's code says.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
changes:
  - group: argoproj.io
    kind: Application
    actions: [patch, delete]
    harm:
      - path: operation.sync.prune
        is: true
        unless: { path: operation.sync.dryRun, is: true }
        says: Deletes what’s no longer in its source.
        verb: prune
      - path: operation.sync.syncOptions
        contains: Replace=true
        unless: { path: operation.sync.dryRun, is: true }
        says: Writes each resource whole, dropping what anything else set on it.
        verb: replace
```

* **`path`**: where in the change, as names with dots between.
* **One of `is`, `set` or `contains`**: the value that harms, `set: true` for its being there at all, or a value a list there holds.
* **`says`**: what it does, up to 160 characters, and **`verb`**: the word on the button, up to 24.
* **`unless`**: a `path` and the value at it that makes the change harmless, like a dry run. It quiets the harm only where the person set that value with a switch or a check of their own: never a value the plugin wrote, one read off the object, or a default filled in where it isn't asked.

A JSON patch is judged failing closed: an operation that copies, moves, adds or replaces onto or into a harm's path counts as harming, whatever it carries. An action whose patch may write a harm's path has to ask first.

### What a patch can’t change

Some of an object's `metadata` is the cluster's to set, or its owner's, and no plugin's:

* **In a patch or an undo:** `ownerReferences`, `finalizers`, `managedFields`, `namespace`, `name`, `uid`, `resourceVersion`, `generation`, `creationTimestamp` and `deletionTimestamp`.
* **In an object to create:** `ownerReferences`, `finalizers` and `managedFields`. A new object has its name, and can have a namespace.

So that nothing filled in later can get round this, a few things are written out and never come from a template:

* `metadata`, in a patch or an object to create, is a map written in the file, not one placeholder.
* Each operation of a JSON patch is written out, and so are its `path` and `from`, as text with no `{{ }}` in them.
* A JSON patch operation can't name `/metadata` itself or the whole object, and can't name one of the fields above in its `path` or `from`, whatever the operation is.

Labels and annotations are yours to set, and their values can be templates.

The file is checked when it's read. The change is checked again as it's sent, filled in: one that would change any of these isn't sent, and the notification says "Lumovi didn’t send this: it changes metadata.finalizers, which a plugin can’t change."

### Inputs

Inputs are values an action asks for, used in its templates as `{{ input.name }}`.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
- name: Switch over
  inputs:
    - name: instance
      label: New primary
      type: choice
      from: '.status.instanceNames[*]'
  confirm: The instance chosen becomes the primary, and {{ .status.currentPrimary }} a replica.
  subresource: status
  patch: { status: { targetPrimary: '{{ input.instance }}' } }
  done: Switching {{ .metadata.name }} over to {{ input.instance }}

- name: Pause at
  inputs:
    - { name: replicas, label: Replicas while paused, type: number, default: '{{ .status.originalReplicaCount }}' }
  patch:
    metadata:
      annotations: { autoscaling.keda.sh/paused-replicas: '{{ input.replicas }}' }
```

<ResponseField name="name" type="string" required>
  How templates name it: letters, digits and `_`, not starting with a digit, like `replicas`.
</ResponseField>

<ResponseField name="label" type="string" required>
  What the dialog calls it.
</ResponseField>

<ResponseField name="type" type="string" default="text">
  * `text`: a text field.
  * `number`: a whole number from 0 to 1,000. In a patch, a template that's only this input gives a number (except under `labels` and `annotations`).
  * `choice`: one of a few options, picked from a list.
  * `switch`: yes or no, filled in as `true` or `false`. Its `default` is `true` or `false`.
</ResponseField>

<ResponseField name="options" type="list">
  A choice's options: `[Off, Initial, Recreate]`.
</ResponseField>

<ResponseField name="choices" type="list">
  A choice's options as labels the person picks among, each filling in its `value` as it is, any value JSON can hold: `[{ label: Apply, value: { apply: {} } }]`. Up to 20.
</ResponseField>

<ResponseField name="bulk" type="boolean">
  `false`: not asked where the action is done to several at once; its `default` fills it in for each, so it needs one.
</ResponseField>

<ResponseField name="from" type="path">
  Where to read a choice's options from instead, like `'.status.instanceNames[*]'`. When it finds nothing, the dialog says **There's nothing to choose from.**
</ResponseField>

<ResponseField name="default" type="template">
  The value it starts with. Without one, a number starts at 0, and a choice on its first option.
</ResponseField>

A choice has one of `options`, `choices` or `from`, and other inputs have none of them. Every input needs a value before the action can run. Templates in `patch`, `create`, `confirm` and `done` can only name inputs the action has.

### Creating objects

`create` is an object to create, written as you'd write its YAML, with templates in it. It needs an `apiVersion`, a `kind`, and a `metadata.name` or `metadata.generateName` as text. What it creates is one of the plugin's own kinds: one under its place in the sidebar, or one a view of it shows. So its `apiVersion` and `kind` are written out, not templates.

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
- name: Back up now
  icon: archive
  inputs:
    - { name: method, label: Method, type: choice, options: [barmanObjectStore, volumeSnapshot] }
  create:
    apiVersion: postgresql.cnpg.io/v1
    kind: Backup
    metadata:
      generateName: '{{ .metadata.name }}-'
    spec:
      cluster: { name: '{{ .metadata.name }}' }
      method: '{{ input.method }}'
  done: Started a backup of {{ .metadata.name }}
```

* **Its name.** With `generateName`, Lumovi picks the random end of the name itself, the way the API server would, so the dialog shows the name it will get.
* **Its namespace.** The object's own, unless `metadata.namespace` says otherwise. Kinds without namespaces get none.
* **Copying.** A template that's a whole value copies it, lists and objects included, and leaves the field out when it finds nothing. Tekton's **Run again** copies a run's pipeline, parameters and workspaces this way: `params: '{{ .spec.params }}'`.
* **Before and after.** The dialog shows the object as YAML first, under **Creates** with its kind and name. Once it's created, **Open** in the notification opens it.

Creating something can't be undone from the notification: delete what it made instead.

### Permissions and commands

An action is enabled only when the cluster says your account may make the change: `patch` on the kind (or on its `status`, with `subresource: status`) for a patch, and `create` on the kind it creates for a `create`. Like every change, it shows the equivalent command, `kubectl patch …` or `kubectl create -f <name>.yaml`, and is refused in a [read-only](/changes/read-only) cluster.

## Texts

A plugin's own texts are plain text, and short enough to show:

* **80 characters at most** for a `name`, a `label` and an input's `default`.
* **500 characters at most** for a `confirm`, a `detail` and a `done`, and for a status rule's `label`, which is a template.
* **No characters that can't be seen**, in any text or key of the file: the ones that turn a text's direction, the ones with no width, and control characters. A `name`, a `label` and a `default` can't hold a line break or a tab either. The problem names the character by its code point, like U+202E.

What a patch or a created object holds is the cluster's to limit, so the two lengths don't apply inside `patch`, `undo` and `create`.

## Icons

Plugins can use these [Lucide](https://lucide.dev/icons) icons, for their place in the sidebar, their views' kinds and their actions:

`activity`, `archive`, `bell`, `box`, `boxes`, `bug`, `camera`, `cloud`, `database`, `file-check`, `flame`, `gauge`, `git-branch`, `git-merge`, `globe`, `hexagon`, `key-round`, `layers`, `lock`, `network`, `package`, `play`, `puzzle`, `radar`, `refresh-cw`, `rocket`, `route`, `scaling`, `server`, `server-cog`, `shield-check`, `signpost`, `square`, `timer`, `waves`, `waypoints`, `workflow`

## Problems

Lumovi checks every plugin before using it. One with a problem isn't used at all. [All plugins](/custom-resources/plugins#when-a-file-isn’t-in-use) lists its file as a row, **Not in use. 2 problems to fix in the file.**, with everything wrong with it underneath, each line saying where in the file it is, from the top.

| Message | Cause |
| - | - |
| `holds nothing` | An empty file, or one with only comments |
| `should start with apiVersion: lumovi.dev/v1beta1 and kind: Plugin` | The file isn't a plugin |
| `a view goes inside a plugin: start with kind: Plugin and put this under its views.` | A document of kind `View` |
| `holds 2 documents, and a file is one plugin` | More than one document in a file |
| `needs metadata.name` | A plugin without a name |
| `metadata.name: "My Platform" should be lowercase letters, digits and hyphens, starting with a letter and ending with a letter or a digit, at most 63 characters` | A name that can't be a plugin's |
| `metadata.name: "lumovi-tools" starts with Lumovi’s own name, which a plugin can’t have` | A name that reads as Lumovi's. `lumovi` and `kubernetes` are refused the same way |
| `needs spec` | A plugin with no `spec` |
| `spec.version: is required` | A plugin without a version |
| `spec.version: "1.0" should be three numbers, like 1.0.0` | A version that isn't three numbers |
| `spec.needs.lumovi: is required` | A plugin that doesn't say which Lumovi it needs |
| `needs Lumovi 1.30.0 or newer; this is 1.22.0` | A plugin for a newer Lumovi than this one |
| `spec.groups: is required: the API groups its kinds are in, like [example.com]` | A plugin without `groups` |
| `spec.groups[0]: "Platform/x" should be an API group’s name, like cert-manager.io` | A group that isn't written as one |
| `spec.views[0].kinds[0]: "Database.platform.example.com" should be a kind’s own name, like Certificate: its group goes under group` | A kind with more than its name in `kind` |
| `spec.views[0].kinds[0]: Widget.example.com isn’t in one of its groups: add example.com to spec.groups` | A kind outside the plugin's `groups` |
| `spec.groups[0]: apps is Kubernetes’ own, so a plugin can’t have it` | One of Kubernetes' groups among `groups` |
| `spec.groups[0]: lumovi.dev is Lumovi’s own, so a plugin can’t have it` | Lumovi's group among `groups` |
| `spec: needs views, or kinds for a place in the sidebar` | A plugin with neither |
| `spec: needs a label for its place in the sidebar` | `kinds` without a `label` |
| `spec: a label, an icon or a category is for a place in the sidebar, and that needs kinds` | One of them without `kinds` |
| `spec: Pod has a page of its own, so it can’t be in a plugin` | A kind Lumovi has a page for, among a plugin's `kinds` |
| `spec.views[0].kinds[0]: Pod has a page of its own, so it can’t be in a plugin` | The same, in a view |
| `spec.views[0].kinds[0]: CustomResourceDefinition.apiextensions.k8s.io is shown by Lumovi itself, so it can’t be in a plugin` | A kind Lumovi has a view of its own for |
| `spec.views[0].kinds[0]: Lease.coordination.k8s.io is Kubernetes’ own: to show it, name it under spec.shows` | A view of one of Kubernetes' kinds that isn't under `shows` |
| `spec.views[0].actions: a view of Kubernetes’ own kind only shows it, so it can’t have actions` | Actions on a view of one of Kubernetes' kinds |
| `spec.kinds[1]: Lease.coordination.k8s.io is Kubernetes’ own, so it can’t be under a plugin’s place in the sidebar` | One of Kubernetes' kinds among a plugin's `kinds` |
| `spec.shows[0]: Database.platform.example.com isn’t Kubernetes’ own: a view names it once platform.example.com is in spec.groups` | A tool's kind under `shows` |
| `spec.shows[0]: LimitRange isn’t shown by any of its views` | A kind under `shows` that no view shows |
| `spec.category: should be one of cluster, network, config, storage, not "workloads"` | A sidebar section plugins can't sit in |
| `spec.pages: a plugin of yours draws pages only from a folder of its own, for Developer mode: platform/plugin.yaml, with its plugin.js` | In a file of yours on its own, a field only a plugin with code uses: `pages`, `tabs`, `commands`, `reads`, `showsSecrets`, `changes`, `opens`, `links`, `logs` or `server`. The line names the field, and the folder by the plugin's name. See [Your own plugins with code](/custom-resources/plugins#your-own-plugins-with-code). |
| `spec.pages: this server’s plugins are data: only the plugins that come with Lumovi draw pages here` | One of those fields in a [server's](/server/plugins) file |
| `spec.server.paths[0]: takes no fragment: Lumovi asks for the path as listed` | A `#` in a path. Others say a path should start with `/`, has an empty segment, can't step with `.` or `..`, has a %-escape, fills only a whole segment, or has a character a path can't hold, said as its code point where it can't be seen, like `U+202E`. |
| `spec.server.paths[0]: fills nothing in its query: Lumovi asks for it as listed` | A `{name}` in a path's query. Others say it has an empty query, or a character a query can't hold. |
| `spec.server.in: should be one of its own kinds, and …` | An `in` of a kind that isn't one of the plugin's own: the line names it |
| `spec.logs: should be shown or read, not "all"` | A value of `logs` that isn't one |
| `spec.logs: read needs Pods in spec.reads: its code reads the logs only of pods it was handed` | `logs: read` without Pods in `reads` |
| `spec.colour: isn’t something a plugin has` | A misspelled field of the plugin's |
| `metadata.labels: isn’t something a plugin has` | A key under `metadata` other than `name`, or one at the top beside `apiVersion`, `kind`, `metadata` and `spec` (`status: isn’t …`) |
| `spec.views[0].colums: isn’t something a view has` | A misspelled field of a view's |
| `spec.views[0].columns[0].path: is required` | A column without a path |
| `spec.views[0].icon: should be one of activity, archive, …` | An icon that isn't on the list |
| `spec.views[0].columns[1].path: “spec.ready” should start with a dot, like .spec.replicas` | A path that isn't one |
| `spec.views[0].kinds[0]: every kind (*) is of a group: name it` | `kind: '*'` without a `group` |
| `spec.views[1].kinds[0]: Database.platform.example.com is already shown by spec.views[0]` | Two views of one kind in a file |
| `spec.label: is longer than 80 characters` | A [text](#texts) over its length |
| `spec.label: holds a character that can’t be seen (U+202E)` | A text or a key with a character nobody can see |
| `spec.views[0].related[0]: needs labels or a fieldSelector to find them` | A related list that would be every object of its kind |
| `spec.views[0].actions[0]: needs a patch, or an object to create (not both)` | An action with neither, or both |
| `spec.views[0].actions[0]: patches are an object for type merge, and a list for type json` | A patch of the wrong shape |
| `spec.views[0].actions[0]: what it creates needs an apiVersion, a kind, and a metadata.name or generateName` | An incomplete object to create |
| `spec.views[0].actions[0]: what it creates: "platform.example.com/v1/x" should be an API version, like cert-manager.io/v1` | An `apiVersion` that isn't a group and a version |
| `spec.views[0].actions[0].create: makes a Job, which isn’t one of this plugin’s kinds` | An action that creates a kind the plugin doesn't show |
| `spec.views[0].actions[0]: can’t undo creating something` | An `undo` on an action that creates |
| `spec.views[0].actions[0]: {{ input.size }} isn’t one of its inputs` | A template naming an input the action doesn't have |
| `spec.views[0].actions[0]: runs at once, so it has to say so: runs: at-once` | An action that asks nothing first, without `runs: at-once` |
| `spec.views[0].actions[0]: asks first, so it can’t say runs: at-once` | `runs: at-once` on an action with a `confirm`, `inputs` or a `create` |
| `spec.views[0].actions[0]: writes the object’s status, so it has to ask first: give it confirm` | An action that writes `status` on the object itself, not through `subresource: status`, and asks nothing first |
| `spec.views[0].actions[0]: confirmTitle: only an action that asks first (with confirm or inputs) has one` | A `confirmTitle` or `confirmButton` on an action that runs at once |
| `spec.views[0].actions[0]: changes metadata.finalizers, which a plugin can’t change` | A patch or a new object that sets [what isn't a plugin's](#what-a-patch-can’t-change) |
| `spec.views[0].actions[0]: fills in an object’s metadata whole, which a plugin can’t: write it out` | `metadata` as one placeholder |
| `spec.views[0].actions[0]: fills in an operation whole, which a plugin can’t: write it out` | A JSON patch operation as one placeholder |
| `spec.views[0].actions[0]: needs each operation’s path written out, as text` | A JSON patch operation without a `path` that's text |
| `spec.views[0].actions[0]: fills in where an operation changes, which a plugin can’t: write its path out` | A `path` or `from` with a template in it |
| `spec.views[0].actions[0]: replaces all of an object’s metadata, which a plugin can’t change` | A JSON patch operation on `/metadata` |
| `spec.views[0].actions[0]: replaces the object whole, which a plugin can’t` | A JSON patch operation on the whole object |
| `spec.views[0].actions[0].inputs[0]: a choice needs options, or a path to read them from` | A choice without `options` or `from`, or with both |
| `spec.views[0].actions[0].inputs[0]: only a choice has options` | `options` or `from` on a text or number input |
| `It’s larger than 256 KB.` | A file over the limit, which isn't read |
| `It couldn’t be read: ENOENT: no such file or directory, open '…'` | A file that can't be read, like a link to nothing or to a folder. What follows says why, in the operating system's words. |

The rest are about a file beside another plugin:

| Message | Cause |
| - | - |
| `platform is platform.yaml’s name too` | A second file with a name another has. The first by file name is used |
| `Database.platform.example.com is already platform’s kind` | A second plugin of yours that shows a kind another does. The first by file name is used |
| `Certificate.cert-manager.io is cert-manager’s kind. A plugin of yours named cert-manager takes its place, all of it` | A plugin of yours that shows a kind one of Lumovi's has, under another name |
| `constraints.gatekeeper.sh is all gatekeeper’s. A plugin of yours named gatekeeper takes its place, all of it` | A plugin of yours with a kind in a group one of Lumovi's has whole |
| `argoproj.io is in argo-cd’s groups too, so this plugin can’t have every kind of it` | `kind: '*'` for a group another plugin has kinds in |

What a problem quotes from a file is cut at 40 characters, and a character that can't be seen in it is shown as its code point.

A file that isn't valid YAML shows the parser's message. One that can't be read as YAML for any other reason is listed as one that couldn't be read, with the reason, and the others load.

When a plugin of yours with a problem has the name of one of Lumovi's, Lumovi's is what's shown, and the file's row says so: **Lumovi’s Flux is shown instead**. It's said only where the file's name for itself could be read.

**API resources** says only that there are problems, **A plugin couldn’t be used.** or how many, with **See Plugins**; and at its foot how many plugins are Lumovi's and how many are yours, and where yours go, with **How to write a plugin**.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.