Developers · Examples

Code examples, annotated.

Small, complete pieces you can copy. Each follows the IDOP 1.0 specification; the counter is a real package you can download and open.

§ 01A complete document

The counter.

A button whose count is saved in the file. Download it, open it in IDOP Cloud, click, save — and the count is in the file the next time you open it.

counter.idop
counter.idop
├── mimetype          application/vnd.idop+zip
├── idop.json         the manifest
├── code/
│   ├── index.html    entry point
│   └── app.js
└── storage/
    └── count.json    written by the reader on Save
code/app.js
const button = document.querySelector('#add');
let count = 0;
try {
  count = JSON.parse((await idop.storage.read('count.json')).text).count;
} catch (error) {
  if (error.code !== 'IDOP-STORAGE-NOT-FOUND') throw error;
}
button.textContent = String(count);
button.addEventListener('click', async () => {
  count += 1;
  button.textContent = String(count);
  await idop.storage.write('count.json', JSON.stringify({ count }));
});

§ 02Storage

Transactions, and asking to save.

code/app.js
// All or nothing: either both writes happen, or neither does.
await idop.storage.transaction([
  { op: 'write', path: 'cards.json', data: JSON.stringify(cards) },
  { op: 'write', path: 'meta.json', data: JSON.stringify({ updated: Date.now() }) },
]);

// Ask the reader to show its own Save control. The decision stays with the user.
await idop.ui.requestSave();

§ 03Errors

Branch on the code, never the message.

Messages are diagnostics and may be localised. Codes are stable and listed in §16 of the specification.

code/app.js
try {
  const { text } = await idop.storage.read('settings.json');
  settings = JSON.parse(text);
} catch (error) {
  switch (error.code) {
    case 'IDOP-STORAGE-NOT-FOUND':
      settings = defaults;          // first run
      break;
    case 'IDOP-STORAGE-UTF8':
      showProblem('Saved settings are not text.');
      break;
    default:
      throw error;                   // never parse error.message
  }
}

§ 04Network

Calling an API with a key the document never sees.

Declare the capability and the credential binding in the manifest; the reader asks the user, holds the key, and attaches it only for the declared origin.

idop.json (excerpt)
"requiredCapabilities": [{
  "id": "ai",
  "type": "network",
  "origins": ["https://api.example.com"],
  "methods": ["POST"],
  "credentialBinding": "api-key",
  "purpose": "Generate answers"
}],
"credentialBindings": [{
  "id": "api-key",
  "required": true,
  "description": "Key for api.example.com"
}]
code/app.js
// The document never sees the key: the reader adds it,
// and only for an origin the manifest declared.
const response = await idop.network.request({
  capability: 'ai',
  url: 'https://api.example.com/v1/answers',
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ question }),
});
if (response.status === 200) render(JSON.parse(response.body));

§ 05Viewers

Opening ordinary files with an IDOP document.

A reader may let a user open, say, a .docx with an IDOP document as its viewer (§11.4, informative).

code/app.js
// A document opened as the viewer for an ordinary file
// receives exactly that one file, and nothing else.
const input = await idop.host.getInputFile();
// { name, extension, size, bytes }
render(input.name, input.bytes);

// Hand an edited copy back to the reader. This writes nothing by itself.
await idop.host.putOutputFile(editedBytes);

More in the documentation.

IDOP Cloud runs in the browser. Start from a template, open a file you were sent, or keep your own — no installation, and no account needed just to open a document.