Storage integration
Discovery and capabilities
Read /hosting/discovery to get urlsrc per file extension and /hosting/capabilities to learn what the editor server can do.
On this page
The editor server describes itself through two public endpoints, served through the O3O proxy without authentication. A WOPI host reads them to learn which URL opens each file type and what the server can do.
/hosting/discoveryList of actions per file type, as XML.
/hosting/capabilitiesEditor server capabilities, as JSON.
Discovery structure#
The root is <wopi-discovery> containing one <net-zone name="external-http">. Each <app> is a group: by application (writer, calc, impress, draw) or by MIME type. Each <action> has ext (file extension, empty for MIME entries), name (edit, view, editnew) and urlsrc.
HTTP/1.1 200 OK
Content-Type: text/xml
<wopi-discovery>
<net-zone name="external-http">
<app favIconUrl="http://localhost:8080/browser/825c9caa93/images/x-office-document.svg" name="writer">
<action default="true" ext="odt" name="edit" urlsrc="http://localhost:8080/browser/825c9caa93/cool.html?"/>
<action ext="odt" name="view" urlsrc="http://localhost:8080/browser/825c9caa93/cool.html?"/>
<action default="true" ext="docx" name="edit" urlsrc="http://localhost:8080/browser/825c9caa93/cool.html?"/>
<action ext="docx" name="view" urlsrc="http://localhost:8080/browser/825c9caa93/cool.html?"/>
<action ext="docx" name="editnew" urlsrc="http://localhost:8080/browser/825c9caa93/cool.html?"/>
<!-- ... -->
</app>
<app name="calc"> <!-- xlsx, xls, ods, csv ... --> </app>
<app name="impress"> <!-- pptx, ppt, odp ... --> </app>
<app name="application/vnd.openxmlformats-officedocument.wordprocessingml.document">
<action default="true" ext="" name="edit" urlsrc="http://localhost:8080/browser/825c9caa93/cool.html?"/>
<action default="true" ext="" name="view" urlsrc="http://localhost:8080/browser/825c9caa93/cool.html?"/>
</app>
<!-- ... -->
<app name="Capabilities">
<action ext="" name="getinfo" urlsrc="http://localhost:8080/hosting/capabilities"/>
</app>
</net-zone>
</wopi-discovery>urlsrcends with?. AppendWOPISrc=<encoded URL>and optional parameters such aslang=vi.- The origin of
urlsrccomes fromO3O_ONLINE_SERVER_NAME; the scheme ishttpswhenO3O_ONLINE_SSL_TERMINATION=true. It does not depend on the address you used to call discovery. - The
/browser/<hash>/segment changes with the server version. Always read discovery again; never hard-code it. Caching for a few minutes is enough. - The editor server opens extensions such as docx, doc, odt, rtf, txt, xlsx, xls, ods, csv, pptx, ppt, odp and more; the exact list is the set of
extwithname="edit". The O3O.Editor embed layer accepts only the 12 extensions listed in the configuration reference.
# Look up urlsrc by file extension from discovery (standard library only)
import urllib.request
import xml.etree.ElementTree as ET
O3O_URL = "http://localhost:8080"
def load_actions():
xml = urllib.request.urlopen(O3O_URL + "/hosting/discovery", timeout=10).read()
actions = {}
for action in ET.fromstring(xml).iter("action"):
ext, name = action.get("ext"), action.get("name")
if ext: # skip MIME-type entries (empty ext)
actions.setdefault((ext, name), action.get("urlsrc"))
return actions
actions = load_actions()
print(actions[("docx", "edit")]) # http://localhost:8080/browser/<hash>/cool.html?
print(actions[("xlsx", "view")])
print(sorted({ext for ext, name in actions if name == "edit"}))// Node.js 18+ (built-in fetch). Cache the result for a few minutes instead of calling it on every open.
const O3O_URL = "http://localhost:8080";
async function urlsrcFor(ext, action = "edit") {
const xml = await (await fetch(`${O3O_URL}/hosting/discovery`)).text();
for (const tag of xml.match(/<action\b[^>]*>/g) || []) {
const attr = (n) => (tag.match(new RegExp(`\\b${n}="([^"]*)"`)) || [])[1];
if (attr("ext") === ext && attr("name") === action) return attr("urlsrc");
}
throw new Error(`no ${action} action for .${ext}`);
}
urlsrcFor("docx").then(console.log); // http://localhost:8080/browser/<hash>/cool.html?Capabilities#
{
"convert-to": {
"available": true,
"endpoint": "/cool/convert-to"
},
"hasMobileSupport": true,
"hasProxyPrefix": false,
"hasSettingIframeSupport": true,
"hasTemplateSource": true,
"hasWopiAccessCheck": true,
"productName": "…",
"productVersion": "26.04.4.1",
"productVersionHash": "825c9caa93",
"serverId": "CDF2D4FC"
}| Field | Meaning |
|---|---|
productName, productVersion | Name and version of the editor server. The DEV image returns the upstream name; the source-built O3O image (coming soon) will return the O3O name. Do not switch features on this value. |
convert-to | The editor server reports a conversion endpoint, but with O3O that path is NOT usable from outside: o3o-proxy returns 404 for /cool/convert-to and /lool/convert-to. Only a Nextcloud on the same Docker network, calling o3o-online:9980 directly, uses it to render previews. To convert files, call DocBuilder's POST /v1/convert: it has authentication, plan limits and does not count as a connection. |
hasMobileSupport, hasWopiAccessCheck, … | Capability flags; a WOPI host may read them to adapt its own interface. |
The <proof-key> element in discovery (public key to verify X-WOPI-Proof). Not present in v1.