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 Method | SDK Method | Description |
|---|---|---|
network.fetch | network.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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | Yes | -- | The URL to fetch. |
options.method | string | No | "GET" | HTTP method: GET, POST, PUT, DELETE, PATCH. Case-insensitive. |
options.headers | Record<string, string> | No | {} | Request headers to include. |
options.body | string | object | null | No | null | Request 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:
| Field | Type | Description |
|---|---|---|
status | number | HTTP status code (e.g., 200, 404, 500). Defaults to 0 if unavailable. |
statusText | string | HTTP reason phrase (e.g., "OK", "Not Found"). Defaults to "" if unavailable. |
ok | boolean | true if status is in the range 200–299, false otherwise. |
headers | Record<string, string> | Response headers. Multi-value headers are joined with ", ". |
body | string | Response 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-Typeheader. If the response is JSON, you must callJSON.parse()on thebodyyourself. - HTTP errors are never thrown. Non-2xx status codes (e.g.,
404,500) are returned as normal results with the appropriatestatusandstatusText. 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:
| Port | Purpose |
|---|---|
4820 | Terminal server port |
3200 | Internal app port |
Blocked URL Patterns
Requests to localhost addresses on blocked ports are rejected. The following hostnames are treated as localhost:
localhost127.0.0.1::10.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:
| Condition | Error 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);
}
});