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/:
Every domain module (company, masters, vouchers) strictly follows the exact same 3-phase pipeline:
- Template Load: Reads
body.xmlonce at module load time via synchronous file reading (fs.readFileSync). - Build: Invokes
buildXml(body, { staticVariables, tdlMessage })to substitute request variables into the XML template. - 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:
<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:
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
<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:
{
"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
<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.
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.
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.
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.
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:
- Open
src/v6/masters/masters.json. - Add an entry with your desired TDL collection definition:
{
"costCentres": {
"staticVariables": "<SVEXPORTFORMAT>$$SysName:XML</SVEXPORTFORMAT>",
"tdlMessage": "<TYPE>CostCentre</TYPE><FETCH>$$Alias:Name</FETCH>"
}
}
- Call it immediately via the existing masters interface:
const result = await masters.get("My Company", "costCentres");
Adding a new voucher type #
To support an additional voucher type (such as Receipts, Payments, or Journal):
- Create the subfolder:
src/v6/vouchers/receipts/ - Add an
info.jsonfile with your custom TDL query definition. - Add an
index.jsfile matching the structure inpurchases/index.js. - Export the module in
src/v6/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.
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. |