Docly Developers
ExampleUpdated 17 Oct 2026

Syncing data service

A scheduled service that fetches products from an external API and updates price and campaign fields on the matching documents in the site. SyncUrl and SyncToken are read from #/Config. Place the file at #/Services/SyncService.js and register it in #/Services.json, for example { "Schedules": [{ "Name": "Product sync", "Daily": ["06:00"], "Task": { "Path": "SyncService.js" } }] } - see Services - scheduling and events.

Example

Code #/Services/SyncService.js

export default () => {
    let config = docly.getJson("#/Config") || {};

    if (!config.SyncToken) throw new Error("SyncToken is not set in #/Config.");
    if (!config.SyncUrl) throw new Error("SyncUrl is not set in #/Config.");

    // httpPost does not throw when the call fails - it returns { DoclyError }.
    let result = docly.httpPost(config.SyncUrl, { "token": config.SyncToken });
    if (!result || result.DoclyError) throw new Error("Sync request failed: " + (result ? result.DoclyError : "no response"));
    if (!Array.isArray(result)) throw new Error("Sync response is not a list.");

    let products = docly.getFiles("/", { name: "*", recursive: true }) || [];

    let errors = [];
    let updates = [];
    let notModified = [];

    let n = new Date();
    let now = docly.format(n, "yyyy-MM-dd") + "T" + docly.format(n, "HH:mm");

    for (let p of result) {
        let values = p.Values || {};
        let sourceId = values.ROWID;

        let target = products.find(x => x.ProductId == sourceId);
        if (target == null) {
            errors.push({ id: sourceId, name: p.Name, message: "Product not found" });
            continue;
        }

        let newPrice = values.BasePrice;
        let campaignPrice = "";
        let campaignEnd = "";

        // Is a campaign running on the product right now?
        for (let c of values.Campaigns || []) {
            if (c.FromDate <= now && c.ToDate >= now) {
                campaignPrice = c.CampaignPrice;
                campaignEnd = c.ToDate;
            }
        }

        if (newPrice != target.Price || campaignPrice != target.CampaignPrice || campaignEnd != target.CampaignEnd) {
            updates.push({
                id: sourceId,
                name: p.Name,
                newPrice: newPrice,
                oldPrice: target.Price,
                campaignPrice: campaignPrice,
                oldCampaignPrice: target.CampaignPrice,
                campaignEnd: campaignEnd,
                oldCampaignEnd: target.CampaignEnd
            });
            let targetPath = (target.folderpath || "") + "/" + target.filename;
            docly.patchFile(targetPath, { "Price": newPrice, "CampaignPrice": campaignPrice, "CampaignEnd": campaignEnd });
        } else {
            notModified.push({ id: sourceId, name: p.Name });
        }
    }

    return { products: result.length, updates: updates, notModified: notModified, errors: errors };
};