TallyPrime Part 23: TDL Development Masterclass — Reports, Forms, Parts, Objects, Collections, Functions & Custom Invoice | FreeLearning365

TallyPrime Part 23: TDL Development Masterclass — Reports, Forms, Parts, Objects, Collections, Functions & Custom Invoice | FreeLearning365
🔧
📐
⚙️
Part 23 of 45 — TallyPrime Deep-Dive Masterclass

TDL Development: The Language That Extends TallyPrime

From objects and collections to reports, forms, parts, lines, fields, functions, actions, events, and user-defined fields. Build custom reports, custom invoices, and complete TallyPrime extensions — hands-on, step-by-step, with production-ready code examples.

📖 150 min read 📚 14 Chapters 🔧 Objects & Collections 📐 Custom Reports 🧾 Custom Invoice ⚙️ UDF & Events
1

What Is TDL?

The foundation — history, purpose, and why TDL is the only way to extend Tally

TDL stands for Tally Definition Language. It is the proprietary, domain-specific programming language designed specifically for extending TallyPrime. Everything beyond Tally's built-in features — custom reports, custom invoices, user-defined fields, automated workflows, external integrations, and third-party add-ons — is built using TDL.

🏗️ The One Sentence Definition: TDL is to TallyPrime what Visual Basic for Applications (VBA) is to Microsoft Excel — a built-in scripting language that extends the parent application without modifying its core.

Why TDL Exists

No software can anticipate every business requirement. A dairy company needs a milk-collection report. A pharma distributor needs batch-wise expiry tracking. A textile exporter needs multi-currency invoices with specific layouts. Tally cannot ship every possible feature.

Instead, Tally provides a complete definition language that lets developers build anything the business needs — while keeping the underlying engine stable, tested, and compliant.

2000+
TDL Definitions in Tally
500+
Built-in Reports (TDL Driven)
1000+
Tally Add-ons on Market
0
Core Modifications Needed

What TDL Can Do

  • Create entirely new reports — define report structure, columns, filters, calculations, and layouts from scratch
  • Modify existing reports — add columns, change sorting, add filters, or hide fields in Tally's built-in reports
  • Build custom invoice and voucher formats — full control over print layouts, logos, QR codes, barcodes, and dynamic content
  • Add user-defined fields (UDF) — extend masters and vouchers with custom data fields specific to your business
  • Automate workflows — trigger actions on voucher save, validation, delete, print, or user login
  • Integrate with external systems — send data to web services, fetch data from databases, respond to HTTP requests
  • Customise the UI — add buttons, menus, keyboard shortcuts, and custom forms
  • Extend TallyPrime Server capabilities — build enterprise-grade extensions that scale across users

What TDL Cannot Do

✗ TDL Is Not
  • Not a general-purpose language like C#, Java, or Python
  • Not a way to bypass Tally's accounting logic
  • Not a way to modify Tally's core engine
  • Not a replacement for Tally's built-in features
  • Not suitable for building standalone applications
✓ TDL Is
  • A domain-specific extension language
  • A way to define reports, forms, and behaviours
  • A way to extend without breaking
  • A complement to Tally's built-in features
  • Suitable for extending Tally's ecosystem

TDL vs Other Extension Approaches

ApproachWhat It Can DoLimitations
XML/JSON Integration Push/pull vouchers, ledgers, masters Cannot create new reports or modify UI
ODBC Read-only analytics access No writes, no UI modification
TDL Everything — reports, invoices, UDF, events, integrations Requires TDL knowledge, specific tooling
💡
Real-World Impact: Tally's entire ecosystem of 1,000+ add-on products — payroll extensions, industry-specific reports, e-invoicing integrations, WhatsApp notifications, and mobile apps — is built on TDL. Every add-on you have ever used in Tally was written in TDL.

Who Should Learn TDL

  • Software developers building Tally add-ons, integrations, or enterprise extensions
  • ERP consultants who need to deliver custom requirements beyond Tally's built-in features
  • Chartered accountants who want to build custom MIS reports for their clients
  • CFOs and finance leaders who want to understand what is possible with Tally before commissioning projects
  • Business analysts who need to bridge the gap between business needs and technical implementation
2

TDL Architecture & Execution Model

How TDL sits inside TallyPrime and how it executes at runtime

Before writing a single line of TDL, you must understand where TDL sits in TallyPrime's architecture and how it executes. This mental model is critical — it separates developers who merely copy-paste TDL from those who can design and debug custom extensions.

Where TDL Sits in the Stack

🖥️
TallyPrime UI Layer
What the user sees and interacts with — menus, forms, reports, buttons
▼
📐
TDL Definitions Layer
Your TDL files define what UI elements exist, how they behave, what data they show
▼
🔧
TDL Interpreter
Reads TDL definitions, resolves attributes, evaluates formulas, executes actions
▼
📦
Object Model Layer
Business data represented as objects — Ledgers, Vouchers, Groups, Stock Items
▼
⚙️
Data Engine
Retrieves, filters, sorts, aggregates data from the object tree
▼
💾
Tally File System (TPFS)
Proprietary file-based storage — .900 and .1800 files
💻 Developer Insight: TDL does not bypass any layer. When you write a TDL report that shows ledger balances, that data still flows through the object model and data engine — you are simply defining what to show, not how to fetch it. This is why TDL is safe: it cannot break Tally's data integrity.

How TDL Executes — The Lifecycle

  1. Load: When TallyPrime starts, it loads its own TDL definitions first, then loads any external TDL files you have added (add-ons, custom reports, etc.)
  2. Parse: The TDL interpreter reads each definition and builds an internal model of Reports, Forms, Parts, Lines, Fields, Functions, and Actions
  3. Resolve: When a user opens a report, Tally resolves the report definition, evaluates any attributes or formulas, and constructs the display
  4. Fetch: Data is retrieved via Collections from the object model. Filters are applied. Sorting is applied. Aggregations are computed
  5. Render: The report is drawn on screen using Tally's built-in rendering engine
  6. Interact: User actions (clicks, key presses) trigger Events, which fire Actions, which may modify data, navigate, or run functions

