{ "schemaVersion": 1, "files": [ { "path": "SKILL.md", "content": "---\nname: homarr-custom-widget\ndescription: Author safe Homarr Custom JSX v2 widgets from API documentation. Use when creating or fixing Homarr custom widgets, including multi-source dashboards, queries, actions, options, temporary inputs, and Mantine JSX.\nlicense: Apache-2.0\nmetadata:\n version: \"2.0.0\"\n---\n\n# Homarr Custom Widget\n\nCreate one widget at a time. Repeat the workflow when the user asks for several widgets; do not invent packs or shared connections.\n\n## Choose the authoring reference\n\nWhen connected to Homarr, retrieve the live widget schema and component catalog before authoring. Live metadata is authoritative because Homarr and Mantine versions can differ between installations.\n\nFor offline authoring, read these installed, release-matched references in order:\n\n1. [Schema](references/schema.md)\n2. [JSX runtime](references/runtime.md)\n3. [Security boundary](references/security.md)\n4. [Component catalog](references/components.md)\n5. [Mantine Core](references/mantine-core.md)\n6. [Mantine Dates](references/mantine-dates.md)\n7. [Mantine Charts](references/mantine-charts.md)\n8. [Homarr components](references/homarr-components.md)\n\nTogether, this file and those references are the complete offline authoring bundle for the matching Homarr release. Do not require MCP tools, repository access, or external component documentation to produce a best-effort widget. If live metadata is available, prefer it over the installed static bundle.\n\n## Authoring workflow\n\n1. Read the API documentation supplied by the user. Ask for a missing base URL only when it cannot be inferred safely.\n2. Build one credential-free `homarr-custom-widget-v2` manifest and one safe JSX template.\n3. Use immutable local `const` blocks for repeated derived values and `RecursiveList` for arbitrary-depth trees. Do not emulate either feature with unrestricted JavaScript or authored recursion.\n4. Return the complete manifest and JSX in the exact fenced-block format requested by the authoring prompt.\n5. When the user supplies Homarr diagnostics, correct every reported issue while preserving unrelated fields.\n\n## Output rules\n\n- Use inline sources, named requests, definition-owned options, temporary bound inputs, and one safe JSX template.\n- Use `kind`, not the HTTP method, to distinguish reads from user-triggered actions.\n- Give every declared request parameter an explicit source. Load queries use `optionsBinding` with an option reference or primitive literal; manual queries and actions receive values only from the invoking component's `params` prop. Never infer bindings from matching names.\n- Never put a credential in JSX, options, headers, URLs, examples, exports, logs, or chat output.\n- Use declarative `bind` controls and Homarr request components. Controlled collection callbacks, zero-argument derived-value IIFEs, and the `RecursiveList` child template may use the restricted safe-block grammar documented in the runtime reference. Never emit imports, hooks, refs, raw callback props, browser requests, or arbitrary JavaScript.\n- Include responsive loading, empty, error, and success states.\n- Keep actions explicit, permission-scoped, confirmed when destructive, and followed by targeted invalidation.\n" }, { "path": "references/schema.md", "content": "# Custom Widget v2 schema\n\nEvery import and export is one object with `$schema: \"homarr-custom-widget-v2\"`, metadata, `sources`, `requests`, `optionsSchema`, `defaultOptions`, and `template`. Secrets are accepted separately by create/update/preview operations and never exported.\n\nThe safe-local-binding and `RecursiveList` expansion changes only template authoring. It does not add database fields, request fields, persistence, or another widget schema version.\n\n```ts\ninterface HomarrCustomWidgetV2 {\n $schema: \"homarr-custom-widget-v2\";\n name: string;\n description?: string;\n iconUrl?: string;\n sources: CustomWidgetSource[];\n requests: CustomWidgetRequest[];\n optionsSchema: CustomWidgetOptionsSchema;\n defaultOptions: Record;\n template: string;\n}\n```\n\nA source has a stable lowercase `id`, display `name`, `baseUrl`, `networkScope` (`public`, `private`, or `loopback`), and one auth mode: `none`, `bearer`, `basic`, `apiKeyHeader`, or `apiKeyQuery`.\n\nA request has `id`, `sourceId`, `kind`, any supported HTTP `method`, same-origin `pathTemplate`, declared primitive `parameters`, optional `optionsBinding`, `queryTemplate`, and `bodyTemplate`, optional safe `staticHeaders`, `auth`, `minimumBoardPermission`, optional `trigger`, caching, confirmation, and invalidation. Parameter names may use camelCase. Reference a declared parameter with `{name}` in a path or `{ \"$param\": \"name\" }` in structured query/body data.\n\nEvery declared parameter needs an explicit source. For a load query, bind each parameter in `optionsBinding` with `{ \"$option\": \"optionName\" }` or a primitive literal of the declared type. The referenced option must exist in `optionsSchema`. For manual queries and actions, the invoking `SubFetch`, `ActionButton`, or `ToggleSwitch` supplies the complete parameter object through `params`. Never infer a source because a parameter, option, or temporary input has the same name.\n\nDynamic option requests have no JSX invoker and therefore also require `optionsBinding` for every parameter they declare.\n\nOptions use a restricted object JSON Schema. Set `additionalProperties: false`. Presentation metadata lives under `x-homarr`; dynamic selects use `optionsSource.requestId`, optional `itemsPath` for wrapped arrays, `valuePath`, and `labelPath`. Default options must validate against the schema.\n\nOption and bind names start with a letter and use letters, numbers, `-`, or `_`; camelCase is supported. Every JSX `bind` value is a literal temporary input name.\n\nDo not model credentials as options or static defaults. Credential-like option names and values are rejected; source authentication is configured separately.\n\nDo not add `stateSchema`, `defaultState`, scripts, imports, or component declarations to the manifest. Temporary interaction values come only from JSX `bind` controls and are exposed through `inputs`. Derived values declared with safe `const` blocks exist only while evaluating the template.\n\nThis reference is sufficient for offline authoring against the matching release. When connected, retrieve the live Custom Widget schema; it supersedes this static copy if the installed Homarr version differs.\n" }, { "path": "references/runtime.md", "content": "# JSX runtime\n\nTemplates read four roots: `data.`, `status.`, `options`, and `inputs`.\n\nUse Mantine Core, Dates, Charts, safe Tabler icons, and Homarr runtime components. Safe installed Mantine exports are discovered automatically. Ordinary serializable props and Mantine style props pass through; `style` and `styles` are scoped and sanitized.\n\nRender icons as ``. When connected, retrieve `homarr://custom-widgets/components/TablerIcon` for the supported icon names.\n\nUse `SubFetch` for a manual query, `ActionButton` or `ToggleSwitch` for an action, and `RefreshButton` to refresh data. A query with `trigger: \"load\"` populates its data automatically and follows the widget refresh interval.\n\nManual requests receive values only from the invoking component:\n\n```jsx\n\nRestart\n```\n\nLoop variables and other JSX expressions can flow into a request only through that `params` prop. A load query has no invoking component, so every declared parameter must have an `optionsBinding` entry:\n\n```json\n{\n \"parameters\": { \"endpointId\": \"string\", \"showAll\": \"boolean\" },\n \"optionsBinding\": {\n \"endpointId\": { \"$option\": \"endpointId\" },\n \"showAll\": false\n }\n}\n```\n\nDo not rely on parameter and option names matching. For an invalid-placeholder or unresolved-parameter error on a load query, check missing `optionsBinding` entries before changing placeholder syntax.\n\nBind local controls declaratively:\n\n```jsx\n\n\n