When you wonder what are x++ compile time functions used for you can check with Dynamics Edge d365fo training courses such as Microsoft MB-700 solution architect for more in depth instruction. For now check out the below free guide as an introduction to what they are and what they can be used for.

What Are Compile-Time Functions in X++?
Compile-time functions in Dynamics 365 Finance and Operations X++ development and programming refer to special functions that execute during the compilation phase (not at runtime). They are used to validate references to application metadata (like classes, tables, fields, etc.) at compile time and to retrieve information (such as an object’s name or ID) in a safe way. In practice, this means you should use compile-time functions instead of hard-coded strings or IDs when referring to metadata objects. By doing so, the X++ compiler will verify the existence of the referenced object during build. If the referenced object (class, table, field, etc.) doesn’t exist or is spelled incorrectly, you get a compile-time error rather than a runtime failure. This makes your code more robust to changes in the application’s model (for example, if an element is renamed or removed, you’ll be alerted at compile time). Another benefit is that these functions have no runtime cost – they are resolved by the compiler and are not present in the generated execution code. (Note that compile-time functions require literal identifiers as arguments – you cannot pass a variable or string to them, since the compiler must know the exact metadata element at compile time.)
Common Compile-Time Functions in X++
Below are some of the most commonly used compile-time functions in X++ (Dynamics 365 Finance & Operations), along with their purpose and a brief example of each in use:
classStr
Returns the name of a class as a string, with compile-time verification that the class exists. In other words, classStr(ClassName)
yields the class’s name (e.g. "ClassName"
) and will cause a compile error if no such class is found. This is safer than using a hard-coded class name string, because any typo or missing class is caught during compilation.
Example: Using classStr
to get the name of the Global class:
str className = classStr(Global);
Global::info(strfmt("Class name: %1", className));
// Infolog: "Class name: Global"
classNum
Retrieves the unique ID of a class (the class’s numeric identifier in the system). Like classStr
, this compile-time function ensures the class exists at compile time. It returns an integer (the class ID) which can be useful for certain reflection or framework calls.
Example: Using classNum
to get the class ID of the Global class:
int classId = classNum(Global);
Global::info(strfmt("Class ID of Global: %1", classId));
// Infolog (for example): "Class ID of Global: 2"
methodStr
Verifies that a given method exists on a specified class and returns the method’s name as a string. The syntax is methodStr(ClassName, methodName)
, where methodName
is provided as an identifier (not a quoted string). If the method is not found in that class, the compiler will throw an error. This is useful for getting method names (for logging, dynamic invocation, etc.) while ensuring the method actually exists in the class.
Example: Using methodStr
to get the name of the timeout
method on the SysHelpInitTimeOut
class:
str methodName = methodStr(SysHelpInitTimeOut, timeout);
Global::info(strfmt("Method name: %1", methodName));
// Infolog: "Method name: timeout"
tableStr
Returns the name of a table as a string, with compile-time validation that the table exists. Instead of writing a table name as a literal string, use tableStr(TableName)
to get a string "TableName"
. This guarantees the table is present in the model – if not, you’ll get a compile error. It’s commonly used when you need the table name dynamically (for example, constructing queries or messages).
Example: Using tableStr
to get the name of the CustTable
table:
str tableName = tableStr(CustTable);
Global::info(strfmt("Table name: %1", tableName));
// Infolog: "Table name: CustTable"
tableNum
Retrieves the ID of a specified table (the table’s numeric identifier) at compile time. Calling tableNum(TableName)
returns an integer representing the table’s ID. This will fail to compile if the table doesn’t exist. tableNum
is often used in lower-level operations or interfacing with kernel APIs that require a table ID.
Example: Using tableNum
to get the ID of the CustTable
table:
int tableId = tableNum(CustTable);
Global::info(strfmt("Table ID of CustTable: %1", tableId));
// Infolog (for example): "Table ID of CustTable: 77"
fieldStr
Returns the name of a field as a string, given a table and field reference, and ensures that field exists on that table. The syntax is fieldStr(TableName, FieldName)
. This is preferable to using a raw string for a field name because the compiler will verify that the field is valid for the specified table. It’s commonly used when building dynamic queries, filters, or for logging field names.
Example: Using fieldStr
to get the name of the CashDisc field on CustTable:
str fieldName = fieldStr(CustTable, CashDisc);
Global::info(strfmt("Field name: %1", fieldName));
// Infolog: "Field name: CashDisc"
fieldNum
Retrieves the numeric ID of a field in a given table. The call fieldNum(TableName, FieldName)
returns an integer (field ID) and will not compile if the field doesn’t exist on that table. This is often used when interfacing with functions that require field IDs or for low-level operations (such as constructing query ranges or field maps by ID).
Example: Using fieldNum
to get the field ID of CashDisc in CustTable:
int fieldId = fieldNum(CustTable, CashDisc);
Global::info(strfmt("Field ID of CustTable.CashDisc: %1", fieldId));
// Infolog: "Field ID of CustTable.CashDisc: 10" // (example ID)
extendedTypeStr
(and extendedTypeNum
)
extendedTypeStr
returns the name of an Extended Data Type (EDT) as a string, and ensures that the EDT exists in the AOT metadata. Similarly, extendedTypeNum(EDTName)
returns the unique numeric ID of that EDT. These functions are used when you need to refer to an EDT programmatically (for example, getting the type’s name or checking an EDT in code) and want compile-time safety.
Example: Using extendedTypeStr
and extendedTypeNum
for the AccountName
EDT:
int typeId = extendedTypeNum(AccountName);
str typeName = extendedTypeStr(AccountName);
Global::info(strfmt("EDT name: %1, EDT ID: %2", typeName, typeId));
// Infolog: "EDT name: AccountName, EDT ID: 2251" // (example ID)
(In the above example, AccountName is an EDT in the system. The code will not compile if AccountName
EDT does not exist.)
Note: There are many other compile-time functions in X++ for various artifact types (for example,
formStr
for forms,menuItemDisplayStr
for menu items,queryStr
for queries,enumStr
for enums, etc.). The ones listed above are among the most frequently used. All of them follow the same pattern: they validate the existence of the specified object at compile time and return either its name or ID.
Best Practices for Using Compile-Time Functions
- Use them whenever possible for metadata references: It’s a recommended practice to use compile-time functions instead of hard-coded names/IDs for classes, tables, fields, and other AOT elements. This ensures your code breaks at compile time if something changes, rather than failing unpredictably at runtime. As Microsoft’s documentation states, this approach “is better than discovering the error later during run time”.
- Write safer and resilient code: By leveraging compile-time functions, your X++ code becomes more robust to customization and upgrades. If a developer renames or removes an element (say a field or class), any reference via a compile-time function will immediately flag an error during build. This early feedback helps maintain correctness as the application evolves.
- No performance overhead: Don’t hesitate to use these functions thinking they might slow down execution. They are resolved at compile time and have no effect at run time. The compiler replaces the calls with the actual name or ID constants, so using
tableStr(MyTable)
is just as efficient as using the literal"MyTable"
at runtime – with the added safety of compile-time checking. - Remember the literal requirement: All arguments to compile-time functions must be literal identifiers (not quotes or variables). For example, use
tableStr(CustTable)
nottableStr("CustTable")
. The latter would not compile, because the compiler needs a direct reference to verify. This is by design – the compiler can only check things that are known at compile time. In scenarios where you truly need dynamic names at runtime, compile-time functions can’t be used (you would fallback to other mechanisms, but you lose the compile-time checking in those cases).
By adhering to these practices, Dynamics 365 F&O developers can reduce bugs and ensure that their X++ code is tightly bound to the application’s metadata, catching mistakes early in the development cycle. Using compile-time functions like those above is considered a standard best practice in D365 F&O development for safer and more maintainable code.
Have a Question ?
Fill out this short form, one of our Experts will contact you soon.
Call Us Today For Your Free Consultation
Call Now