Where TDL Files Live

📁 Common TDL File Locations
  • Built-in TDL: Bundled inside TallyPrime itself — you never see these files directly
  • Add-on TDL: Placed in the TallyPrime installation folder or a custom folder configured in Tally
  • User TDL: Loaded via the Load TDL menu in TallyPrime Developer or via F1 → Settings → TDLs & Add-Ons
  • Compiled TDL: TDL files can be compiled into .tcp (Tally Compiled Program) format for distribution and protection

TallyPrime Developer — The TDL IDE

TallyPrime Developer is the official IDE for writing, testing, and debugging TDL code. It provides:

  • Syntax highlighting for TDL keywords, definitions, attributes, and values
  • Code completion for definitions and attributes
  • Built-in debugger to step through TDL execution
  • Object browser to explore Tally's built-in objects and methods
  • Definition search to find where Tally defines its own reports, functions, and behaviours
  • Compilation to .tcp format for distribution
💡 Pro Tip: The single most valuable learning resource for TDL developers is TallyPrime Developer's Definition Search. Search for any built-in report (e.g., Balance Sheet) and see exactly how Tally defines it. This is like having the source code to Tally's built-in reports.
3

TDL Syntax Basics

Definitions, attributes, values — the three building blocks of every TDL program

TDL is a declarative language. You do not write procedural code that executes line by line. Instead, you declare definitions that describe what you want Tally to do. Tally's interpreter reads your declarations and figures out the execution.

The Three Building Blocks

Building BlockPurposeExample
Definition Declares a component — a report, form, part, line, field, function, etc. [Report: MyReport]
Attribute Specifies a property of a definition Title : "My Report"
Value The actual data assigned to an attribute "My Report", 123, Yes

The Simplest TDL Definition

;; The simplest TDL definition
[Report: MyFirstReport]
    Title : "My First Custom Report"
    Form : MyFirstForm

This definition declares a report named MyFirstReport. The report has a title and uses a form named MyFirstForm. That is the entire report definition. Tally will now know this report exists — though it will not yet appear in the menu until we add it to a menu structure.

Comments in TDL

;; This is a single-line comment in TDL

/* This is a
   multi-line comment */

Naming Conventions

  • Definitions use PascalCase: MyCustomReport, SalesRegisterExtended
  • Attributes use PascalCase: Title, Form, Variable
  • Values can be strings, numbers, expressions, or keywords
  • User-Defined Functions typically start with UDF_ or a prefix
  • User-Defined Fields use the UDF prefix
  • Never use reserved keywords as definition names — check TallyPrime Developer's definition browser

Value Types in TDL

TypeExamplesNotes
String "Hello", "Sales Report" Always in double quotes
Number 100, 3.14, -500 Can be integer or decimal
Keyword Yes, No, True, False Boolean-like values
Method $Name, $ClosingBalance Prefixed with $, retrieves data from objects
Formula #MyFormula, $$Value:Field Prefixed with # or $$
Variable SVFromDate, SVToDate System variables (SV prefix) or custom variables
Expression 1 + 2, #Amount * 0.05 Mathematical or logical expressions

A Slightly Richer Example

;; A report with multiple attributes
[Report: MyLedgerReport]
    Form : MyLedgerForm
    Title : "Custom Ledger Report"
    Variable : SVFromDate, SVToDate
    Variable : MyFilter
    Set : MyFilter : "All Ledgers"

[Form: MyLedgerForm]
    Part : MyLedgerPart
    Width : 100 % Screen
    Height : 100 % Screen

[Part: MyLedgerPart]
    Lines : MyLedgerLines
    Repeat : MyLedgerLines : MyLedgerColl

[Line: MyLedgerLines]
    Fields : Name, ClosingBalance
    Local : Field : Name : Set as : $Name
    Local : Field : ClosingBalance : Set as : $ClosingBalance

[Collection: MyLedgerColl]
    Type : Ledger
    Fetch : Name, ClosingBalance
    Sort : -$ClosingBalance
💡 Learning Tip: Do not try to memorise TDL syntax. Instead, learn the patterns. Reports always reference Forms. Forms always reference Parts. Parts always reference Lines. Lines always reference Fields. Collections fetch data. This consistent pattern repeats across all TDL code.
4

Objects — The Building Blocks of Data

Understanding how Tally represents business entities as objects

In Tally's architecture, every business entity is an object. A Ledger is an object. A Voucher is an object. A Stock Item is an object. A Group is an object. Even a Company is an object. Understanding this object model is fundamental to TDL.

What Is an Object?

