feat(custom-widgets): complete JSX v2
This commit is contained in:
263 files changed
+65503
-17816
No files matched your search
@@ -108,3 +108,4 @@ The advanced deployments environment variables should only be used if you know w
|
||||
| Environment Variable | Description |
|
||||
| ------------------------ | -------------------------------------------------- |
|
||||
| `DB_MIGRATIONS_DISABLED` | Disable db migrations. For example for helm charts |
|
||||
| `NEXT_PUBLIC_WORKSHOP_API_URL` | Build-time public origin for a separately hosted Workshop PocketBase service. Defaults to `https://homarr.dev`. |
|
||||
@@ -27,11 +27,11 @@ So make sure to manually whitelist this address.
|
||||
|
||||
### Can I add my own widgets? Do I need programming skills?
|
||||
|
||||
Yes. Administrators can create [Custom Widgets](/docs/management/custom-widgets) that read an HTTP API and render the response with a built-in display type. These display types require no programming.
|
||||
Yes. [Custom Widgets](/docs/management/custom-widgets) can read public or private HTTP APIs, render responsive Mantine JSX, expose per-board options, and run permission-checked actions. Custom JSX v2 is the only custom-widget format.
|
||||
|
||||
Custom JSX is available when you need a more specific layout or an interactive control. It uses a restricted declarative JSX interpreter with a classified Mantine component set. Network operations are declared as named, same-origin requests and are checked against board permissions on the server. It does not execute arbitrary JavaScript or expose browser `fetch`.
|
||||
You can author the JSON and JSX directly, use the one-click prompt with ChatGPT or another assistant, or let an MCP agent validate and preview a widget before saving it. The workbench parses a pasted AI response, shows request capabilities, and produces redacted diagnostics for another iteration.
|
||||
|
||||
You should be comfortable reading JSON and JSX to author complex templates. The management workbench includes examples, diagnostics, a component reference, API response inspection, and a prompt you can copy into an AI assistant. Always review imported templates and request capabilities before saving them.
|
||||
The template is declarative: it cannot import packages, execute arbitrary JavaScript, or call browser `fetch`. Network operations are named, same-origin server requests protected by Homarr's board permissions and network policy. You should still review origins, methods, permissions, and actions before installing or publishing a widget.
|
||||
|
||||
### Is it secure to expose Homarr to the web?
|
||||
|
||||
|
||||
@@ -83,3 +83,12 @@ The OpenAPI and MCP APIs include board management endpoints for common automatio
|
||||
- Configure global board defaults such as public home boards and status behavior
|
||||
|
||||
Use the interactive API reference to inspect the exact request body for each endpoint under the `boards` and `settings` tags.
|
||||
|
||||
## Custom Widget Authoring Resources
|
||||
|
||||
Custom JSX v2 has two stable HTTP discovery endpoints:
|
||||
|
||||
- `GET /api/custom-widgets/authoring-prompt` returns the concise current authoring prompt and live resource identifiers.
|
||||
- `GET /api/custom-widgets/skill` returns the Agent Skill name, version, `SKILL.md`, archive URL, and SHA-256.
|
||||
|
||||
The short-lived credential entry route is generated by `customWidget_secretRequestUser`; do not construct or persist those URLs yourself. Definition CRUD, validation, previews, template patches, and secret writes are exposed through authenticated tRPC and MCP procedures. Exported definitions never include credentials.
|
||||
@@ -1,26 +1,30 @@
|
||||
---
|
||||
sidebar_label: AI and MCP authoring
|
||||
---
|
||||
# Agents and downloadable skill
|
||||
|
||||
# AI and MCP authoring
|
||||
An external agent creates widgets one at a time through Homarr's MCP server. The harness owns any multi-widget task list; Homarr does not create packs or multi-widget drafts.
|
||||
|
||||
Homarr exposes an iterative, validation-first workflow to authenticated administrators. Agents should continue until validation, preview, and diagnostics are clean; they must not invent inline network access.
|
||||
<a className="button button--primary" href="/downloads/homarr-custom-widget-2.0.0.zip" download>
|
||||
Download Custom Widget Skill
|
||||
</a>
|
||||
|
||||
## Recommended loop
|
||||
The archive follows the Agent Skills format and includes `SKILL.md` plus schema, runtime, and security references. Its SHA-256 is `5571b74b2faafe8a9e230231c1915193e889c7afa95acef38a1f70befe14a9e1`.
|
||||
|
||||
1. Read `customWidget_schema` and the generated component/capability reference.
|
||||
2. Read an existing definition with `customWidget_byId`, or create a v3 draft.
|
||||
3. Call `customWidget_validate`; fix every issue using its exact path.
|
||||
4. Call `customWidget_preview` for the base GET and retain the preview session ID.
|
||||
5. Test each named query with `customWidget_previewQuery`.
|
||||
6. Dry-run every action with `customWidget_simulatePreviewAction`.
|
||||
7. Inspect `customWidget_previewJournal` for status, duration, and method coverage.
|
||||
8. Revalidate, then call import/create or a focused update/patch operation.
|
||||
## MCP discovery
|
||||
|
||||
Stored secret values are never readable. Omitting secrets during an update preserves them. MCP actions are simulation-only; enabling live preview actions remains a browser-only administrator decision.
|
||||
- Prompt: `homarr-custom-widget-author`
|
||||
- Resources: `homarr://custom-widgets/schema`, `components`, `components/{name}`, `examples/{name}`, and `skill`
|
||||
- HTTP: `GET /api/custom-widgets/authoring-prompt` and `GET /api/custom-widgets/skill`
|
||||
|
||||
## Template format
|
||||
The authoring-prompt tool description intentionally contains only one sentence. Workflow instructions live in the prompt and link to resources.
|
||||
|
||||
Use multiline JSX when reading or patching templates. Prefer focused template read/write/patch tools over rewriting an escaped one-line JSON string. When a complete portable artifact is needed, use the authoring bundle placeholder plus a separate fenced JSX block.
|
||||
## Agent loop
|
||||
|
||||
The AI prompt is generated from the same registry, examples, request rules, and limits as the validator. Agents should still trust validation output over prompt memory because the installed Homarr version is authoritative.
|
||||
1. Fetch the prompt, live schema, and component catalog.
|
||||
2. Read the external API documentation.
|
||||
3. Build and validate one definition without saving.
|
||||
4. Create a preview session and test its queries/actions.
|
||||
5. Open the returned preview URL with browser tools.
|
||||
6. Set a supplied credential with the dedicated secret-write permission, or ask the user through the ten-minute Homarr credential URL.
|
||||
7. Inspect the redacted journal and patch until valid.
|
||||
8. Create the widget, then repeat for the next requested widget.
|
||||
|
||||
Credential URLs are scoped to one definition/preview, source, and required fields. The agent receives only pending/completed status. Plaintext is encrypted immediately and never returned.
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
sidebar_label: Maintainer architecture
|
||||
---
|
||||
|
||||
# Maintainer architecture
|
||||
|
||||
Reusable Custom Widget behavior belongs to `@homarr/custom-widgets`:
|
||||
|
||||
- `core` owns schemas, descriptors, migrations, import/export, examples, diagnostics metadata, and prompt generation.
|
||||
- `jsx` owns AST parsing, shared policy, interpreter, bindings, registry, wrappers, and analysis.
|
||||
- `runtime` owns renderers and named-request controls through injected ports.
|
||||
- `server` owns manifests, network execution, caching, limits, preview sessions, and typed domain errors.
|
||||
- `workbench` owns form contracts, editor analysis, preview UI, response tree, and import review through injected ports.
|
||||
|
||||
The API package retains persistence and board authorization adapters. Widgets retain board registration and runtime transport wiring. Next.js routes retain navigation and shells. The domain package must never import API, widgets, or route modules.
|
||||
|
||||
## Add a display type
|
||||
|
||||
Extend the typed descriptor and discriminated schema, defaults/hydration/serialization, renderer, workbench section, preview, import round-trip fixtures, docs, and translations. Create, edit, import, export, MCP, and preview must all use the same transform.
|
||||
|
||||
## Add a JSX component
|
||||
|
||||
Classify the upstream export, document safety mode and supported props, add a wrapper if behavior needs constraining, and update registry tests. CI fails when a Mantine upgrade introduces an unclassified export.
|
||||
|
||||
## Change networking
|
||||
|
||||
Implement policy below the framework-neutral server boundary and expose infrastructure through a typed port. Add tests for permissions, typed substitution, DNS classes, redirects, headers, timeouts, limits, redaction, cache behavior, and action non-retry semantics before wiring the tRPC adapter.
|
||||
|
||||
Production files target 300 lines. Algorithmic AST/network modules may use a documented 400-line ceiling. Dependency-boundary, cycle, file-size, translation, generated-artifact, component-classification, focused unit, integration, E2E, accessibility, and docs-build gates protect the architecture. After a Next.js production build, `pnpm check:custom-widget-bundle` also enforces a 768 KiB gzip ceiling across the client chunks referenced by Custom JSX.
|
||||
@@ -1,36 +1,34 @@
|
||||
---
|
||||
sidebar_label: Authoring workbench
|
||||
---
|
||||
# Workbench and ChatGPT
|
||||
|
||||
# Authoring workbench
|
||||
The desktop workbench keeps code and preview side by side. Mobile uses Configure and Preview tabs. Save and preview actions stay available while the form scrolls.
|
||||
|
||||
The Custom Widget form is a continuous workflow: choose a display, configure the HTTP connection, configure the display, then test it in the adjacent preview. Each section stays visible so you can revisit earlier choices without losing context.
|
||||
CodeMirror edits requests, options, state, and JSX. The editor supplies installed Mantine components, request/source IDs, option/state names, runtime components, and icons. The request toolbar jumps to a declaration and atomically renames its invalidation, dynamic-option, and JSX references. Diagnostics are copyable and secret-free.
|
||||
|
||||
## Choose a display
|
||||
## Manual workflow
|
||||
|
||||
The display grid is the first product decision. Built-in displays are best for common metrics, tables, statuses, and actions. Choose Custom JSX when the layout needs multiple Mantine components, conditional presentation, charts, dates, or named requests.
|
||||
1. Add the API source and select its network scope and authentication mode.
|
||||
2. Enter credentials. Existing values are shown only as configured; plaintext is never revealed.
|
||||
3. Declare named load/manual queries and actions.
|
||||
4. Define item options and local state.
|
||||
5. Write JSX using `data`, `status`, `options`, and `state`.
|
||||
6. Test requests, preview compact/standard/wide layouts, and inspect runtime and accessibility diagnostics.
|
||||
7. Save, then select the definition and its options in the board widget picker.
|
||||
|
||||
Changing display type preserves shared connection fields and initializes safe display defaults. A Custom JSX template is never blank; new and migrated forms receive the tested starter fixture.
|
||||
## ChatGPT copy/paste
|
||||
|
||||
## Test and preview
|
||||
Enter a short request and optional API documentation URL, then select **Copy AI prompt**. Paste it into ChatGPT or another model. The model must return exactly:
|
||||
|
||||
The preview has four tabs:
|
||||
1. A fenced `json` block with one complete widget and `template: "__HOMARR_TEMPLATE__"`.
|
||||
2. A fenced `jsx` block with the readable template.
|
||||
|
||||
- **Widget** renders at compact, standard, or wide board-like sizes.
|
||||
- **Response** uses a tree to inspect data, copy paths, and insert paths into JSX. The external-response action opens a temporary local blob and never publishes the response.
|
||||
- **Request** shows the redacted base request, named capabilities, status, duration, and preview journal.
|
||||
- **Diagnostics** shows template size, named requests, network scope, and blocking or advisory issues.
|
||||
Use **Paste AI response** to fill the workbench. Enter credentials yourself, test, and preview. If validation or a request fails, select **Copy diagnostics for AI** and paste the correction back into Homarr.
|
||||
|
||||
Only GET base requests run automatically. Preview actions are simulated until an administrator explicitly enables live actions for the five-minute preview session. DELETE still requires confirmation.
|
||||
The prompt is under 8,000 characters. It includes the installed Mantine version and capability rules but not a complete prop list, MCP workflow, server URL, or credentials.
|
||||
|
||||
## Keyboard and clipboard
|
||||
## Options
|
||||
|
||||
The JSX and JSON editors support native Ctrl/Cmd+Z and redo, plus visible toolbar controls. Cursor position, character count, and the 50,000-character limit remain visible.
|
||||
Options use a restricted object JSON Schema. Supported controls include text, textarea, number, switch, select, multi-select, slider, date, time, color, URL, nested groups, conditions, and advanced JSON.
|
||||
|
||||
On the list page, paste a complete v3 export with Ctrl+V or Command+V to import it as new. On an edit page, pasting opens a replacement review. Nothing is applied before the origin, methods, credential inheritance, and permissions are shown.
|
||||
Credentials are not widget options. Credential-like option names and static default values are rejected; configure authentication under the widget's API source so values stay encrypted and out of exports and board configuration.
|
||||
|
||||
For AI output, prefer the authoring bundle format: a JSON block containing `"template": "__HOMARR_TEMPLATE__"`, followed by a JSX block. This keeps multiline JSX readable and avoids JSON escape errors.
|
||||
|
||||
## Save behavior
|
||||
|
||||
Errors block save; warnings do not. Server validation runs again on every create or update. Leaving with dirty changes triggers protection. Secrets omitted during edit remain unchanged, and secrets are never exported.
|
||||
Dynamic selects use `x-homarr.optionsSource` and a named query. Add `itemsPath` when the response wraps its result array, then use `valuePath` and `labelPath` relative to each item. These requests run only while configuring the item and use the same protected server executor as widget requests.
|
||||
@@ -1,49 +1,59 @@
|
||||
---
|
||||
sidebar_label: Custom JSX language
|
||||
---
|
||||
# Custom JSX runtime
|
||||
|
||||
# Custom JSX language
|
||||
Templates are declarative JSX interpreted inside a widget-local error boundary. They cannot import modules or run arbitrary JavaScript.
|
||||
|
||||
Custom JSX is a declarative expression language, not browser JavaScript. Homarr parses an explicit AST subset and emits React elements from a classified component registry. Templates cannot access browser globals, native prototypes, constructors, refs, portals, raw event handlers, or direct `fetch`.
|
||||
|
||||
## Data and rendering
|
||||
|
||||
The base GET response is available as `data`. Safe member access, conditionals, arrays, object literals, and interpreter-created collection callbacks can compose displays:
|
||||
## Data roots
|
||||
|
||||
```jsx
|
||||
<Stack gap="xs">
|
||||
{data.services
|
||||
.filter((service) => service.visible)
|
||||
.map((service) => (
|
||||
<Group key={service.id} justify="space-between">
|
||||
<Text>{service.name}</Text>
|
||||
<Badge color={service.online ? "green" : "red"}>{service.status}</Badge>
|
||||
</Group>
|
||||
))}
|
||||
<Stack>
|
||||
<TextInput bind="search" label="Search TV shows" />
|
||||
<SubFetch requestId="search-series" params={{ query: state.search }} />
|
||||
{data["search-series"]?.map((series) => (
|
||||
<Group key={series.tvdbId}>
|
||||
<Text>{series.title}</Text>
|
||||
<ActionButton requestId="monitor-series" params={{ tvdbId: series.tvdbId }}>
|
||||
Monitor
|
||||
</ActionButton>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
```
|
||||
|
||||
Properties are resolved own-only. Reflective names such as `constructor`, `prototype`, `__proto__`, `caller`, `callee`, `arguments`, `bind`, `call`, and `apply` are rejected even when computed or Unicode escaped.
|
||||
- `data.<requestId>` contains sanitized JSON or text results.
|
||||
- `status.<requestId>` contains loading, success, HTTP, and error state.
|
||||
- `options` contains the validated board-item configuration.
|
||||
- `state` contains browser-session local state.
|
||||
|
||||
## Components and props
|
||||
Load-triggered queries populate data automatically and follow the widget refresh interval. `SubFetch` runs a manual query. `ActionButton` and `ToggleSwitch` invoke actions.
|
||||
|
||||
Every Mantine export is classified as allowed, wrapped, or denied. The [generated component reference](./#component-reference) is authoritative. Props are allowlisted per component; unsupported props produce warnings and are ignored. Polymorphic roots, unsafe URLs, portals, positioning escapes, refs, and function props are stripped.
|
||||
## Mantine capabilities
|
||||
|
||||
`SubData` is a convenience for simple response paths. Prefer the render-child pattern for rich nested results:
|
||||
Homarr discovers exports from the installed Mantine Core, Dates, and Charts packages and generates prop metadata from their installed TypeScript definitions. Ordinary serializable props pass through. Style props, `style`, and `styles` are sanitized and scoped to the tile. Safe new Mantine exports become available without a hand-maintained allowlist; provider, portal, app-shell, modal, drawer, file, and low-level callback families are classified by the capability denylist.
|
||||
|
||||
Use safe Tabler icons through the resolver, for example `<TablerIcon name="server" size={18} />`. The editor completes installed icon names, and `homarr://custom-widgets/components/TablerIcon` exposes the same names to MCP agents.
|
||||
|
||||
The runtime blocks imports, hooks, refs, callbacks, polymorphic roots, provider replacement, arbitrary portal targets, browser networking, npm packages, evaluation, prototype-pollution keys, unsafe URLs, global selectors, and escaping CSS.
|
||||
|
||||
## Bindings
|
||||
|
||||
Declare state types and defaults, then bind controls:
|
||||
|
||||
```jsx
|
||||
<SubFetch requestId="detail" params={{ id: data.selectedId }}>
|
||||
{(detail, meta) => (
|
||||
<Stack>
|
||||
<Title>{detail.name}</Title>
|
||||
<Text>HTTP {meta.status}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</SubFetch>
|
||||
<TextInput bind="search" />
|
||||
<Calendar bind="selectedDate" />
|
||||
<Switch bind="enabled" />
|
||||
<MultiSelect bind="selectedEntities" data={options.entities} />
|
||||
```
|
||||
|
||||
## Safe helpers and budgets
|
||||
Homarr adapts ordinary `value/onChange`, `checked/onChange`, `active/onChange`, and `opened/onChange` patterns plus documented unusual component signatures. State supports primitive values and `string[]`, `number[]`, or `date[]` for multi-value controls. Authored callbacks are not allowed.
|
||||
|
||||
Bindings include restricted conversion, string, array, object, JSON, math, URI, and pure date helpers. Native `Date` objects and native prototypes are not exposed.
|
||||
## Diagnostics
|
||||
|
||||
Templates are limited to 50,000 characters, 10,000 rendered nodes, and explicit AST-depth, operation, collection, and string budgets. Budget errors stop only the Custom JSX renderer and are contained by its error boundary.
|
||||
- `BLOCKED_CAPABILITY`: unsafe behavior; saving fails.
|
||||
- `UNKNOWN_COMPONENT`: the component does not exist; saving fails.
|
||||
- `UNKNOWN_MANTINE_PROP`: advisory; the prop is passed through, not discarded.
|
||||
- `INVALID_PROP_VALUE`: warning or error depending on safety.
|
||||
- `BINDING_UNAVAILABLE`: the component renders, but `bind` is unavailable.
|
||||
- `RUNTIME_RENDER_ERROR`: rendering failed inside this tile only.
|
||||
|
||||
Homarr never silently drops an ordinary prop.
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
sidebar_label: Extension recipes
|
||||
---
|
||||
|
||||
# Custom Widget extension recipes
|
||||
|
||||
These recipes keep every authoring and runtime path on the same typed source of truth. Run `pnpm check:custom-widgets` after each structural change and add focused package tests before changing an adapter.
|
||||
|
||||
## Add a built-in display type
|
||||
|
||||
1. Add the discriminated configuration schema in `@homarr/custom-widgets/core`.
|
||||
2. Add one descriptor with its icon, category, localized metadata keys, defaults, hydration, serialization, and validation.
|
||||
3. Add the renderer to the runtime display map. It must consume normalized display data and injected actions only.
|
||||
4. Add a bounded workbench section that edits the descriptor-owned form fields.
|
||||
5. Add one fixture and assert create, edit, v2/v3 import, export, MCP, and preview round trips against it.
|
||||
6. Add translation keys and update the display table in the user guide.
|
||||
|
||||
Do not add route-specific conversion code. `buildDisplayFormValues` and `buildDisplayConfigFromFormValues` are the only form conversion boundary.
|
||||
|
||||
## Add or update a JSX component
|
||||
|
||||
1. Classify the Mantine export as `allowed`, `wrapped`, or `denied` in the component registry. Denied entries require a reason.
|
||||
2. Declare supported props and subcomponents. Function props, refs, portals, polymorphic roots, and unsafe positioning stay denied unless a wrapper removes the unsafe behavior.
|
||||
3. Implement wrappers in the JSX package and register them through the package runtime-component factory.
|
||||
4. Add analyzer and renderer tests for accepted props, stripped props, reflective-property attacks, and the rendered-node budget.
|
||||
5. Regenerate the docs reference and verify the editor completion and AI prompt include the same entry.
|
||||
|
||||
The component-classification test must pass against the locked Mantine version. Never expose an upstream namespace or native prototype as a shortcut.
|
||||
|
||||
## Add a named-request capability
|
||||
|
||||
1. Extend the request schema and manifest renderer in `core` and `server` without accepting a raw runtime URL, method, header, or body.
|
||||
2. Enforce permission, network scope, redirect, DNS, header, body, response, concurrency, and rate policies in the framework-neutral server service.
|
||||
3. Expose infrastructure through a typed server port. Translate domain errors only in the API adapter.
|
||||
4. Add the control to runtime through the injected request port. Queries use query semantics; actions require a gesture and use mutation semantics.
|
||||
5. Add preview simulation and redacted-journal coverage. Live actions remain an explicit administrator-only browser choice.
|
||||
6. Update MCP validation and simulation tools without exposing stored secrets or unrestricted action execution.
|
||||
|
||||
Tests must prove actions are never mounted automatically, polled, retried, cached, or redirected. DELETE must retain full permission and confirmation.
|
||||
|
||||
## Add a workbench feature
|
||||
|
||||
1. Put reusable state, analysis, or presentation in `@homarr/custom-widgets/workbench`.
|
||||
2. Receive localized messages and typed callbacks as props. The package cannot import the tRPC client, Next.js navigation, notifications, or Homarr widget adapters.
|
||||
3. Keep the Next.js component as a transport and page-shell adapter.
|
||||
4. Cover keyboard operation, focus order, reduced motion, 320/768/desktop layouts, dirty-state behavior, and error-boundary recovery.
|
||||
5. Keep each production module below 300 lines and preserve the continuous two-pane form behavior.
|
||||
|
||||
## Change examples or AI authoring
|
||||
|
||||
Examples live as typed shared fixtures. Tests, snippets, documentation data, and the AI prompt consume those fixtures. AI output uses an authoring bundle: a JSON block with `__HOMARR_TEMPLATE__`, followed by a readable multiline JSX block. Do not require agents to escape an entire template into one JSON string.
|
||||
|
||||
After changing a fixture, validate every example, render it, regenerate the component reference, and run the prompt snapshot tests. MCP agents must be able to read a widget, patch template lines with a revision, validate, preview named queries, simulate actions, inspect the journal, and update the definition without receiving secret values.
|
||||
|
||||
## Required checks
|
||||
|
||||
```bash
|
||||
pnpm check:custom-widgets
|
||||
pnpm test
|
||||
pnpm test:e2e
|
||||
pnpm turbo typecheck --affected
|
||||
pnpm turbo lint --affected
|
||||
pnpm turbo build --filter=@homarr/docs
|
||||
```
|
||||
|
||||
Also run the component-classification, generated-reference drift, bundle-budget, accessibility, and focused package/API suites configured by the repository. A change is incomplete if any create, edit, import, export, MCP, preview, or board-runtime path uses a separate transform.
|
||||
@@ -1,204 +1,49 @@
|
||||
---
|
||||
tags:
|
||||
- Custom Widgets
|
||||
- API
|
||||
- Widgets
|
||||
- JSX
|
||||
tags: [Custom Widgets, API, JSX, MCP]
|
||||
---
|
||||
|
||||
import { CustomJsxComponentReference } from "@site/src/components/custom-jsx-component-reference";
|
||||
|
||||
# Custom Widgets
|
||||
|
||||
Custom widgets connect a dashboard item to an HTTP API without compiling a new Homarr widget. Administrators choose the endpoint and authentication, then use a built-in display type or a Custom JSX template to render the response.
|
||||
Custom JSX v2 is Homarr's only custom-widget format. A definition owns its metadata, inline API sources, named requests, options, local state, and safe JSX template. Credentials belong to a source but are encrypted separately and never exported.
|
||||
|
||||
## Guides
|
||||
There are no legacy display types, reusable connections, packs, or compatibility modes.
|
||||
|
||||
- [Authoring workbench](./authoring): create, edit, preview, paste, undo, import, and export.
|
||||
- [Custom JSX language](./custom-jsx): supported syntax, components, bindings, diagnostics, and limits.
|
||||
- [Named requests and security](./requests-and-security): permissions, network scopes, request manifests, and server protections.
|
||||
- [AI and MCP authoring](./agent-authoring): an iterative agent workflow that validates and previews before saving.
|
||||
- [Compatibility and migration](./migration): v1/v2 JSX and v2/v3 import behavior.
|
||||
- [Troubleshooting](./troubleshooting): common editor, preview, permission, and networking failures.
|
||||
- [Maintainer architecture](./architecture): package boundaries, extension points, and required tests.
|
||||
- [Extension recipes](./extension-recipes): step-by-step changes for displays, JSX components, requests, and workbench features.
|
||||
## Start here
|
||||
|
||||
:::caution
|
||||
- [Workbench and ChatGPT](./authoring): create widgets manually or use the copy/paste AI loop.
|
||||
- [JSX runtime](./custom-jsx): data roots, Mantine components, bindings, and diagnostics.
|
||||
- [Requests and security](./requests-and-security): sources, actions, permissions, and network protections.
|
||||
- [Agents and downloadable skill](./agent-authoring): MCP prompt/resources/tools and the portable Agent Skill.
|
||||
- [Troubleshooting](./troubleshooting): fix validation, request, configuration, and runtime failures.
|
||||
|
||||
A custom widget can expose data or perform actions with the credentials configured for it. Import definitions only from sources you trust and review every network capability before using the widget on a shared board.
|
||||
## One definition
|
||||
|
||||
:::
|
||||
|
||||
## Create a widget
|
||||
|
||||
Open **Management → Custom Widgets**, select **Add**, and configure:
|
||||
|
||||
- **Name, description, and icon**: identify the widget in management and the widget picker.
|
||||
- **URL**: the full HTTP or HTTPS base endpoint.
|
||||
- **Authentication**: none, bearer token, basic authentication, API-key header, or API-key query parameter.
|
||||
- **Display type**: a built-in renderer or Custom JSX.
|
||||
|
||||
Use **Test** to run the current GET request. The preview workbench separates the rendered widget, sanitized response tree, redacted request journal, and diagnostics. It marks sample data as stale whenever the connection or display configuration changes after a test.
|
||||
|
||||
Testing creates a five-minute, administrator-bound preview session for unsaved named queries. Action controls are simulated by default. An administrator can explicitly enable live actions for that session; DELETE still requires confirmation.
|
||||
|
||||
## Display types
|
||||
|
||||
| Display type | Use it for |
|
||||
| ---------------- | --------------------------------------------------------- |
|
||||
| Single Value | One metric with a label and unit |
|
||||
| Key Value | Several labeled values |
|
||||
| Table | Arrays of records |
|
||||
| Stat Grid | Compact colored metrics |
|
||||
| Progress Bars | Capacity, quota, and completion |
|
||||
| Status Indicator | Health and availability |
|
||||
| Count Grid | Compact counters |
|
||||
| Raw | Sanitized JSON inspection |
|
||||
| Action Button | One explicit API action |
|
||||
| Custom JSX | A declarative Mantine layout with optional named requests |
|
||||
|
||||
## Custom JSX workbench
|
||||
|
||||
The JSX editor provides syntax highlighting, formatting, undo/redo buttons and Ctrl/Cmd+Z shortcuts, a searchable component reference, character and cursor status, and diagnostics. Errors block saving; warnings identify behavior that still receives server-side validation. Templates can contain up to 50,000 characters and render up to 10,000 nodes.
|
||||
|
||||
The template reads the base GET response from `data`:
|
||||
|
||||
```jsx
|
||||
<Stack gap="xs" p="sm">
|
||||
<Text fw={600}>{data.name}</Text>
|
||||
<Badge color={data.online ? "green" : "red"}>{data.status}</Badge>
|
||||
</Stack>
|
||||
```
|
||||
|
||||
The **Copy AI prompt** action is available on desktop and mobile. Its prompt, component list, and examples are generated from the same metadata used by the renderer, so the reference stays aligned with the installed Homarr version.
|
||||
|
||||
### JSX API versions
|
||||
|
||||
- **Version 2** is the default. Network access is declared in a named-request manifest and enforced by the server.
|
||||
- **Version 1** remains available for display-only templates. Inline network components are disabled until the definition is reviewed and migrated.
|
||||
|
||||
Definitions without `jsxApiVersion` are treated as version 1. Display-only templates can be migrated by selecting version 2 and saving an empty request list.
|
||||
|
||||
## Named requests
|
||||
|
||||
Version 2 templates never choose an arbitrary URL, method, header, or body at runtime. An administrator declares each allowed operation once, and the template references its `requestId` with typed parameters.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "customJsx",
|
||||
"jsxApiVersion": 2,
|
||||
"networkScope": "public",
|
||||
"template": "<SubFetch requestId=\"service-detail\" params={{ id: data.id }}><SubData path=\"status\" as=\"Badge\" /></SubFetch>",
|
||||
"requests": [
|
||||
{
|
||||
"id": "service-detail",
|
||||
"kind": "query",
|
||||
"method": "GET",
|
||||
"pathTemplate": "/api/services/{id}",
|
||||
"parameters": { "id": "string" },
|
||||
"auth": "inherit",
|
||||
"minimumBoardPermission": "view",
|
||||
"cacheTtlSeconds": 30
|
||||
}
|
||||
]
|
||||
```ts
|
||||
interface HomarrCustomWidgetV2 {
|
||||
$schema: "homarr-custom-widget-v2";
|
||||
name: string;
|
||||
description?: string;
|
||||
iconUrl?: string;
|
||||
sources: CustomWidgetSource[];
|
||||
requests: CustomWidgetRequest[];
|
||||
optionsSchema: CustomWidgetOptionsSchema;
|
||||
defaultOptions: Record<string, unknown>;
|
||||
stateSchema?: Record<string, "string" | "number" | "boolean" | "date" | "string[]" | "number[]" | "date[]">;
|
||||
defaultState?: Record<string, unknown>;
|
||||
template: string;
|
||||
}
|
||||
```
|
||||
|
||||
Path placeholders use `{parameter}` and are URL-encoded by the server. JSON body placeholders use `{ "$param": "parameter" }` and preserve the declared string, number, or boolean type.
|
||||
Exports, imports, the UI, MCP, and Workshop all validate this schema. Old files are rejected. If a board references a missing definition, only that tile shows **Custom widget unavailable**; the board and sibling widgets continue rendering.
|
||||
|
||||
`SubData` remains available for simple paths. For richer nested output, pass an interpreter-created render child; it receives the response data and safe status metadata:
|
||||
## Create and configure
|
||||
|
||||
```jsx
|
||||
<SubFetch requestId="service-detail" params={{ id: data.id }}>
|
||||
{(detail, meta) => (
|
||||
<Stack gap="xs">
|
||||
<Badge color={detail.online ? "green" : "red"}>{detail.status}</Badge>
|
||||
<Text size="xs">HTTP {meta.status}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</SubFetch>
|
||||
```
|
||||
Open **Management → Custom Widgets → Add custom widget**. The continuous workbench has sections for general information, API sources and credentials, requests, options, state, JSX, and preview. Use the sticky navigation to jump between sections; there are no artificial “next” steps.
|
||||
|
||||
| Component | Request kind | Behavior |
|
||||
| --------------- | ------------ | ------------------------------------------------------------- |
|
||||
| `SubFetch` | `query` | Loads a named GET request automatically or manually |
|
||||
| `ActionButton` | `action` | Runs a named POST, PUT, PATCH, or DELETE after a user gesture |
|
||||
| `ToggleSwitch` | `action` | Passes declared on/off parameters and rolls back on failure |
|
||||
| `RefreshButton` | None | Refreshes the parent GET response |
|
||||
A board item selects a definition from a searchable picker and immediately renders the definition's options. Invalid options block insertion. If an updated definition makes existing configuration invalid, opening the item shows the normal repair form.
|
||||
|
||||
Queries require board **view** permission. POST, PUT, and PATCH actions require **modify** by default. DELETE requires **full** permission and confirmation. Allowing viewers to execute a non-DELETE action requires explicit administrator approval. Anonymous viewers cannot execute actions.
|
||||
## Import, export, and Workshop
|
||||
|
||||
### Network scopes
|
||||
Export downloads one canonical JSON document without credentials. Import validates and shows the origins, auth modes, network scopes, methods, actions, and required board permissions before creating anything.
|
||||
|
||||
| Scope | Allowed destinations |
|
||||
| ---------- | ------------------------------------------------------------ |
|
||||
| `public` | Public IP addresses only |
|
||||
| `private` | Public addresses plus RFC1918 and IPv6 ULA private addresses |
|
||||
| `loopback` | Public, private, and loopback addresses |
|
||||
|
||||
Link-local, cloud-metadata, unspecified, multicast, documentation, carrier-grade NAT, and IPv4-mapped bypass addresses remain blocked in every scope.
|
||||
|
||||
### Example with a named query
|
||||
|
||||
The editor and AI prompt include a shared Pokédex fixture that loads details only for the visible page. Its named request is equivalent to:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "pokemon-detail",
|
||||
"kind": "query",
|
||||
"method": "GET",
|
||||
"pathTemplate": "/api/v2/pokemon/{name}",
|
||||
"parameters": { "name": "string" },
|
||||
"auth": "none",
|
||||
"minimumBoardPermission": "view",
|
||||
"cacheTtlSeconds": 300
|
||||
}
|
||||
```
|
||||
|
||||
## Agent authoring through MCP
|
||||
|
||||
Administrators can use Homarr's MCP server to iterate on custom widgets without bypassing validation or network controls. A typical agent workflow is:
|
||||
|
||||
1. Call `customWidget_schema` to inspect the current import format.
|
||||
2. Generate a complete `homarr-custom-widget-v3` draft.
|
||||
3. Call `customWidget_validate` and correct every returned issue.
|
||||
4. Call `customWidget_preview` to test the base GET request through Homarr's hardened executor.
|
||||
5. Use `customWidget_previewQuery` for named queries and `customWidget_simulatePreviewAction` to verify action parameters without sending mutations.
|
||||
6. Inspect `customWidget_previewJournal`, then validate again.
|
||||
7. Call `customWidget_import`, or read an existing widget with `customWidget_byId` and apply a focused `customWidget_update`.
|
||||
|
||||
MCP preview actions are simulation-only. Enabling live preview actions remains an explicit administrator choice in the browser workbench. Stored secret values are never returned by MCP; omit `secrets` when updating an existing widget to preserve them.
|
||||
|
||||
## Component reference
|
||||
|
||||
Custom JSX exposes a classified subset of Mantine Core, Charts, Dates, and Homarr components. Allowed components render directly. Wrapped components receive safe props or read-only behavior. Denied components remain in the registry so dependency upgrades cannot silently expand the template surface.
|
||||
|
||||
<CustomJsxComponentReference />
|
||||
|
||||
This table and the searchable component list in the editor are generated from Homarr's runtime registry.
|
||||
|
||||
Component props are filtered. Polymorphic roots, portals, refs, unsafe positioning, raw event handlers, and `dangerouslySetInnerHTML` are not exposed. Direct browser globals and direct `fetch` calls are unavailable, while ordinary text containing the word “fetch” is valid.
|
||||
|
||||
Safe bindings include conversion helpers, a restricted `Math` and `JSON` surface, object and array helpers, URI encoding helpers, and pure date formatting helpers. Collection callbacks and rendered output are subject to interpreter budgets.
|
||||
|
||||
## HTTP protections and limits
|
||||
|
||||
All base, preview, query, and action requests use the same server executor:
|
||||
|
||||
- Only HTTP and HTTPS are accepted. URL credentials and fragments are rejected.
|
||||
- Named paths must stay on the base endpoint origin.
|
||||
- DNS results are validated against the selected network scope and pinned for the request.
|
||||
- Queries can follow up to three revalidated same-origin redirects. Actions do not follow redirects.
|
||||
- Authorization, cookie, host, forwarding, proxy, content-length, and hop-by-hop headers cannot be supplied by a template.
|
||||
- Request bodies are limited to 10 KiB. Decompressed responses are limited to 1 MiB, 32 JSON levels, and 50,000 JSON nodes.
|
||||
- GET requests can be cached and deduplicated. Actions are never cached, polled, or retried automatically.
|
||||
|
||||
## Import and export
|
||||
|
||||
New exports use `"$schema": "homarr-custom-widget-v3"`. Version 2 imports remain accepted. Secrets are never exported and must be entered again after import.
|
||||
|
||||
Review the base origin, network scope, named request methods, inherited authentication, and required permissions before saving an imported widget. A version 1 template that contains inline network components stays disabled until it is converted to named requests.
|
||||
|
||||
AI-generated Custom JSX can also be pasted as a Homarr authoring bundle: one fenced JSON block with `"template": "__HOMARR_TEMPLATE__"`, followed by one fenced JSX block containing the readable multiline template. Homarr combines the blocks before validation, avoiding JSON newline and quote escaping. Raw JSON imports remain supported.
|
||||
|
||||
On the custom-widget list, paste a complete export with Ctrl+V or Command+V to open the import review and create it as a new definition. On an edit page, the same shortcut opens a replacement review and fills the existing form after confirmation. Pasted definitions never include secrets.
|
||||
The [Workshop](/docs/management/workshop) publishes valid widgets immediately. Users can vote or report a submission. Workshop administrators may dismiss reports or delete any post; deletion does not remove installed local copies.
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
sidebar_label: Compatibility and migration
|
||||
---
|
||||
|
||||
# Compatibility and migration
|
||||
|
||||
Homarr preserves existing display definitions while moving network-enabled Custom JSX to the named-request model.
|
||||
|
||||
## JSX API versions
|
||||
|
||||
Missing `jsxApiVersion` means v1. Display-only v1 templates can be opened and saved as v2 with an empty request manifest. Inline `url`, `method`, `headers`, and body props are inert in v2.
|
||||
|
||||
Static v1 network components can be converted by creating a named request and replacing inline capability props with `requestId` and typed `params`. Dynamic target construction requires an administrator to design an explicit path template and parameter list. Existing non-GET base definitions stop polling and show a network-review state.
|
||||
|
||||
## Import versions
|
||||
|
||||
Exports use `homarr-custom-widget-v3`; v2 imports remain accepted. Import migration normalizes display configuration, preserves v1 semantics, and never imports secret values. The review screen must be accepted before persistence.
|
||||
|
||||
Test migration with the same base response and compare rendered output, query results, action simulation, permissions, and journal entries before enabling the migrated definition on shared boards.
|
||||
@@ -1,32 +1,24 @@
|
||||
---
|
||||
sidebar_label: Requests and security
|
||||
---
|
||||
# Requests and security
|
||||
|
||||
# Named requests and security
|
||||
Each widget owns one or more API sources. Most use a single `default` source. Add another only when one widget genuinely combines APIs, such as a Sonarr/Radarr calendar.
|
||||
|
||||
Custom JSX v2 uses a saved request manifest. Runtime components pass only a `requestId` and declared primitive parameters; they cannot change the URL, method, headers, authentication, or body shape.
|
||||
Sources support no authentication, bearer tokens, basic authentication, API-key headers, and API-key query parameters. Credentials are encrypted per definition, source, and kind. Duplicating or exporting a widget does not copy credentials.
|
||||
|
||||
## Capability kinds
|
||||
## Named requests
|
||||
|
||||
`SubFetch` references a GET `query`. `ActionButton` and `ToggleSwitch` reference a POST, PUT, PATCH, or DELETE `action`. Base data is GET-only. Actions require an explicit gesture and are never polled, cached, retried automatically, or run on mount.
|
||||
Queries and actions support GET, POST, PUT, PATCH, and DELETE. `kind`, not method, controls behavior:
|
||||
|
||||
Default board permissions are view for queries, modify for POST/PUT/PATCH, and full for DELETE. Viewer actions require explicit high-risk approval; anonymous users cannot execute actions. DELETE always requires confirmation.
|
||||
- Queries may load automatically or run through `SubFetch`; they may include a body.
|
||||
- Actions only run after a user gesture; they may require confirmation and invalidate named queries.
|
||||
|
||||
## Parameter substitution
|
||||
Successful actions evict the declared queries from both the browser and server response cache before refetching them.
|
||||
|
||||
Path placeholders use `{name}` and are URL-encoded by the server. Runtime parameters must exactly match all declared names and types. JSON bodies substitute `{ "$param": "name" }` recursively and preserve string, number, or boolean types.
|
||||
Path parameters use `{name}`. Structured query/body templates use `{ "$param": "name" }`. Runtime values can come from validated options, local state, and action input.
|
||||
|
||||
## Network boundary
|
||||
## Protections
|
||||
|
||||
Named paths stay on the base URL origin. HTTP and HTTPS are accepted; URL credentials and fragments are rejected. DNS answers are classified and pinned. Public, private, and loopback scopes progressively allow destinations, but link-local, metadata, unspecified, multicast, documentation, carrier-grade NAT, and IPv4-mapped addresses are always blocked.
|
||||
All requests use Homarr's server executor. It enforces source origin, network scope, DNS and redirect validation, SSRF blocking, permissions, rate limits, request/parameter/body limits, timeouts, response-size limits, and secret injection. Static headers cannot override auth, routing, proxy, cookie, or hop-by-hop headers. Credential-like static values are rejected.
|
||||
|
||||
Queries may follow three revalidated same-origin redirects. Actions do not follow redirects. Credentials never cross origins. Reserved authentication, cookie, host, forwarding, proxy, content-length, security, and hop-by-hop headers cannot be declared.
|
||||
`public` allows public destinations, `private` additionally allows private LAN destinations, and `loopback` additionally allows loopback. Sensitive infrastructure ranges remain blocked according to the executor policy.
|
||||
|
||||
## Limits
|
||||
|
||||
- Request body: 10 KiB UTF-8.
|
||||
- Decompressed response: 1 MiB, 32 JSON levels, 50,000 JSON nodes.
|
||||
- Per user/item: 60 queries, 10 actions, and 3 deletes per minute.
|
||||
- Concurrency: 4 per user/item and 8 per definition.
|
||||
|
||||
Successful GETs can use manifest cache TTLs and single-flight deduplication. Logs contain redacted origin/method/error metadata, never secrets, request bodies, or query values.
|
||||
Every request declares view, modify, or full board access and the server enforces it before contacting the source. DELETE always needs full access and confirmation. Actions are never cached, retried, or triggered on load.
|
||||
@@ -1,33 +1,21 @@
|
||||
---
|
||||
sidebar_label: Troubleshooting
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## The preview says the template is required
|
||||
## Widget unavailable
|
||||
|
||||
New Custom JSX forms always include the starter fixture. If an imported definition has an empty template, reopen its import review; malformed imports must be corrected rather than previewed as JSX.
|
||||
The definition was deleted or reset. Remove the broken tile from the board and install or create a current Custom JSX v2 definition. There is no legacy recovery or migration wizard.
|
||||
|
||||
## A prop is ignored
|
||||
## Validation fails
|
||||
|
||||
The diagnostics name the component and prop. Check the generated component reference. Unsupported props are intentionally removed; use a supported layout primitive or wrapper prop instead of polymorphic, portal, ref, positioning, or raw handler props.
|
||||
Use the first schema/JSX diagnostic and its path. Ensure source/request IDs are unique, every request references a source, parameters are declared, defaults match options/state schemas, and network components reference the correct request kind.
|
||||
|
||||
## A request does not run
|
||||
## Request fails
|
||||
|
||||
Confirm that the component kind matches the manifest: `SubFetch` needs a query; action controls need an action. Parameter names and primitive types must match exactly. Base requests and queries require GET. Actions only run from a gesture, and edit mode disables controls.
|
||||
Check the source network scope, base URL, credentials, request method/path/body, runtime parameters, and board permission. The preview journal shows redacted method, request ID, path template, status, and duration without credentials.
|
||||
|
||||
## Network scope errors
|
||||
## Mantine prop warning
|
||||
|
||||
Resolve the hostname from the Homarr server environment. Select private or loopback scope only when required and understood. Link-local and metadata destinations cannot be enabled by any scope. Redirects must remain on the base origin.
|
||||
`UNKNOWN_MANTINE_PROP` is advisory and the prop is passed through. Confirm the prop against the installed Mantine version. `BLOCKED_CAPABILITY` is deliberate and must be expressed through safe options, state, bindings, or named requests instead.
|
||||
|
||||
## Permission errors
|
||||
## Existing item needs repair
|
||||
|
||||
The placed board item is the authorization anchor. A definition ID alone cannot authorize credential use. Verify board view/modify/full access, explicit viewer-action approval, and that the caller is not anonymous.
|
||||
|
||||
## Stale or failed preview data
|
||||
|
||||
Connection, authentication, body, method, secret, or manifest changes mark samples stale. Run the GET test again. Background query failures keep prior content visible and offer retry; actions roll toggles back and show error feedback.
|
||||
|
||||
## Import paste is ignored
|
||||
|
||||
Paste a complete v2/v3 JSON export or authoring bundle while the list or edit page is focused. Ordinary text and partial JSON are ignored so normal typing is not intercepted.
|
||||
The definition's options schema changed incompatibly. Open the item settings and repair the highlighted configuration. Homarr does not guess option migrations.
|
||||
@@ -166,54 +166,25 @@ The tools table on the MCP tab is dynamically generated - it always reflects the
|
||||
|
||||
## Custom Widget Workflow for AI Agents
|
||||
|
||||
AI agents can create and iterate on custom widgets using these tools in sequence.
|
||||
Agents create one `homarr-custom-widget-v2` definition at a time. Start with the native prompt `homarr-custom-widget-author`, then read the live schema and component resources rather than guessing the installed Mantine surface.
|
||||
|
||||
:::tip Editing an existing widget?
|
||||
Skip to step 5. Use `customWidget_byId` to fetch it and `customWidget_readTemplate` / `customWidget_writeTemplate` to edit its template directly.
|
||||
:::
|
||||
1. Call `customWidget_getAuthoringPrompt` and `customWidget_schema`.
|
||||
2. Build one widget and call `customWidget_validate` without saving.
|
||||
3. Call `customWidget_previewCreate`, then test named requests with `customWidget_previewQuery` and `customWidget_previewAction`.
|
||||
4. Open the returned preview URL, inspect `customWidget_previewJournal`, and iterate with `customWidget_templatePatch` or `customWidget_update`.
|
||||
5. Set a supplied credential with `customWidget_secretSet`, or use `customWidget_secretRequestUser` to issue a ten-minute Homarr entry URL. The agent receives completion status, never the plaintext.
|
||||
6. Call `customWidget_create`, then repeat for the next widget.
|
||||
|
||||
### 1. Get the schema
|
||||
Discovery resources:
|
||||
|
||||
Call `customWidget_schema` to receive the JSON Schema for the `homarr-custom-widget-v3` format. This tells the agent what fields are required and their types.
|
||||
- `homarr://custom-widgets/schema`
|
||||
- `homarr://custom-widgets/components`
|
||||
- `homarr://custom-widgets/components/{name}`
|
||||
- `homarr://custom-widgets/examples/{name}`
|
||||
- `homarr://custom-widgets/skill`
|
||||
|
||||
### 2. Draft and validate
|
||||
The complete tool surface is `customWidget_schema`, `customWidget_validate`, `customWidget_list`, `customWidget_get`, `customWidget_create`, `customWidget_update`, `customWidget_delete`, `customWidget_templatePatch`, `customWidget_previewCreate`, `customWidget_previewQuery`, `customWidget_previewAction`, `customWidget_previewJournal`, `customWidget_secretSet`, `customWidget_secretRequestUser`, `customWidget_getAuthoringPrompt`, and `customWidget_getSkill`.
|
||||
|
||||
Build a widget JSON object and call `customWidget_validate` with it. The tool returns structured issues (path + message) so the agent can fix and re-validate until the draft is clean.
|
||||
`custom-widget-manage` permits authoring. Supplying a secret directly requires the separate `custom-widget-secret-write` permission. Request execution retains Homarr's SSRF, DNS, redirect, response-size, timeout, rate, network-scope, and board-permission protections.
|
||||
|
||||
### 3. Create the widget
|
||||
|
||||
Call `customWidget_import` with the validated `homarr-custom-widget-v3` draft. This returns the new widget `id`. Use `customWidget_create` only when you intentionally need its lower-level creation input, including secret fields.
|
||||
|
||||
### 4. Test with real data
|
||||
|
||||
Call `customWidget_preview` with the widget's connection info. This makes a real GET request through Homarr's hardened executor and returns the sanitized response plus a preview session ID.
|
||||
|
||||
For Custom JSX v2 widgets with named requests, use `customWidget_previewQuery` to test each named GET query, and `customWidget_simulatePreviewAction` to dry-run actions without network calls.
|
||||
|
||||
### 5. Iterate on the template
|
||||
|
||||
Use `customWidget_readTemplate` to get the current JSX template as plain text, then `customWidget_writeTemplate` to push updates. The write tool accepts either:
|
||||
|
||||
- `template` — a single string (newlines as `\n`)
|
||||
- `templateLines` — an array of strings (joined with newlines server-side, avoids `\n` escaping)
|
||||
|
||||
Both options validate the template AST before saving. This avoids re-sending the full widget JSON for template-only edits.
|
||||
|
||||
For small changes, prefer `customWidget_patchTemplate`. Pass the `revision` returned by `customWidget_readTemplate` and one or more line edits. Homarr rejects stale revisions, applies all edits atomically, and validates the complete template against its named request manifest before saving.
|
||||
|
||||
### 6. Update non-template fields
|
||||
|
||||
Call `customWidget_byId` to get the full widget, modify fields, and call `customWidget_update` with only the changed fields.
|
||||
|
||||
### Available components in templates
|
||||
|
||||
Custom JSX templates support 100+ Mantine components plus:
|
||||
|
||||
- `<TablerIcon name="server" size={20} color="blue" />` — 100+ curated Tabler icons by kebab-case name (e.g. `"player-play"`, `"alert-triangle"`)
|
||||
- `<SubFetch>` / `<ActionButton>` / `<ToggleSwitch>` — network-capable components for named requests
|
||||
- Array methods: `map`, `filter`, `find`, `findIndex`, `some`, `every`, `reduce`, `sort`, `slice`, `flat`, `reverse`, `at`, `pop`, `join`, `includes`, `indexOf` (note: `pop` returns last element without mutating)
|
||||
- String methods: `toUpperCase`, `toLowerCase`, `trim`, `trimStart`, `trimEnd`, `slice`, `substring`, `includes`, `startsWith`, `endsWith`, `split`, `replace`, `replaceAll`, `repeat`, `padStart`, `padEnd`, `charAt`, `indexOf`, `lastIndexOf`
|
||||
|
||||
:::note
|
||||
All `customWidget_*` tools require admin-level API key permissions. The preview executor enforces rate limits (60 queries/min, 10 actions/min).
|
||||
:::
|
||||
See [Agents and downloadable skill](./custom-widgets/agent-authoring) for the full loop and portable Agent Skill.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Workshop
|
||||
|
||||
[Open Workshop](/workshop) to discover, inspect, vote on, report, download, and publish Custom JSX v2 widgets.
|
||||
|
||||
Valid submissions publish immediately. Authors can edit or delete their posts, and content changes increment the revision. Widget credentials are rejected and never belong in a submission.
|
||||
|
||||
Votes are unique per user and submission. Public listings show upvotes, downvotes, and score without exposing voter identities. Reports are also unique per reporter and submission and contain a category plus explanation.
|
||||
|
||||
Workshop administrators use a protected administration page to review reports grouped by submission, see reporter identities and explanations, inspect vote totals, dismiss reports, delete any submission, browse unreported posts, and view the audit trail. Workshop has no banning, account-state, moderator, approval, quarantine, badge, or remote-deletion system.
|
||||
|
||||
Deleting a Workshop post never deletes widgets already installed in a self-hosted Homarr instance.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Custom API"
|
||||
description: "Displays data from an administrator-defined HTTP API using built-in renderers or safe Custom JSX."
|
||||
description: "Renders an administrator-defined Custom JSX v2 widget with server-side API requests, options, and actions."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
@@ -10,13 +10,13 @@ import { customApiWidget } from ".";
|
||||
|
||||
<WidgetHeader widget={customApiWidget} categories={["Custom", "API"]} />
|
||||
|
||||
The Custom API widget displays an API definition created by an administrator in [Management → Custom Widgets](/docs/management/custom-widgets). A definition owns the endpoint, credentials, display configuration, and any named network capabilities used by Custom JSX.
|
||||
The Custom API widget displays a Custom JSX v2 definition created in [Management → Custom Widgets](/docs/management/custom-widgets). A definition owns one or more inline API sources, named requests, an options schema, local state, and one safe JSX template. Source credentials are encrypted separately and never reach the browser or an export.
|
||||
|
||||
### Add the widget
|
||||
|
||||
<AddingWidget />
|
||||
|
||||
Choose a custom-widget definition and a refresh interval. The board item stores only the selected definition and display options; credentials remain server-side.
|
||||
Choose a definition from the searchable picker, configure its generated options, and select a refresh interval. The board item stores only the selected definition ID and validated option values; credentials remain server-side.
|
||||
|
||||
:::note
|
||||
|
||||
@@ -26,14 +26,12 @@ A definition must be enabled and available to the board before it appears in the
|
||||
|
||||
### Permissions and actions
|
||||
|
||||
The base response and named query requests require permission to view the board. Named POST, PUT, and PATCH actions require permission to modify the board by default. DELETE requires full board permission and a confirmation. Anonymous viewers cannot execute actions.
|
||||
Each request declares its minimum board permission independently of its HTTP method. Queries and actions may use GET, POST, PUT, PATCH, or DELETE; `kind` controls runtime behavior. DELETE always requires full board permission and confirmation. Anonymous viewers cannot execute actions.
|
||||
|
||||
Interactive controls are disabled while the board is in edit mode. Actions run only after an explicit user gesture and are never triggered by mounting, refresh polling, or automatic retries. Authoring previews simulate actions unless an administrator explicitly enables live actions for the short-lived preview session.
|
||||
|
||||
### Refresh behavior
|
||||
|
||||
The refresh interval applies to the base GET response. A Custom JSX query can declare its own cache duration and can be refreshed manually. Named actions can invalidate the parent response or related named queries after they succeed.
|
||||
The refresh interval reruns load-triggered queries. Manual queries run through `SubFetch`, and each query may declare its own cache duration. Successful actions can invalidate only their declared load or manual queries.
|
||||
|
||||
If the definition needs a non-GET base request, migrate it to a GET base response plus a named action. Existing non-GET Custom JSX definitions require review before network access is enabled.
|
||||
|
||||
See [Custom Widgets management](/docs/management/custom-widgets) for the editor, display types, named-request manifest, network scopes, security limits, and migration guidance.
|
||||
See [Custom Widgets management](/docs/management/custom-widgets) for the workbench, options, named-request manifest, Mantine JSX surface, network scopes, and security limits.
|
||||
@@ -4,6 +4,7 @@ import { IconApi } from "@tabler/icons-react";
|
||||
export const customApiWidget: WidgetDefinition = {
|
||||
icon: IconApi,
|
||||
name: "Custom API",
|
||||
description: "Displays data from a custom API endpoint using configurable display types and optional JSX templates.",
|
||||
description:
|
||||
"Renders an administrator-defined Custom JSX v2 widget with server-side API requests, options, and actions.",
|
||||
path: "../../widgets/custom-api",
|
||||
};
|
||||
Reference in new issue
Block a user