Editful
DocsNetwork requests

Call an API from a plugin.

Declare each HTTPS origin, then let the Editful host carry requests across the plugin boundary.

  • Explicit origins
  • Host transport
  • Bounded responses

Declare each origin and method

A plugin starts without network access. Add network to its capabilities, then declare every HTTPS origin and method the plugin uses in editful.plugin.ts.

ts
import { definePluginConfig } from '@editful/plugin-tools';

export default definePluginConfig({
  schemaVersion: 2,
  id: 'example:weather',
  name: 'Weather cards',
  description: 'Adds current weather data to the canvas.',
  version: '0.1.0',
  entry: './src/index.ts',
  minAppVersion: '0.10.0',
  capabilities: ['commands', 'network'],
  network: [
    {
      origin: 'https://api.weather.example',
      methods: ['GET'],
      purpose: 'Read current conditions for a location selected by the user.',
    },
  ],
});

The declaration uses an exact origin, without a path, query, credentials, or wildcard. Editful compares each request's origin and method with this policy before any connection starts. The purpose appears with the plugin's permissions, so describe the user-facing reason for the access.

Paths and query parameters remain part of the request URL. A redirect stays inside the declared origin; a redirect to another origin is rejected even when that second origin also appears elsewhere in the policy.

Network declarations accept HTTPS origins only. Add one declaration per origin and list only the methods the plugin sends.

Send a request from an action

Commands, editors, importers, interactions, and agent actions receive an action context. Call action.network.request(...) from that context instead of calling global fetch.

ts
const response = await action.network.request({
  url: `https://api.weather.example/current?place=${encodeURIComponent(place)}`,
  method: 'GET',
  headers: { accept: 'application/json' },
  response: 'json',
  timeoutMs: 5_000,
});

if (response.status < 200 || response.status >= 300) {
  throw new Error(`Weather request failed (${response.status})`);
}

const conditions = response.body;

The promise resolves with a status, a bounded set of response headers, and the decoded body. HTTP errors such as 404 and 500 still resolve, so check status before using the body.

method
Uses GET when omitted. The method must appear in the origin declaration.
headers
Adds application headers. Editful removes cookies and transport-controlled headers.
body
Sends a string or Uint8Array, up to 1 MiB.
response
Decodes the body as json, text, or bytes. The default is json.
timeoutMs
Accepts 100–30,000 milliseconds. The default is 30,000.

Let the host choose the transport

Plugin code uses the same request contract in every Editful host. It does not detect Electron, reach into a preload bridge, or choose between native and browser fetch.

The desktop host validates action requests and sends them from the Electron main process. This avoids renderer CORS rules while keeping the request inside the plugin's declared policy. A web embedding supplies the same PluginNetwork service with browser fetch; browser CORS rules apply to that transport. An embedding without a network service rejects the request as unavailable.

Editful omits ambient credentials in both transports. Requests do not inherit cookies from the app, and third-party services do not receive a board credential unless the request uses the first-party authentication mode described below.

Canvas document assets use a separate host-owned transfer path. Browser hosts use web fetch; packaged Editful uses Electron networking for signed storage transfers. Plugin code does not branch on the host environment.

Choose credentials explicitly

Use auth: 'board' for a declared Editful first-party endpoint that acts on the current board. Editful injects the credential only for host-approved first-party origins, and only when the URL's /v1/boards/{boardId}/ segment matches the active action context. The credential never enters plugin code.

ts
const boardId = action.boardId;
if (boardId === undefined) {
  throw new Error('This request needs an active board.');
}

const response = await action.network.request({
  url: `https://assets-canvas.sam.ink/v1/boards/${encodeURIComponent(boardId)}/integrations/unsplash/random?count=12`,
  method: 'GET',
  response: 'json',
  auth: 'board',
});

action.boardId identifies the board attached to the current action context. It is undefined when the host does not provide an active board, so check it before building a board-scoped URL.

For a third-party API, declare a plugin secret and its secret-ref setting, read the selected credential from action.secrets, and add the service's authorization header yourself. Keep credentials out of URLs because URLs commonly appear in server and proxy logs.

Work within the request boundary

The desktop host validates and bounds each individual action request. It does not impose a host-side request-rate or concurrency quota on plugins.

Request body
1 MiB maximum.
Response body
4 MiB maximum.
Redirects
Up to 3, all within the declared origin.
Response headers
content-type, etag, last-modified, retry-after, x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset.

An action cancellation also cancels its active request. Editful reports that case as PluginActionCancelledError. Policy failures, timeouts, invalid response decoding, and transport failures use PluginNetworkError. Check HTTP status codes separately because a completed HTTP response is not a transport error.

Use the request API for the job

Editful exposes related network APIs at different plugin boundaries.

action.network.request(...)
Calls an API from a command, editor, importer, interaction, or agent action. It returns JSON, text, or bytes.
host.network.request(...)
Loads binary tiles, styles, or other renderer data inside a gpu-renderer contribution. It supports GET and POST and uses browser networking.
action.remoteMedia.probe(...)
Validates and decodes a declared remote image, returns its dimensions, and warms the host image cache.

Remote images use a separate remoteMedia declaration because Editful validates both the origin and the returned media type. Use the Images guide for URL-backed canvas images.