📦 Object Components
  • Identity: Every object has a name or identifier (e.g., "Rahim Traders", "SI-2026-0001")
  • Methods: Functions to retrieve data from the object (e.g., $Name, $ClosingBalance)
  • Collections: Groups of related objects (e.g., a Ledger's Bill Allocations)
  • Attributes: Properties that define the object's behaviour
  • Parent: The object higher in the hierarchy (e.g., a Ledger's parent Group)

The Object Hierarchy

🏢
Company Object
Root of the entire object tree — contains all other objects
▼
📁
Group Objects
Chart of accounts hierarchy — Assets, Liabilities, Income, Expenses
▼
📒
Ledger Objects
Individual accounts — customers, suppliers, banks, expenses
▼
📄
Voucher Objects
Transactions — sales, purchases, receipts, payments, journals
▼
📦
Entry Objects
Individual ledger entries within a voucher — debit and credit lines
▼
💰
Bill Allocation Objects
Bill-wise details within a ledger entry — individual invoices and payments

Accessing Object Data — Methods

In TDL, you retrieve data from an object using methods. Methods always start with the $ symbol.

;; Common methods for accessing object data

$Name                ;; The name of the current object
$ClosingBalance      ;; Ledger's closing balance
$OpeningBalance      ;; Ledger's opening balance
$Parent              ;; The parent of the current object
$Date                ;; Voucher date
$VoucherNumber       ;; Voucher number
$Amount              ;; Amount in ledger entry
$IsDeemedPositive    ;; Is the entry a debit?
$Group               ;; The group a ledger belongs to
💻 Method Chaining: Methods can be chained to navigate the object hierarchy. For example, $Parent.$Name returns the name of the current object's parent. This is how you walk up and down the object tree.

How to Explore Available Methods

  • Open TallyPrime Developer and use the Object Browser
  • Search for built-in TDL definitions that reference the object type you are working with
  • Read the official TDL reference documentation
  • Use Debug mode to inspect the current object's available methods
  • Study Tally's own TDL source for any built-in report that uses that object type

Practical Example — Walking the Hierarchy

;; Example: Get the Group name of a Ledger
[Function: GetLedgerGroup]
    Returns : String
    Variable : LedgerObj : Object : Ledger
    Returns : $LedgerObj.Parent.$Name

;; Example: Check if a Ledger is a Sundry Debtor
[Function: IsSundryDebtor]
    Returns : Logical
    Variable : LedgerObj : Object : Ledger
    Returns : $LedgerObj.Parent.$Name = "Sundry Debtors"
📦
Object Count: A mid-sized company with 200 ledgers, 500 stock items, and 10,000 vouchers has roughly 50,000+ objects in its Tally database — all living in a single hierarchical tree that can be traversed in milliseconds.
5

Collections — Fetching Sets of Objects

How TDL retrieves, filters, sorts, and aggregates data from the object model

A Collection is a set of objects retrieved from the object model. Collections are how TDL queries data. Every report you build in TDL is powered by one or more collections.

The Anatomy of a Collection

[Collection: MyLedgerCollection]
    Type    : Ledger
    Fetch   : Name, ClosingBalance, Parent
    Filter  : MyLedgerFilter
    Sort    : -$ClosingBalance

[System: Formulae]
    MyLedgerFilter : $$IsLedger:$Name
AttributePurposeExample
Type What kind of objects to fetch Ledger, Voucher, StockItem, Group
Fetch Which methods to compute and cache Name, ClosingBalance
Filter Which objects to include A formula returning Yes/No
Sort Order of objects -$ClosingBalance (descending)
Walk Traverse parent-child relationships MyLedgerColl (walk down)

Collection Types

  • Simple Collection: Fetches a flat list of objects of one type (e.g., all Ledgers)
  • Nested Collection: A collection inside a collection (e.g., a Ledger's vouchers)
  • Walking Collection: Traverses parent-child relationships (e.g., a Group's sub-groups and ledgers)
  • Method Collection: Uses a Method to determine which objects to fetch

Filters — The Power of Selective Data

Filters determine which objects end up in a collection. They are formulas that evaluate to Yes or No for each object. If the formula returns Yes, the object is included. If No, it is excluded.

;; Filter: Only Sundry Debtors with balance over 10000
[System: Formulae]
    HighValueDebtors : $$IsSundryDebtor:$Parent AND
                       $ClosingBalance > 10000

;; Filter: Only Sales vouchers in a date range
    SalesInRange : $VoucherTypeName = "Sales" AND
                    $Date >= $$SVFromDate AND
                    $Date <= $$SVToDate
💻 Filter Performance Tip: Filters evaluate for every object in the collection scope. Keep them efficient. Avoid complex expressions that call slow methods. If you find a filter running slow, try fetching fewer objects or pre-computing values.

Sorting — Order Matters

;; Sort ascending by name
Sort : $Name

;; Sort descending by balance
Sort : -$ClosingBalance

;; Multi-level sort: group ascending, then balance descending
Sort : $Parent, -$ClosingBalance

Aggregation — Computing Totals

TDL provides built-in aggregate functions that operate on collections:

  • $Total — Sum of a method across all objects in a collection
  • $Count — Number of objects in a collection
  • $Average — Average of a method across all objects
  • $Maximum — Highest value of a method
  • $Minimum — Lowest value of a method
💡 Learning Strategy: Start with the simplest possible collection — Type : Ledger with no filters. Get it working. Then add a filter. Then add sorting. Then add aggregation. Build up complexity one step at a time.
6

Reports — The Top-Level Definition

Report definitions, variables, menus, and how reports connect to the UI

A Report is the top-level definition in TDL. It represents an entire screen that the user can open, view, and interact with. Every report in Tally — from the Day Book to the Balance Sheet — is defined as a Report.

The Minimal Report Definition

[Report: MyReport]
    Form    : MyForm
    Title   : "My Custom Report"
    Variable : SVFromDate, SVToDate
AttributePurpose
Form The form that defines the report's layout
Title The title displayed at the top of the report
Variable Variables this report uses (system or custom)
Set Initial value for a variable
Menu Context menu shown when the report is open
On Enter Action to run when the user enters the report
On Exit Action to run when the user exits the report
Repeat Report-level repetition (for hierarchical reports)

System Variables You Will Use Everywhere

  • SVFromDate — The report's "from" date (user can change with F2)
  • SVToDate — The report's "to" date (user can change with F2)
  • SVCurrentCompany — The active company
  • SVCurrentDate — Today's date
  • SVPeriodicity — Monthly, quarterly, yearly reporting period
  • SVCostCentre — The active cost centre
  • SVShowLedgerDetails — A flag for showing drill-down detail

Adding a Report to the Menu

A report definition alone does not make it accessible from the Tally menu. You must add it to a Menu definition.

;; Add report to a custom menu
[Menu: MyCustomMenu]
    Add : Item : MyReport : MyReport
    Key : Alt+F10

;; Alternative: Add to existing menu
[Menu: Display More Reports]
    Add : Item : MyReport : MyReport
⚠️ Menu Modification Caution: Modifying Tally's built-in menus should be done carefully. If your custom report has a bug, it can break the parent menu. Always test in a separate TDL file first and revert if needed.

Drill-Down — Reports Inside Reports

One of Tally's signature UX patterns is drill-down. From a Balance Sheet, you can press Enter on a Group to see its Ledgers. Press Enter on a Ledger to see its Vouchers. Press Enter on a Voucher to see its entries.

;; Enable drill-down on a field
[Field: LedgerName]
    Use     : Name Field
    Set as  : $Name
    On      : Enter : DrillDown

[Report: DrillDownReport]
    Form    : LedgerDetailForm
    Title   : $$String:$Name + " - Details"
    Variable : SVCurrentLedger
    Set     : SVCurrentLedger : $$Value:$Name
💻 Drill-Down Magic: Drill-down in Tally is not "opening a new report." It is a context shift — the same report definition re-evaluates with a different context. This is why drill-down in Tally feels instantaneous while drill-down in many other ERPs feels like a page reload.
7

Forms, Parts, Lines, Fields — The UI Hierarchy

How TDL constructs a report visually — from outermost container to innermost data element

A report's visual layout in TDL is built through a strict hierarchy: Form → Part → Line → Field. Every TDL UI element lives somewhere in this hierarchy. Understanding this is essential for building any custom report or invoice.

The Four-Level Hierarchy

🖼️
Form — The Screen Container
Holds an entire screen. Contains one or more Parts. Controls width, height, and screen positioning
▼
📐
Part — The Section Container
A logical section within the Form. Can be a header, body, footer, or side panel
▼
📏
Line — The Row Container
A horizontal row within a Part. Repeats for each item in a collection
▼
📄
Field — The Data Element
A single column or data cell. Holds one value from the object being displayed

Form Definition

[Form: MyReportForm]
    Part    : MyHeaderPart, MyBodyPart, MyFooterPart
    Width   : 100 % Screen
    Height  : 100 % Screen
    Button  : MyPrintButton, MyExportButton
    Key     : F12 : MyConfigMenu

A form defines:

  • Which parts it contains and in what order they appear
  • The overall dimensions (width and height)
  • What buttons appear at the bottom
  • What keys are bound to actions (like F12 for configuration)

Part Definition

[Part: MyBodyPart]
    Lines    : MyHeaderLine, MyDataLine, MyTotalLine
    Repeat   : MyDataLine : MyLedgerColl
    Scroll   : Vertical
    Border   : Thin Left
    Common   Border : Yes
Part AttributePurpose
LinesWhich lines this part contains
RepeatWhich line repeats for each object in a collection
ScrollEnable horizontal or vertical scrolling
BorderAdd border lines
Common BorderShare border with adjacent parts
VerticalYes for vertical parts (split screen)

Line Definition

[Line: MyDataLine]
    Fields   : LedgerName, OpeningBal, Debit, Credit, ClosingBal
    Local    : Field : LedgerName : Set as : $Name
    Local    : Field : OpeningBal : Set as : $OpeningBalance
    Local    : Field : Debit : Set as : $$AsAmount:$$Debit
    Local    : Field : Credit : Set as : $$AsAmount:$$Credit
    Local    : Field : ClosingBal : Set as : $ClosingBalance
    Border   : Thin Bottom

Field Definition

[Field: LedgerName]
    Use     : Name Field
    Width   : 30 % Screen
    Style   : Bold
    Align   : Left

[Field: ClosingBal]
    Use     : Amount Field
    Width   : 20 % Screen
    Align   : Right
    Style   : Normal
Field AttributePurposeCommon Values
UsePre-defined field typeName Field, Amount Field, Date Field
WidthField widthPercentage or fixed character count
AlignText alignmentLeft, Right, Center
StyleVisual stylingBold, Normal, Italic, Underline
Set asThe value to displayA method or formula
InfoTooltip textString
💡 Developer Pattern: When designing a new report, sketch the visual layout on paper first. Identify which parts you need (header, body, footer), which lines will repeat, and which fields each line contains. Only then write TDL. This saves hours of refactoring.
8

Variables & System Formulas

Custom variables, system formulas, and the mechanics of data flow in TDL

Variables are named storage for values. System Formulas are named expressions that TDL evaluates to compute values. Together, they are how TDL handles dynamic data — anything that depends on user input, current context, or calculated logic.

Types of Variables

TypePrefixPurpose
System Variable SV Built-in — from date, to date, current company, active user
System Formula $$ Built-in computed formula — $$AsAmount, $$String, $$Value
User Variable Any name Custom variable you declare in a report or form
Method Variable Any name Variable that holds a method reference

Declaring Custom Variables

[Report: MyReport]
    Form     : MyForm
    Variable : MyStartDate
    Variable : MyEndDate
    Variable : MyShowZero
    Set      : MyStartDate : $$SVFromDate
    Set      : MyEndDate : $$SVToDate
    Set      : MyShowZero : No

System Formulas — The Workhorses

System formulas always start with $$. They are called "system" because they are built into Tally. Some of the most used:

  • $$String:... — Convert a value to a string
  • $$Value:... — Get the current value of a field or variable
  • $$AsAmount:... — Format a number as an amount (with commas and decimals)
  • $$AsQty:... — Format a number as a quantity
  • $$IsLedger:... — Check if an object is a Ledger
  • $$IsSundryDebtor:... — Check if an object is a Sundry Debtor
  • $$IsSundryCreditor:... — Check if an object is a Sundry Creditor
  • $$Total:... — Sum a method across a collection
  • $$Count:... — Count objects in a collection
  • $$CollectionFieldByKey:... — Look up a field value in a collection

Naming Your Own Formulas

;; Define formulas in the System: Formulae section
[System: Formulae]
    IsHighValue : $ClosingBalance > 100000
    IsCurrentMonth : $Date >= $$SVFromDate AND $Date <= $$SVToDate
    GSTAmount : $Amount * 0.15
    IsSalesVoucher : $VoucherTypeName = "Sales"

Using Variables in a Report

[Report: HighValueReport]
    Form     : HighValueForm
    Variable : SVFromDate, SVToDate
    Variable : MinAmount
    Set      : MinAmount : 100000

[Collection: HighValueColl]
    Type   : Ledger
    Fetch  : Name, ClosingBalance
    Filter : $ClosingBalance > $$Value:MinAmount
💻 Debugging Variables: In TallyPrime Developer, you can set breakpoints and inspect the value of any variable at runtime. This is invaluable when your formula is not producing the expected result.
9

Functions — Reusable Logic in TDL

Built-in functions and user-defined functions for computations, validations, and transformations

Functions in TDL let you package reusable logic. Tally provides hundreds of built-in functions, and you can define your own User-Defined Functions to encapsulate business logic.

Built-in Function Categories

CategoryExamples
String Functions $$StringLength, $$StringFind, $$StringReplace, $$Upper, $$Lower, $$Trim
Number Functions $$Round, $$Ceiling, $$Floor, $$Abs, $$Power, $$Sqrt
Date Functions $$Year, $$Month, $$Day, $$DateDiff, $$AddDays
Format Functions $$AsAmount, $$AsQty, $$AsDate
Aggregate Functions $$Total, $$Count, $$Maximum, $$Minimum
Type Check Functions $$IsLedger, $$IsVoucher, $$IsEmpty
Object Functions $$Object, $$CollectionFieldByKey, $$Fetch

Defining a User Function

;; User-defined function that calculates GST
[Function: UDF_CalculateGST]
    Parameter : Amount : Number
    Parameter : Rate : Number
    Returns   : Number
    Returns   : $$Value:Amount * $$Value:Rate / 100

;; User-defined function that determines debit or credit
[Function: UDF_IsDebit]
    Parameter : EntryObj : Object : LedgerEntry
    Returns   : Logical
    Returns   : $EntryObj.IsDeemedPositive

Using User Functions

;; Call a user function from a formula
[System: Formulae]
    GSTOnAmount : #UDF_CalculateGST(10000, 15)

;; Call a user function from a field
[Field: TaxAmount]
    Set as : #UDF_CalculateGST($Amount, 15)
💻 Naming Convention: Prefix your User Functions with UDF_ or your company's short code (e.g., ABC_CalculateGST). This avoids collisions with Tally's built-in functions and with functions from other add-ons.

Recursion in User Functions

TDL supports recursive functions, which are especially useful for traversing hierarchical structures like Group → Ledger trees.

;; Recursive function to sum all balances in a Group
[Function: UDF_GroupBalance]
    Parameter : GroupObj : Object : Group
    Returns   : Number
    Returns   : $GroupObj.ClosingBalance

;; Note: This is a simplification. Real recursive group walking
;; requires walking child groups and ledgers separately.
⚠️ Recursion Limit: TDL has a recursion depth limit. If your recursive function goes too deep (e.g., a very deep group hierarchy), you may hit the limit and get an error. Structure your functions to be iterative where possible.
10

Actions & Events — The Behaviour Layer

Triggering logic on user interaction, voucher save, validation, and other events

Actions are blocks of behaviour that execute in response to Events. An event is something that happens — a user pressing a key, a voucher being saved, a report being entered. Actions respond to those events.

Common Events in TDL

EventWhen It FiresTypical Use
On EnterUser enters a report or formInitialise variables, load defaults
On ExitUser exits a report or formClean up, save state
On SaveVoucher is being savedValidate, compute fields, send notifications
On DeleteVoucher is being deletedPrevent deletion, log event
On AlterVoucher is being alteredTrack changes, validate modifications
On PrintVoucher is being printedCustom print layout, additional data
On KeyUser presses a keyKeyboard shortcuts
On ButtonUser clicks a buttonCustom button actions
On FocusField gains focusAuto-populate, validation

Defining an Action

[Action: MyOnSaveAction]
    Action  : Set : MyVariable : 1
    Action  : New Object : Voucher
    Action  : Display : "Voucher Saved Successfully"

[VoucherType: Sales]
    On : Save : MyOnSaveAction

Common Actions

  • Set — Assign a value to a variable
  • Display — Show a message dialog
  • New Object — Create a new object (Voucher, Ledger, etc.)
  • Alter Object — Modify an existing object
  • Delete Object — Delete an object
  • Open Report — Navigate to a report
  • Call — Execute a user-defined function
  • Export — Export data to file
  • Import — Import data from file
  • HTTP Post — Send an HTTP request to an external service
  • Log — Write to a log file
  • Break — Stop execution (for debugging)

Example — Voucher Save Validation

;; Prevent saving Sales vouchers above a threshold without approval
[Function: UDF_ValidateSalesAmount]
    Returns : Logical
    Returns : $$Value:$$Total:SalesAmount < 500000

[Action: ValidateSalesAmount]
    Action : Call : UDF_ValidateSalesAmount
    Action : Display : "Sales above 5 lakh requires manager approval"
    Action : Break

[VoucherType: Sales]
    On : Save : ValidateSalesAmount
💡 Real-World Use Cases for Actions:
• Send WhatsApp/SMS alerts when a high-value voucher is saved
• Auto-calculate commission on Sales vouchers
• Validate customer credit limit before saving a Sales invoice
• Push voucher data to an external ERP via HTTP POST
• Log user activity for audit purposes
• Auto-print invoices with custom layouts
11

User-Defined Fields (UDF)

Adding custom fields to masters and vouchers without modifying the core schema

UDF (User-Defined Fields) are custom fields you can add to Tally's masters and vouchers. This is how you extend Tally's data model to capture business-specific information — without modifying the underlying database.

Why UDF Matters

  • No schema migration: Add fields without touching Tally's core
  • Reportable: UDFs can be used in filters, sorts, and report columns
  • Integrable: UDFs can be pushed and pulled via XML/JSON
  • Safe: UDFs never break Tally's accounting logic
  • Customisable: Define per company or globally

Common UDF Scenarios

Object TypeExample UDFBusiness Use
Customer LedgerUDF_RegionRegion-wise sales reporting
Customer LedgerUDF_CreditScoreRisk-based credit limits
Supplier LedgerUDF_PaymentPriorityPayment scheduling
Stock ItemUDF_ShelfLifeExpiry tracking
Stock ItemUDF_ManufacturerBrand-wise analysis
Sales VoucherUDF_SalesPersonCommission calculation
Sales VoucherUDF_DeliveryDateLogistics tracking
Purchase VoucherUDF_ApprovedByApproval workflow

Defining a UDF

;; Define a UDF for Customer Ledger
[UDF: UDF_Region]
    Type       : String
    Length     : 20
    Report     : Ledger
    Tabular    : Yes
    Display    : Region

[UDF: UDF_CreditScore]
    Type       : Number
    Report     : Ledger
    Tabular    : Yes
    Display    : Credit Score

UDF Attributes Explained

AttributePurposeValues
TypeData typeString, Number, Date, Logical, Amount, Quantity
LengthMaximum length (for String)A number
ReportWhat master the UDF belongs toLedger, StockItem, Voucher, etc.
TabularShow in list viewYes / No
DisplayLabel shown to userA string
Set asDefault valueAny value

Using UDF in Reports

[Collection: CustomerColl]
    Type   : Ledger
    Fetch  : Name, ClosingBalance, UDF_Region
    Filter : $UDF_Region = "Dhaka"

[Field: RegionField]
    Set as : $UDF_Region

UDF via XML Integration

<LEDGER NAME="Rahim Traders" ACTION="Create">
    <PARENT>Sundry Debtors</PARENT>
    <UDF:REGION>Dhaka</UDF:REGION>
    <UDF:CREDITSCORE>750</UDF:CREDITSCORE>
</LEDGER>
💻 UDF XML Naming: UDFs in XML use the format <UDF:FIELDNAME>. The field name is the UDF name you defined, with the UDF_ prefix included. So UDF_Region becomes <UDF:REGION> in XML.
🔧
Real-World Power: A pharma distributor used UDFs to add Batch Number, Expiry Date, and Storage Condition to stock items — enabling expiry alerts and regulatory compliance reports, all without modifying Tally's core.
12

Building a Custom Report — Complete Walkthrough

Step-by-step from requirement to working TDL report

Let us build a real custom report from scratch. Requirement: A report showing all Sundry Debtors with their region (from UDF), outstanding balance, and a high-value flag for balances over ৳1,00,000.

Step 1 — Design the Visual Layout

📐 Layout Plan
  • Header: Report title and date range
  • Column Headers: Customer Name, Region, Outstanding, Flag
  • Data Rows: One row per Sundry Debtor
  • Total Row: Sum of all outstanding balances
  • Footer: Count of high-value customers

Step 2 — Define the UDF (if not already existing)

[UDF: UDF_Region]
    Type    : String
    Length  : 20
    Report  : Ledger
    Tabular : Yes
    Display : Region

Step 3 — Define the Collection

[Collection: DebtorColl]
    Type    : Ledger
    Fetch   : Name, ClosingBalance, UDF_Region, Parent
    Filter  : IsSundryDebtor
    Sort    : -$ClosingBalance

[System: Formulae]
    IsSundryDebtor : $Parent = "Sundry Debtors"

Step 4 — Define the Report

[Report: DebtorRegionReport]
    Form     : DebtorRegionForm
    Title    : "Debtor Region Report"
    Variable : SVFromDate, SVToDate
    Variable : SVCurrentCompany

Step 5 — Define the Form

[Form: DebtorRegionForm]
    Part   : DebtorRegionHeader, DebtorRegionBody
    Width  : 100 % Screen
    Height : 100 % Screen
    Key    : F2 : ChangeDate
    Key    : F4 : ChangePeriod

Step 6 — Define the Parts

[Part: DebtorRegionHeader]
    Lines   : DebtorHeaderLine
    Border  : Thin Bottom

[Part: DebtorRegionBody]
    Lines   : DebtorDataLine, DebtorTotalLine
    Repeat  : DebtorDataLine : DebtorColl
    Scroll  : Vertical

Step 7 — Define the Lines

[Line: DebtorHeaderLine]
    Fields : HeaderName, HeaderRegion, HeaderBalance, HeaderFlag
    Local  : Field : HeaderName     : Set as : "Customer Name"
    Local  : Field : HeaderRegion   : Set as : "Region"
    Local  : Field : HeaderBalance  : Set as : "Outstanding"
    Local  : Field : HeaderFlag     : Set as : "High Value"
    Style  : Bold

[Line: DebtorDataLine]
    Fields : DataName, DataRegion, DataBalance, DataFlag
    Local  : Field : DataName    : Set as : $Name
    Local  : Field : DataRegion  : Set as : $UDF_Region
    Local  : Field : DataBalance : Set as : $ClosingBalance
    Local  : Field : DataFlag    : Set as : $$String:$$IsHighValue
    Border : Thin Bottom

[System: Formulae]
    IsHighValue : $ClosingBalance > 100000

[Line: DebtorTotalLine]
    Fields : TotalLabel, TotalValue
    Local  : Field : TotalLabel : Set as : "Total:"
    Local  : Field : TotalValue : Set as : $$AsAmount:$$Total:DebtorColl:$ClosingBalance
    Style  : Bold

Step 8 — Define the Fields

[Field: DataName]
    Use   : Name Field
    Width : 40 % Screen
    Align : Left

[Field: DataRegion]
    Width : 20 % Screen
    Align : Center

[Field: DataBalance]
    Use   : Amount Field
    Width : 25 % Screen
    Align : Right

[Field: DataFlag]
    Width : 15 % Screen
    Align : Center

Step 9 — Add to Menu

[Menu: Display More Reports]
    Add : Item : DebtorRegionReport : DebtorRegionReport
💡 Testing Your Report:
1. Save the TDL file
2. In TallyPrime: F1 → Settings → TDLs & Add-Ons → Load TDL
3. Select your file
4. Navigate to Gateway of Tally → Display More Reports
5. Your custom report should appear
💻 Production Tips:
• Test with real data before deploying
• Add error handling for missing UDF values
• Use $$IsEmpty to safely handle null values
• Version your TDL files (v1.0, v1.1, etc.)
• Document every report with comments
13

Building a Custom Invoice — Full Print Layout

Customising invoice print layout with logo, QR code, dynamic fields, and tax summary

Custom invoice layouts are one of the most valuable uses of TDL. Tally's built-in invoice formats cover standard cases, but businesses often need custom layouts — with logos, QR codes, specific tax summaries, terms and conditions, and industry-specific fields.

What You Can Customise

  • Header: Company logo, name, address, GST/VAT numbers
  • Customer Block: Name, address, contact, BIN, shipping details
  • Invoice Details: Number, date, PO reference, delivery date
  • Item Table: Custom columns — HSN, batch, expiry, MRP
  • Tax Summary: Rate-wise tax breakup, CGST/SGST or VAT
  • Totals: Subtotal, discount, tax, round-off, grand total
  • Amount in Words: Auto-conversion using system formula
  • QR Code: UPI payment QR or e-invoice QR
  • Footer: Terms, bank details, signature, declaration
  • Barcode: Product-wise barcode for scanner support

The Invoice Form Structure

[Form: MyCustomInvoiceForm]
    Part   : CompanyHeader, CustomerBlock, InvoiceDetails,
             ItemTable, TaxSummary, GrandTotal,
             AmountInWords, FooterTerms, SignatureBlock
    Width  : 100 % Screen
    Height : 100 % Screen
    Print  : Yes
    Page   : A4
    Margin : Left : 10 mm, Right : 10 mm, Top : 10 mm, Bottom : 10 mm

Adding a Logo to the Invoice

[Part: CompanyHeader]
    Lines : LogoLine, CompanyNameLine
    Border : Thin Bottom

[Line: LogoLine]
    Fields : LogoImage
    Local  : Field : LogoImage : Set as : "C:\\TallyAddons\\logo.png"
    Local  : Field : LogoImage : Width : 40 mm
    Local  : Field : LogoImage : Height : 20 mm

[Line: CompanyNameLine]
    Fields : CompanyName, CompanyAddress
    Local  : Field : CompanyName : Set as : "ABC Electronics Ltd."
    Local  : Field : CompanyName : Style : Bold
    Local  : Field : CompanyName : Align : Center

The Item Table with Custom Columns

[Part: ItemTable]
    Lines : ItemHeaderLine, ItemDataLine
    Repeat : ItemDataLine : ItemColl

[Line: ItemHeaderLine]
    Fields : HSNHeader, ItemHeader, QtyHeader, RateHeader, AmountHeader
    Local  : Field : HSNHeader    : Set as : "HSN"
    Local  : Field : ItemHeader   : Set as : "Item Description"
    Local  : Field : QtyHeader    : Set as : "Qty"
    Local  : Field : RateHeader   : Set as : "Rate"
    Local  : Field : AmountHeader : Set as : "Amount"
    Style  : Bold
    Border : Thin Bottom

[Line: ItemDataLine]
    Fields : HSNData, ItemData, QtyData, RateData, AmountData
    Local  : Field : HSNData    : Set as : $StockItemHSNCode
    Local  : Field : ItemData   : Set as : $StockItemName
    Local  : Field : QtyData    : Set as : $$AsQty:$ActualQty
    Local  : Field : RateData   : Set as : $$AsAmount:$Rate
    Local  : Field : AmountData : Set as : $$AsAmount:$Amount

[Collection: ItemColl]
    Type : StockItem
    Fetch : StockItemName, StockItemHSNCode, ActualQty, Rate, Amount

Adding a QR Code

[Line: QRCodeLine]
    Fields : PaymentQR
    Local  : Field : PaymentQR : Set as : #UDF_GenerateUPIQR("ABC Electronics", "abc@upi", $$Value:GrandTotal)

[Function: UDF_GenerateUPIQR]
    Parameter : Payee : String
    Parameter : UPIID : String
    Parameter : Amount : Number
    Returns   : String
    Returns   : "upi://pay?pa=" + $$Value:UPIID + "&pn=" + $$Value:Payee + "&am=" + $$String:$$Value:Amount

Amount in Words

[Line: AmountInWordsLine]
    Fields : AmountInWords
    Local  : Field : AmountInWords : Set as : $$NumberToWords:$GrandTotal
    Local  : Field : AmountInWords : Style : Italic

Tax Summary

[Line: TaxRateHeader]
    Fields : TaxRateLabel, TaxableLabel, CGSTLabel, SGSTLabel, TotalLabel

[Line: TaxRateData]
    Fields : TaxRate, TaxableAmt, CGSTAmt, SGSTAmt, TotalTaxAmt
    Local  : Field : CGSTAmt : Set as : $$AsAmount:$$Total:TaxColl:$CGST
    Local  : Field : SGSTAmt : Set as : $$AsAmount:$$Total:TaxColl:$SGST
💡 TDL Invoice Testing: Print to PDF first. Check alignment, fonts, page breaks, and dynamic content. Once satisfied, deploy. Always keep a backup of the original invoice TDL so you can revert if needed.
⚠️ Print Testing Checklist:
• Test with long customer names and addresses
• Test with many items (page breaks)
• Test with zero-quantity or zero-rate items
• Test with negative amounts (returns)
• Test on A4, A5, and thermal printer sizes
• Verify QR code scans correctly
• Verify amount in words is correct
14

Deployment, Distribution & FAQ

From tested TDL to production deployment — plus answers to common questions

Compiling TDL to .tcp

For distribution, TDL files should be compiled into .tcp (Tally Compiled Program) format. This protects your source code and simplifies deployment.

🛠️ Compilation Steps
  1. Open TallyPrime Developer
  2. Load your .tdl file
  3. Go to File → Compile or press Ctrl+F9
  4. Choose the output folder for the .tcp file
  5. Test the compiled file in a fresh TallyPrime instance

Distribution Options

MethodBest ForProsCons
Direct .tdl file In-house customisation Easy to modify, source visible Source not protected
Compiled .tcp file Commercial add-ons Source protected, compact Cannot be modified
Installer package Wide distribution Professional, easy install Requires installer tooling
Cloud-loaded TDL SaaS-style add-ons Automatic updates Requires server infrastructure

Best Practices & Pitfalls

✓ Best Practices
  • Version-control every TDL file
  • Test in a separate company before production
  • Comment every function and report
  • Use prefixes (UDF_, ABC_) to avoid collisions
  • Keep TDL files small and modular
  • Handle empty/null values with $$IsEmpty
  • Provide clear error messages
  • Document installation steps
  • Test on target TallyPrime version
  • Backup original before deploying
✗ Common Pitfalls
  • Modifying Tally's built-in reports directly
  • Using reserved keywords as names
  • Hardcoding paths to files (use config)
  • Ignoring version compatibility
  • Forgetting to test across users/companies
  • Skipping null value handling
  • Not testing print layouts
  • Deploying without backup
  • No error logging in production
  • Abandoning TDL without documentation

TDL Learning Resources

  • TallyPrime Developer: The built-in IDE with definition search — your best learning tool
  • Definition Search: Look up any built-in report to see how Tally defines it
  • Tally Developer Reference: Official documentation from Tally Solutions
  • TDL Forums: Community forums and Stack Overflow
  • GitHub: Search for open-source TDL projects
  • Practice: The only way to truly learn TDL is to build real reports

Frequently Asked Questions

❓
Do I need programming experience to learn TDL?
▾

Basic programming understanding helps, but TDL is a declarative language with a gentle learning curve. If you understand accounting concepts and can think logically about data relationships, you can learn TDL. The syntax is declaration-based rather than procedural.

❓
Can TDL modify Tally's core accounting logic?
▾

No. TDL extends Tally without modifying its core. You can add reports, fields, and behaviours, but you cannot change how Tally calculates debits and credits, or how it stores data. This is by design — it protects data integrity.

❓
What is the difference between TDL and TallyPrime Developer?
▾

TDL is the language. TallyPrime Developer is the IDE (Integrated Development Environment) used to write, test, and debug TDL code. TallyPrime Developer was formerly known as Tally Developer.

❓
How do I debug a TDL report that is not showing data?
▾

Common causes:

  • Collection filter is returning no objects
  • Field Set as is not retrieving the right method
  • Date range excludes all data
  • Report is not loaded (check TDLs & Add-Ons)
  • Menu is not properly linked

Use TallyPrime Developer's debugger to set breakpoints and inspect variables.

❓
Is TDL still relevant with cloud-based ERPs?
▾

Yes, more relevant than ever. TallyPrime's entire customisation framework runs on TDL. All third-party Tally add-ons, custom reports, and advanced integrations are built using TDL. As long as TallyPrime is used by businesses, TDL will be the way to extend it.

❓
Can TDL connect to external databases?
▾

Yes. TDL can act as an ODBC client, fetching data from external databases via SQL queries. This enables importing customer, stock, or other data from external systems into Tally. TDL also supports HTTP POST for calling external web services.

❓
How many TDL definitions does TallyPrime ship with?
▾

Thousands. TallyPrime contains 500+ built-in reports, each defined with numerous sub-definitions (forms, parts, lines, fields, functions). The total count of TDL definitions in TallyPrime exceeds 10,000 — all accessible via Definition Search in TallyPrime Developer.

The Complete TDL Learning Path

  1. Week 1 — Foundation: Understand TDL syntax, definitions, attributes, values
  2. Week 2 — Objects & Collections: Master the object model, methods, and collection mechanics
  3. Week 3 — Reports & UI: Build simple reports, forms, parts, lines, fields
  4. Week 4 — Functions & Actions: Write user functions, handle events, trigger actions
  5. Week 5 — UDF & Data Extension: Add user-defined fields to masters and vouchers
  6. Week 6 — Custom Invoice: Build a complete customised invoice layout
  7. Week 7 — Integration: Connect TDL to XML, JSON, ODBC, and HTTP
  8. Week 8 — Production: Compile, deploy, test, document, distribute
🚀
Career Insight: TDL developers are in high demand across India, Bangladesh, and Southeast Asia. Skilled TDL developers can earn 30-50% more than average ERP consultants because the skill is rare and the demand is consistent across thousands of Tally add-on companies.

Series Roadmap — 45 Parts to CFO-Level Mastery

You have completed 22 parts of the TallyPrime Deep-Dive Masterclass. Here is the complete roadmap.

01 Foundation
02 History
03 vs ERP
04 Company
05 COA
06 Double Entry
07 Vouchers
08 Sales
09 Purchase
10 Inventory
11 Inv+Acct
12 AR/AP
13 Banking
14 Tax
15 Cost Centre
16 Payroll
17 Manufacturing
18 Financial Stmt
19 MIS
20 CFO
21 Security
22 Architecture
23 TDL 🔧
24 XML
25 JSON/ODBC
26 Custom ERP
27 Tally Engine
28 Enterprise
29 Tally+AI+BI
30 Case Study
31-45 Advanced
🎯 What's Next: Part 24 — XML Integration Deep Dive takes you into the developer-level details of TallyPrime's XML interface. You will learn the complete XML envelope structure, how to create vouchers, ledgers, stock items, and reports via XML, error handling, and building a complete integration service. Then Part 25 covers JSON and ODBC integration, and Parts 26-30 cover custom ERP integration, enterprise architecture, AI, and the grand case study.

Post a Comment

0 Comments