Skip to main content

Network (HTTP Fetch) API

The Network API allows extensions to make HTTP requests to external services. It provides a fetch-style interface, giving extensions the ability to interact with REST APIs, retrieve remote data, and submit form data -- all without requiring any special permission in the extension manifest.

Overview

The Network API is part of the network namespace in the extension SDK. It exposes a single RPC method for HTTP requests:

RPC MethodSDK MethodDescription
network.fetchnetwork.fetch()Execute an HTTP request

No permission declaration is required in the extension manifest. Any extension can use the Network API immediately.

network.fetch

Performs an HTTP request and returns the response.

SDK Signature

const response = await editor.network.fetch(url, options);

Parameters

ParameterTypeRequiredDefaultDescription
urlstringYes--The URL to fetch.
options.methodstringNo"GET"HTTP method: GET, POST, PUT, DELETE, PATCH. Case-insensitive.
options.headersRecord<string, string>No{}Request headers to include.
options.bodystring | object | nullNonullRequest body. Relevant for POST, PUT, and PATCH.

RPC-Level Parameters

When calling the raw RPC method network.fetch, the parameters map is:

{
"url": "https://api.example.com/data",
"method": "GET",
"headers": { "Authorization": "Bearer token123" },
"body": null
}

The url parameter is required and must be a string. The method parameter defaults to "GET" if omitted.

Response Format

The response is an object with the following fields:

FieldTypeDescription
statusnumberHTTP status code (e.g., 200, 404, 500). Defaults to 0 if unavailable.
statusTextstringHTTP reason phrase (e.g., "OK", "Not Found"). Defaults to "" if unavailable.
okbooleantrue if status is in the range 200–299, false otherwise.
headersRecord<string, string>Response headers. Multi-value headers are joined with ", ".
bodystringResponse body as plain text.

Example response:

{
"status": 200,
"statusText": "OK",
"headers": {
"content-type": "application/json; charset=utf-8",
"cache-control": "max-age=43200"
},
"body": "{\"id\":1,\"title\":\"Example\"}"
}

Key Behaviors

  • Response type is always plain text. The response body is returned as a raw string regardless of the Content-Type header. If the response is JSON, you must call JSON.parse() on the body yourself.
  • HTTP errors are never thrown. Non-2xx status codes (e.g., 404, 500) are returned as normal results with the appropriate status and statusText. This lets your extension handle error responses gracefully rather than catching exceptions.
  • Network-level errors are thrown. Failures such as DNS resolution errors, connection timeouts, or unreachable hosts will throw an exception with a message like "Network request failed: <details>".
  • Multi-value headers are joined. If the server sends multiple values for the same header, they are combined into a single comma-separated string (e.g., "value1, value2").
  • RPC timeout is 30 seconds. If the request does not complete within 30 seconds, the RPC call will time out and return an "RPC timeout" error.

Security

The Network API enforces several security restrictions to prevent extensions from accessing internal app services.

Blocked Ports

The following ports are reserved for internal app use and cannot be accessed by extensions:

PortPurpose
4820Terminal server port
3200Internal app port

Blocked URL Patterns

Requests to localhost addresses on blocked ports are rejected. The following hostnames are treated as localhost:

  • localhost
  • 127.0.0.1
  • ::1
  • 0.0.0.0

A request to any of these hosts on a blocked port (e.g., http://localhost:4820/... or http://127.0.0.1:3200/...) will throw an exception:

Access to localhost:4820 is not allowed for extensions

Unparseable URLs

If the provided URL cannot be parsed, it is rejected defensively with the error:

Invalid URL: <url>

Allowed Requests

  • Requests to any external host (e.g., https://api.github.com) are allowed.
  • Requests to localhost on non-blocked ports (e.g., http://localhost:8080) are allowed. This enables extensions to communicate with local development servers.

Note on file:// URLs

The file:// URL scheme is not explicitly blocked at the HTTP fetch level, but the underlying HTTP client does not support file:// URLs, so such requests will fail with a network error.

Error Handling

Errors from network.fetch fall into two categories:

1. HTTP Error Responses (Non-Throwing)

Non-2xx responses are returned as normal results. Check the status field:

const res = await editor.network.fetch("https://api.example.com/missing");

if (res.status === 404) {
console.log("Resource not found");
} else if (res.status >= 500) {
console.log("Server error:", res.statusText);
}

2. Exceptions (Throwing)

The following conditions throw an exception that should be caught with try/catch:

ConditionError Message Pattern
Blocked port"Access to localhost:<port> is not allowed for extensions"
Invalid/unparseable URL"Invalid URL: <url>"
DNS failure / no connection"Network request failed: <details>"
Other transport error"Failed to execute fetch for <url>: <details>"
RPC timeout (30 seconds)"RPC timeout"
try {
const res = await editor.network.fetch("https://unreachable.example.com");
// Use res.status, res.body, etc.
} catch (err) {
await editor.window.showToast("Request failed: " + err.message);
}

Examples

GET Request

Fetch data from a JSON API:

const res = await editor.network.fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);

if (res.status === 200) {
const post = JSON.parse(res.body);
console.log(post.title);
}

POST Request with JSON Body

Send JSON data to an API:

const res = await editor.network.fetch(
"https://jsonplaceholder.typicode.com/posts",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "New Post",
body: "Content goes here",
userId: 1,
}),
}
);

if (res.status === 201) {
const created = JSON.parse(res.body);
await editor.window.showToast("Created post #" + created.id);
}

PUT Request

Update an existing resource:

const res = await editor.network.fetch(
"https://jsonplaceholder.typicode.com/posts/1",
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: 1,
title: "Updated Title",
body: "Updated content",
userId: 1,
}),
}
);

DELETE Request

Delete a resource:

const res = await editor.network.fetch(
"https://jsonplaceholder.typicode.com/posts/1",
{ method: "DELETE" }
);

if (res.status === 200) {
await editor.window.showToast("Post deleted");
}

Request with Custom Headers

Include authentication or other custom headers:

const apiKey = "your-api-key";

const res = await editor.network.fetch(
"https://api.example.com/protected/data",
{
headers: {
"Authorization": "Bearer " + apiKey,
"Accept": "application/json",
},
}
);

Handling All Response Fields

Inspect the full response including headers:

const res = await editor.network.fetch("https://api.example.com/data");

console.log("Status:", res.status, res.statusText);
console.log("Content-Type:", res.headers["content-type"]);
console.log("Body:", res.body);

Displaying Results in a Popup

Combine the Network API with the Window API to show results to the user:

editor.commands.registerCommand("myExt.fetchAndShow", async () => {
try {
await editor.window.showToast("Fetching data...");

const res = await editor.network.fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);

let body;
try {
body = JSON.stringify(JSON.parse(res.body), null, 2);
} catch {
body = res.body;
}

await editor.window.showPopup({
title: "Fetch Result",
contentType: "markdown",
markdown: [
"## Response",
"",
"**Status:** " + res.status + " " + res.statusText,
"",
"**Body:**",
"```json",
body.substring(0, 1000),
"```",
].join("\n"),
});
} catch (err) {
await editor.window.showToast("Error: " + err.message);
}
});