The Docly file format Last updated: 06. Jul 2026
In Docly there are no files on a disk. Every file is a row in a database, its content is JSON, and its type is decided by a named schema. The file extension you see in the Drive is not stored — it is derived from the schema, following one simple rule: the extension tells you the content format.
A document is a database row
A Docly workspace is a virtual file system. Each "file" is a Document record with a handful of fields that matter to you:
Field | Meaning |
|---|---|
| Stable identity. Survives rename, move, copy, export/import. |
| The stored logical name (e.g. |
| The name of the schema that defines this document's type. |
| The content, stored as a JSON string. This is the heart of the format. |
| Metadata. |
How Values is interpreted depends entirely on the Schema. That is the whole model — everything below is a consequence of it.
The core rule: extension = content format
When a document is shown in the Drive (WebDAV, the file explorer, download), Docly derives a filename with an extension. The extension is not arbitrary — it announces how the content is encoded:
Kind of document | What | Drive name |
|---|---|---|
Schema-less ( | Arbitrary raw JSON, exactly as you wrote it |
|
Form document (has a schema, no file-mapping) | Structured JSON matching the schema's form fields |
|
File-mapped schema (Code File, Uploaded File/Image, …) | A wrapper around a real file body | stored name verbatim ( |
Folder properties (the folder's own settings) | Folder-level settings | reserved dotfile |
So in short:
.json→ the content is raw JSON. You canGETit and parse it directly..docly→ the content is the Docly wrapper format (see the wrapper section below) — a portable envelope around a structured document.A real extension (
.js,.png,.pdf, …) → the document is file-mapped: its schema says "this is a file of type X", and the Drive serves the real bytes.
This rule runs both directions. When you upload something to the Drive, Docly routes it by the same logic in reverse:
A
.doclyfile whose content is a valid wrapper → imported as a Docly document (itsGuidis preserved).A mapped extension → the mapped type;
.jsonbecomes a schema-less document (the content must be valid JSON).Otherwise, text → a Code File; binary → an Uploaded File.
Why identity is verified, not stripped. A Code File may genuinely be named test.docly. Docly never decides identity by blindly stripping an extension — it looks up candidate names (verbatim first) and verifies each hit against the document's real Drive name. This is why a real test.docly opens correctly instead of 404-ing.
Schema-less vs. schema-bound
Schema-less is the simplest case and the one you reach for as an application database:
docly.saveFile("settings.json", { theme: "dark", pageSize: 25 }); Schemaisnull,Valuesis your JSON, stored verbatim.Surfaces in the Drive as
settings.json.Second write to the same path updates in place (no duplicate).
Use this for app state, counters, logs, configuration — anything that is "just data".
Schema-bound documents have a named schema that defines a form (which fields exist, their types) used by the editor and the AI assistant, optional templates for rendering the document as HTML, optional file-mapping (turns the document into a real file), and flags such as versioning, script processing, indexing.
A schema is itself just a document (of document-type DocumentSchema), stored under the workspace path #/Schemas/. You resolve schemas by name, not by numeric id.
Reserved path | Purpose |
|---|---|
| Schema definitions. |
| JavaScript files here become REST endpoints. |
| Renders JSON documents as HTML. |
The .docly wrapper format
When you download a document as .docly, or import one, you are working with the portable, self-contained envelope. It is a single JSON object:
{
"Guid": "okFm8fl4A0Kd4lxI47LH2Q",
"Schema": "Article",
"Tags": ["news", "release"],
"Created": "2026-03-01T09:00:00+01:00",
"Modified": "2026-03-06T11:58:26+01:00",
"Document": {
"Title": "Docly 2.0 is here",
"Body": "<p>Read all about it.</p><img src=\"cid:img_a1b2c3.png\">"
},
"EmbeddedFiles": [
{ "Id": "img_a1b2c3.png", "Data": "iVBORw0KGgoAAAANS..." }
]
} Here the document uses the Article schema; its two fields (Title, Body) live in Document, and the one image referenced from the body is carried along in EmbeddedFiles.
Field | Meaning |
|---|---|
| Identity — preserved across export/import, so a re-imported file is the same document, not a copy. |
| The schema name (works for both tightly- and loosely-coupled schemas). |
| Metadata. |
| The payload — the document's own |
| Any binary attachments, inlined as base64 ( |
Field order is deliberate. Identifying fields (Guid, Schema, Tags, Created, Modified) come first; the large payloads (Document, EmbeddedFiles) come last. That lets Docly recognise a .docly file by sniffing the first few hundred bytes without parsing megabytes of base64. Order does not affect validity — older files with a different order still import fine.
Files inside documents: file-mapping and embedded files
A schema can declare a file-mapping, which turns documents of that type into real files:
A text mapping points at one field that holds the file body. The built-in Code File schema maps its body to the
Contentfield, so ascript.js's source lives inValues.Content— but the Drive serves it verbatim asscript.js.A file-upload mapping points at a field that references an embedded file. Uploaded File / Uploaded Image store the bytes as an embedded attachment (on the file store, not in
Values) and keep only a reference in the JSON.
Embedded files keep large binary data out of Values. Instead of inlining the bytes, the document field holds a cid: reference — a content-id that names an embedded attachment by its Id (cid:img_a1b2c3.png → the EmbeddedFiles entry with "Id": "img_a1b2c3.png").
Relevant scripting helpers:
docly.saveEmbeddedFile(path, embeddedName, data)— store bytes (base64 string or byte array) as an attachment.docly.extractAndEmbedBase64Images(path)— walk a document's HTML fields, pull out every inlinedata:image/...;base64,..., save each as an embedded file, and replace the inline data with acid:reference. Returns how many were extracted.docly.saveFile(path, data, schema, ensurePath, embedBase64)— passembedBase64: trueto run that same extraction automatically on save.
On export, every embedded file referenced by a document is inlined into the .docly wrapper's EmbeddedFiles array — which is what makes a .docly file fully portable. (Embedded files require a schema; schema-less .json documents cannot hold them.)
Heterogeneous arrays: ObjectVariant
When a document field is an array whose items are of different types, each item carries an "ObjectVariant" property naming its variant. This lets the editor (and the AI assistant) render each item with the correct editor:
{
"Items": [
{ "ObjectVariant": "TextItem", "Text": "Some text" },
{ "ObjectVariant": "ImageItem", "Url": "cid:img_a1b2c3.png" }
]
} If you build documents programmatically and omit ObjectVariant on a mixed array, items land in the wrong variant — always include it.
Folders and the .docly properties file
Documents live in folders, which form a tree; a workspace is a root folder.
Each folder can have one special properties document whose stored name is empty (""). In the Drive it appears as the reserved dotfile .docly. It holds folder-level settings, is hidden from normal listings, and cannot be renamed, moved, or replaced through the Drive.
Quick reference
A document = a DB row:
Guid+Schema(name) +Values(JSON content).Extension = content format:
.json= raw JSON (schema-less),.docly= wrapper format (form documents & folder properties), real extension = file-mapped..doclyfile = portable envelope:Guid, Schema, Tags, Created, Modified, Document (payload), EmbeddedFiles[]— identifying fields first so it is stream-recognisable.Schema-less = arbitrary JSON stored raw, ideal as an app database; schema-bound = structured, form-defined, optionally file-mapped or rendered.
ObjectVariantdiscriminates mixed array items; embedded files + base64 keep binary out ofValuesand self-contained in.docly(referenced bycid:).Identity is by
Guid, resolved by verified lookup — never by stripping an extension.
See also About schemas, Create schemas and Built-in schemas.