Skip to content

Network requests

Declare network and list your servers to send HTTPS requests to them. The main process makes each request and returns the status and text of the reply. It needs FluxPlugs 0.5.0-beta.1 or later.

Add a network field next to the capability:

{
"capabilities": ["settings", "network"],
"network": {
"hosts": ["api.example.com", "cdn.example.com"]
}
}

The field is required exactly when capabilities lists network; otherwise the manifest is refused with invalid_plugin_network. List 1 to 16 unique hosts, each a lowercase public DNS name with at least two labels: no IP addresses, wildcards, ports or trailing dot. Names under localhost, local, localdomain, internal, lan, home, test, invalid, example, onion and arpa are refused.

The hosts are part of what the user trusts. The enable prompt lists them under Connects to, and a plugin whose hosts change needs to be confirmed again, like one whose capabilities change.

Requests need no user action. fetch resolves with the reply, including 4xx and 5xx replies; it rejects only when the request is refused or doesn’t finish.

try {
const response = await api.network.fetch({
url: "https://api.example.com/v1/status?lang=" + encodeURIComponent("en"),
headers: { Accept: "application/json" },
});
if (response.status === 200) {
const data: unknown = JSON.parse(response.body);
api.logger.info("Status loaded", { object: typeof data === "object" });
} else {
api.logger.warn("Status request failed", { status: response.status });
}
} catch (error) {
api.logger.warn("Status request refused", {
code: error instanceof Error ? error.message : "unknown",
});
}

For a POST, pass the body as a string and set its Content-Type. Without one, FluxPlugs sends text/plain;charset=utf-8.

async function translate(text: string): Promise<string | undefined> {
const apiKey = await api.settings.get<string>("apiKey");
const response = await api.network.fetch({
url: "https://api.example.com/v1/translate",
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + (apiKey ?? ""),
},
body: JSON.stringify({ text, target: "en" }),
});
if (response.status === 429) return undefined;
if (response.status < 200 || response.status >= 300)
throw new Error("http_" + response.status);
const data = JSON.parse(response.body) as { translation?: unknown };
return typeof data.translation === "string" ? data.translation : undefined;
}
try {
const translated = await translate("Hola");
if (translated !== undefined) await api.ui.notify(translated);
} catch {
await api.ui.notify("The translation service didn't answer.");
}

Keep an API key in a secret setting, and never log it or show it in a notice.

  • URL: HTTPS only, at most 8 KiB, with no credentials or port. The host must be one the user confirmed. The fragment is dropped.
  • Addresses: the host must resolve to public IPv4 addresses. FluxPlugs pins the address it checked and refuses private and reserved ranges. IPv6-only hosts and system proxies aren’t supported.
  • Headers: at most 16, with printable ASCII values of up to 2048 characters. FluxPlugs sets User-Agent: FluxPlugs and Accept-Encoding: identity. You can’t set host, cookie, cookie2, set-cookie, origin, referer, connection, keep-alive, content-length, transfer-encoding, te, trailer, upgrade, expect, via, date, dnt, forwarded, accept-encoding, accept-charset, user-agent, x-http-method, x-http-method-override or x-method-override, or any name starting with proxy-, sec-, x-forwarded- or access-control-. Names are compared without case.
  • Cookies: none are sent or stored.
  • Body: only with POST, at most 32 KiB of UTF-8.
  • Reply: at most 256 KiB of UTF-8 text. Compressed and binary replies are refused. The result has status, contentType when the server sent one, and body.
  • Redirects: up to 3, each checked like the original URL. A 303, or a 301 or 302 after a POST, continues as a GET without body. Headers you set are dropped when a redirect leads to another host.
  • Time: 10 seconds for the whole request, including DNS, redirects and the reply.
  • Concurrency: 4 requests in flight per plugin instance, and 32 plugin instances with requests in flight. Stopping the plugin cancels its requests.
Error Meaning
invalid_network_request Malformed request, http URL, forbidden header, GET body, too big
network_host_denied The host, or a redirect’s host, isn’t in the confirmed list
network_redirect_denied A redirect to a non-HTTPS URL or one with credentials or a port
network_redirect_limit More than 3 redirects
network_private_address The host resolves to a private or reserved address
network_timeout / network_cancelled Took longer than 10 seconds, or the plugin stopped
network_response_too_large The reply is over 256 KiB
network_response_not_text / network_encoding_unsupported The reply isn’t UTF-8 text, or it is compressed
network_limit Too many requests in flight; wait for earlier ones
network_failed The connection failed or broke off
invalid_network_response The host’s answer failed validation
capability_denied:network The manifest doesn’t declare network, or it isn’t confirmed
plugin_stopped / plugin_changed The instance stopped or its code changed; ignore the result

Everything a request carries reaches the listed servers without the user seeing it. A plugin that also has decorations or message_content could send the ids or text of messages on screen, and the enable prompt warns about that combination. Send only what the feature needs, and say in your description which service you use and what you send to it.