Docly JavaScript is not Node
What you'll see
Code written for a Docly workspace opens with const fs = require("fs"), calls await fetch(url), reads process.env.API_KEY, or arrives with a package.json and an npm install step. An AI coding agent asked to add a dependency proposes installing a package.
None of it works, and the failure is rarely a clean error — it is an undefined symbol part-way through a page render, so the page just stops producing output at that point.
What's actually happening
Docly's server-side JavaScript engine is HashJS. It runs on V8, so the language is JavaScript, but the surrounding runtime is Docly's, not Node's. Almost nothing from the Node standard library is present, and there is no package manager: the absence of package.json and node_modules in a Docly workspace is the normal state, not a missing setup step.
The same applies in the browser direction — this is not browser JavaScript either. There is no window, no document, and no fetch on the server side.
One name genuinely collides. docly.process() exists as a script evaluator — it executes a Docly script string with a supplied set of globals, and requires specific access because of the back-door risk. It has nothing to do with Node's process object. There is also a bare global process, which is that same evaluator rather than Node's: it is a function, and process.env is null, so process.env.API_KEY fails on a null property access instead of quietly reading undefined.
That points at the wider trap. The absent names are declared and null, not undefined, which breaks the standard feature-detection idiom:
typeof fetch // "object" -- because fetch is null
typeof fetch !== "undefined" // true -- so the detect passes
fetch(url) // and then fails anyway Measured inventory, re-checked after the August 2026 engine update:
Declared but null | Present and usable |
|---|---|
require, fetch, window, document, module, exports, setTimeout | console, JSON, Math, Date, Promise, Buffer, crypto, btoa, atob, process |
btoa and atob work as expected — btoa("abc") returns "YWJj". Buffer and crypto resolve to objects rather than being absent, but presence is not a promise of Node semantics: check what a member actually does before relying on it. And setTimeout being null means there are no timers at all — there is nothing to defer work onto.
What you get instead is a single platform library, docly.*, covering filesystem, HTTP, mail, images, authentication, sharing and output; plus import for your own code. The standard built-ins are there as usual: JSON, Math, String, Array, Date, console.log.
What to do
Reach for the Docly equivalent rather than the Node idiom:
| Instead of | Use |
|---|---|
require("fs"), fs.readFile | Filesystem functions — docly.getFile, docly.getFiles, docly.getJson, docly.saveFile, docly.saveJson |
fetch, axios, node-fetch | Network functions — docly.httpGet (JSON-parsed body), docly.httpPost, docly.httpFetch (full response: status, headers, raw body) |
nodemailer | Mail functions — docly.smtp.send, plus POP3 and IMAP |
process.env.SECRET | A JSON file under #/, read with docly.getJson(). The #/ tree is never served over HTTP — see Keep private data under the # folder |
npm install <pkg> | A Docly package, or your own .js file under #/ |
require("./helpers") | import { helper } from "#/helpers.js" — absolute paths from site root, no relative ./. Named, namespace and default imports all work |
module.exports | export default / named export |
Express req / res | Auto-bound function parameters plus the query and form globals; the returned object is the response. See Reading query and form fields in API calls |
path.join, relative paths | Absolute paths from site root. In anything the browser will execute, use ~/ — see Use tilde paths in JavaScript |
A minimal API endpoint, for orientation:
// #/API/orders/list.js
import { format } from "#/helpers.js";
export default () => {
let rows = docly.getFiles("/Orders", { recursive: true });
return { count: rows.length, items: rows.map(format) };
}; Do not feature-detect with typeof. Because the absent names are null rather than undefined, typeof x !== "undefined" is true for every one of them. Test x === null if you must, or better, do not reach for the Node name at all.
When you are unsure what a function returns, print it rather than assuming a Node-shaped result. A throwaway .hash page is the fastest way — see Use a scratch hash file to test Docly functions.
Start from the JavaScript reference, and read About JavaScript and HASH files before writing much of anything.