Back to README (Overview)
Internal Deep Dive

tally-to-xml-tdl — Guide

Comprehensive reference on how the library works internally, how Tally TDL XML requests are structured, and how to extend the endpoints.


Architecture #

The library codebase is organized cleanly into modular domain layers under src/v6/:

src/v6/
  └−− body.xml ← shared base XML request envelope template
  └−− company/ ← lists open companies in Tally
  └−− masters/ ← master entities (stock, ledgers, units, groups)
  └−− vouchers/
  │  └−− purchases/ ← purchase voucher retrieval (period / all)
  │  └−− sales/ ← sales voucher retrieval
  └−− core/
    └−− buildXml.js ← template engine filling placeholders
    └−− transport/ ← HTTP POST client talking to Tally
    └−− response/ ← fast XML-to-JSON parsing pipeline
    └−− execute/ ← orchestrates the 3-phase execution cycle

Every domain module (company, masters, vouchers) strictly follows the exact same 3-phase pipeline:

  1. Template Load: Reads body.xml once at module load time via synchronous file reading (fs.readFileSync).
  2. Build: Invokes buildXml(body, { staticVariables, tdlMessage }) to substitute request variables into the XML template.
  3. Execute: Calls executeXml(xml), which executes HTTP transport and returns the parsed JSON envelope.

The XML template #

src/v6/body.xml defines the standard Tally HTTP export envelope containing two exact interpolation placeholders:

xml • body.xml
<ENVELOPE>
  <HEADER>
    <TALLYREQUEST>Export</TALLYREQUEST>
    <TYPE>Collection</TYPE>
    <ID>TDLID</ID>
  </HEADER>
  <BODY>
    <DESC>
      <STATICVARIABLES>
        <SVEXPORTFORMAT>$$SysName:XML</SVEXPORTFORMAT>
        {{STATICVARIABLES}}
      </STATICVARIABLES>
      <TDL>
        <TDLMESSAGE>
          <COLLECTION NAME="TDLID">
            {{TDLMESSAGE}}
          </COLLECTION>
        </TDLMESSAGE>
      </TDL>
    </DESC>
  </BODY>
</ENVELOPE>

buildXml executes a fast, zero-dependency string replacement:

javascript
body
  .replace("{{STATICVARIABLES}}", staticVariables)
  .replace("{{TDLMESSAGE}}", tdlMessage);

staticVariables carries context that Tally Prime requires prior to executing the query (e.g. active company name, export date windows).
tdlMessage defines the TDL collection body specifying which object type and data fields Tally should retrieve.

How TDL queries work #

Tally Prime's HTTP interface parses a <COLLECTION> block defined in TDL (Tally Definition Language). The <TYPE> tag points to the internal Tally schema object, and each <FETCH> tag selects a specific attribute to include in the payload.

Example 1: Fetching Ledgers with GSTIN

xml • TDL Collection
<COLLECTION NAME="TDLID">
  <TYPE>Ledger</TYPE>
  <FETCH>$$Alias:Name</FETCH>
  <FETCH>GSTRegistrationType</FETCH>
  <FETCH>GSTIN</FETCH>
</COLLECTION>

This maps directly to the ledgerNamesWithDetails definition stored in masters.json:

json • masters.json
{
  "ledgerNamesWithDetails": {
    "staticVariables": "<SVEXPORTFORMAT>$$SysName:XML</SVEXPORTFORMAT>",
    "tdlMessage": "<TYPE>Ledger</TYPE><FETCH>$$Alias:Name</FETCH><FETCH>GSTRegistrationType</FETCH><FETCH>GSTIN</FETCH>"
  }
}

Example 2: Purchase Vouchers with Inventory Entries

xml • TDL Collection
<COLLECTION NAME="TDLID">
  <TYPE>Vouchers:VoucherType</TYPE>
  <CHILDOF>$$$$VchTypePurchase</CHILDOF>
  <BELONGSTO>Yes</BELONGSTO>
  <FETCH>AllInventoryEntries</FETCH>
  <FETCH>Date</FETCH>
</COLLECTION>

$$$$VchTypePurchase is Tally's internal formula constant identifying Purchase vouchers.
<CHILDOF> and <BELONGSTO> restrict the collection to only vouchers inheriting from the Purchase voucher type hierarchy.

Core modules #

buildXml(body, { staticVariables, tdlMessage })

Pure string template replacer. Deterministic, fast, with zero side effects.

javascript
import { buildXml } from "tally-to-xml-tdl/src/v6/core/buildXml.js";

const xml = buildXml(body, {
  staticVariables: "<SVCURRENTCOMPANY>MyCompany</SVCURRENTCOMPANY>",
  tdlMessage: "<TYPE>Ledger</TYPE><FETCH>$$Alias:Name</FETCH>"
});

sendXml({ xml, url })

Performs HTTP POST to Tally's local server port using Node.js native fetch. Returns raw XML response string.

javascript
import { sendXml } from "tally-to-xml-tdl/src/v6/core/transport/http.js";

const rawXml = await sendXml({ xml, url: "http://localhost:9000" });

xmlToJson(xml)

High-performance wrapper over fast-xml-parser that produces a plain JavaScript object.

javascript
import { xmlToJson } from "tally-to-xml-tdl/src/v6/core/response/xmlToJson.js";

const json = xmlToJson(rawXml);

executeXml(xml)

Glues sendXml and xmlToJson into a single invocation. Powers all public methods in the package.

javascript
import { executeXml } from "tally-to-xml-tdl/src/v6/core/execute/executeXml.js";

const json = await executeXml(xml);

Adding a new master query #

Adding support for another master catalog (e.g. Cost Centres, Currency, Godowns) takes zero code changes:

  1. Open src/v6/masters/masters.json.
  2. Add an entry with your desired TDL collection definition:
json • masters.json
{
  "costCentres": {
    "staticVariables": "<SVEXPORTFORMAT>$$SysName:XML</SVEXPORTFORMAT>",
    "tdlMessage": "<TYPE>CostCentre</TYPE><FETCH>$$Alias:Name</FETCH>"
  }
}
  1. Call it immediately via the existing masters interface:
javascript
const result = await masters.get("My Company", "costCentres");
Done! That is the entire modification. No functions or classes need to be authored.

Adding a new voucher type #

To support an additional voucher type (such as Receipts, Payments, or Journal):

  1. Create the subfolder: src/v6/vouchers/receipts/
  2. Add an info.json file with your custom TDL query definition.
  3. Add an index.js file matching the structure in purchases/index.js.
  4. Export the module in src/v6/vouchers/index.js:
javascript • vouchers/index.js
export * as receipts from "./receipts/index.js";

Error handling #

The library intentionally does not mask or silently catch exceptions. If Tally Prime is stopped, native fetch will throw a standard TypeError: fetch failed.

javascript
try {
  const result = await masters.get("My Company", "stockItems");
} catch (err) {
  if (err.cause?.code === "ECONNREFUSED") {
    console.error("Tally is not running or HTTP port is not enabled.");
  } else {
    throw err;
  }
}

Troubleshooting Diagnostics:

Diagnostic Root Cause & Resolution
ECONNREFUSED Tally Prime application is closed, or HTTP port 9000 is disabled in F12 Advanced Configuration.
ECONNRESET Tally terminated the connection prematurely during high volume payload transmission.
Empty COLLECTION The active company name string does not match exactly, or no vouchers exist in the specified date range.