Developer docs

Documented against main @ bbedf98 (0.8.9-dev, 4 November 2026) · latest release v0.8.8

Radicle

Radicle repos and issues

Read public repositories with fetch('rad:…'), ask the user's node to seed what it does not have, and write issues and comments as the user.

On this page

A Freedom page can be a full forge client without a server. Canopy, a GitHub-style forge served from Swarm, works exactly this way.

Read with rad: URLs#

Reads are plain fetch() calls against the user's own node, with no prompt and no connection:

const rid = 'z…';   // the bare repository id
const res = await fetch(`rad:${rid}`);
if (res.status === 404) {
  // the node does not have it (yet): see the next section
}
const repo = await res.json();
const head = repo.payloads['xyz.radicle.project'].meta.head;
const issues = await (await fetch(`rad:${rid}/issues?status=open&perPage=50`)).json();

Revisions must be full 40-character SHAs, so start from the metadata's head. Routes, pagination and error bodies are in rad: URLs.

Get a repository onto the node#

rad: only serves what the node stores. To open a repository the node does not have, ask the user to seed it and wait for the fetch:

const fullRid = `rad:${rid}`;

function fetched() {
  return new Promise((resolve, reject) => {
    const onStatus = (s) => {
      if (s.rid !== fullRid) return;
      if (s.state === 'fetched') { done(); resolve(s); }
    };
    const done = () => window.radicle.removeListener('seedStatus', onStatus);
    window.radicle.on('seedStatus', onStatus);
  });
}

await window.radicle.requestAccess();            // prompts once per origin
const waiting = fetched();                       // listen before seeding
const { status } = await window.radicle.seed({ rid: fullRid });  // prompts for this repo
if (status.state !== 'fetched') await waiting;

Things to know:

  • seed resolves as soon as the policy is written; the fetch runs in the background. Show progress.phase (resolving, connecting, fetching, …) so the user sees it moving; there are no percentages.
  • A failed state is not final: the node keeps retrying, and you can call sync({ rid }) to restart the fetch without another prompt. A real app should also give up waiting after a while and say so.
  • After a reload, getSeedStatus({ rid }) returns the latest snapshot.
  • seed and unseed prompt every time, per repository.

Write issues and comments#

Writes are signed with the user's Radicle node identity, the same author everywhere, after a one-time signing grant:

const me = await window.radicle.getIdentity();   // asks for the signing grant
const { id } = await window.radicle.createIssue({
  rid: fullRid,
  title: 'Crash when opening settings',
  description: 'Steps to reproduce: …',
  labels: ['bug'],
});
await window.radicle.commentIssue({ rid: fullRid, issueId: id, body: 'Also on Linux.' });
await window.radicle.editIssueState({ rid: fullRid, issueId: id, state: 'closed' });

Each write goes into the node's own storage, the same storage rad: reads from, and is announced to the network before the call resolves. A write that timed out may still have landed, so read before you retry rather than posting twice.

Limits and input rules (title 200 bytes, body 64 KiB, up to 10 labels) are in the reference. Patches can be commented on but not created from a page in this version; see Not in this version.

Test against a real repository#

Freedom Browser's own Radicle mirror, rad:z3QXuMvMmSeEX3ZgoUidZC1v5MkKE, is public and fine for read tests. Do not create test issues on someone else's repository: initialise your own with the Radicle CLI and use that.