Widget Proxy Request Handling#
All widget data fetching goes through a centralized server-side proxy pipeline. Widget components call useWidgetAPI, which serializes request parameters and calls /api/services/proxy. That route resolves the widget definition, validates the endpoint against an allowlist, rewrites the request, and delegates to a proxy handler — most commonly genericProxyHandler or credentialedProxyHandler — which calls httpProxy to make the outbound HTTP/HTTPS request.
Component → useWidgetAPI → /api/services/proxy → proxyHandler → httpProxy → upstream
Request flow in proxy.js#
The handler performs the following steps for every mapped widget:
- Resolve the mapping — looks up
widget.mappings[req.query.endpoint]to get the real endpoint, allowed params, method, body, segments, and optional per-endpointproxyHandleroverride . - Method enforcement — the mapping's
methodfield (defaulting toGET) is written ontoreq.method; if the incoming request's method doesn't match, a 403 is returned . - Body forwarding —
mapping.bodyreplacesreq.body, enabling POST/PUT payloads defined statically inwidget.js. - URL segment substitution — path-level variables (e.g., a node name) are resolved via
getSafeSegments, which validates andencodeURIComponent-encodes each segment before substituting into the endpoint string . - Query parameter serialization —
paramsandoptionalParamsare merged and appended as a query string (see below). - Delegate — calls
endpointProxy(req, res, map)(falling back toserviceProxyHandler) with the rewritten request .
For widgets using allowedEndpoints (a RegExp) instead of structured mappings, the request is forwarded as-is under GET without any parameter processing .
Query parameter handling: params vs optionalParams#
Widget query parameters originate as an object passed to useWidgetAPI; formatProxyUrl JSON-stringifies them into a single query URL parameter sent to the proxy route.
Inside proxy.js, when req.query.query is present, it is parsed and two mapping-declared lists control which keys reach the upstream API :
| Field | Behavior |
|---|---|
params (a.k.a. mappingParams in code) | Always included — all keys in this array are forwarded unconditionally. If a key is missing from queryParams, it passes undefined. |
optionalParams | Conditionally included — only forwarded if the key is actually present in the incoming queryParams object (filtered by !== undefined). |
Both lists are then concatenated and passed to URLSearchParams, which appends them to the endpoint URL as a query string.
Example — proxmoxbackupserver nodes/localhost/tasks : The mapping declares params: ["errors", "limit", "since"]. The component builds a taskQueryParams object with those keys and passes it to useWidgetAPI . The proxy then reliably appends all three to the upstream URL, e.g. ?errors=true&limit=100&since=1700000000.
Example — CoinMarketCap v1/cryptocurrency/quotes/latest: Uses params: ["convert"] (required) alongside optionalParams: ["symbol", "slug"] — symbol and slug are only forwarded when the component actually supplies them.
Proxy handlers#
Both built-in handlers (generic and credentialed) receive the rewritten req and call httpProxy:
genericProxyHandler: Builds the upstream URL fromwidgets[type].api+endpointusingformatApiCall. Merges headers from widget-type defaults, per-widget overrides, andreq.extraHeaders. Useswidget.method ?? req.methodas the HTTP method. Sendsreq.bodyorwidget.requestBodyas the body .credentialedProxyHandler: Same URL construction, but additionally injects auth headers (Bearer token, Basic auth, API-key headers, etc.) based onwidget.type. For example,proxmoxbackupservergets aPBSAPITokenheader . Header extras fromreq.extraHeaders(populated bymapping.headers) are also merged .
Both handlers call validateWidgetData on 200 responses before optionally applying the mapping's map() transform .
httpProxy and transport#
httpProxy selects HTTP or HTTPS based on the URL protocol, attaches a persistent keep-alive agent, and handles gzip/deflate decompression automatically . TLS certificate verification is disabled (rejectUnauthorized: false), which is intentional to support self-signed certificates common in self-hosted setups . It resolves to [statusCode, contentType, data, responseHeaders].
A separate cachedRequest helper wraps httpProxy with an in-memory cache (default 5-minute TTL) and is used for auxiliary lookups (e.g., icon resolution), not for widget data.
DNS resolution uses a custom lookup function that falls back from dns.lookup to dns.resolve4/dns.resolve6 on ENOTFOUND/EAI_NONAME errors — a fix for Alpine/musl libc environments (e.g., Kubernetes).
Key source files#
| File | Role |
|---|---|
src/pages/api/services/proxy.js | Central routing, endpoint allowlisting, param serialization |
src/utils/proxy/handlers/generic.js | Generic (unauthenticated) proxy handler |
src/utils/proxy/handlers/credentialed.js | Authenticated proxy handler (multi-scheme auth) |
src/utils/proxy/http.js | Low-level HTTP/HTTPS transport, DNS fallback, caching |
| Proxies Guide | Developer docs: handler types, creating custom handlers, testing |
| Widget Metadata Docs | mappings fields: params, optionalParams, method, body, headers, segments |