# Dynamics NAV C/AL Record and Report Functions Cheat Sheet

> Source: https://netray.co/tools/dynamics-nav-c-al-record-functions-reference
> Publisher: Netray (netray.co), AI and integration engineering for ERP and manufacturing systems.
> Section: Free Tools

## Short answer

The C/AL functions NAV 2013 to 2018 developers type every day, with signature, one-line example, AL equivalent and the gotcha that bites.

## Category

microsoft-dynamics

## Erp

Microsoft Dynamics NAV

## Audience

- NAV developer
- Business Central developer

## Tool

**Kind:** worksheet

**Mode:** reference

**Columns:**

#### Function (signature)

**Key:** function

**Type:** text

#### Group

**Key:** group

**Type:** text

#### C/AL example

**Key:** example

**Type:** text

#### AL (BC on-prem)

**Key:** alEquivalent

**Type:** text

#### Gotcha

**Key:** gotcha

**Type:** text

**Rows:**

**Function:** Rec.SETRANGE(Field [, From] [, To])

**Group:** Filtering

**Example:** SalesLine.SETRANGE("Document No.",SalesHeader."No.");

**Al Equivalent:** SetRange

**Gotcha:** SETRANGE(Field) with no values removes the filter on that field

**Function:** Rec.SETFILTER(Field, String [, Value1, ...])

**Group:** Filtering

**Example:** Cust.SETFILTER("No.",'%1..%2|%3','10000','20000','30000');

**Al Equivalent:** SetFilter

**Gotcha:** Use %1 placeholders, not concatenation, or values with & | ( break the filter

**Function:** Filter operators

**Group:** Filtering

**Example:** Item.SETFILTER(Description,'@*bolt*'); Item.SETFILTER("Unit Cost",'>%1&<%2',10,50);

**Al Equivalent:** Same syntax

**Gotcha:** .. range, | or, & and, <> not equal, * wildcard, @ case-insensitive

**Function:** Rec.GETFILTERS

**Group:** Filtering

**Example:** MESSAGE(Cust.GETFILTERS);

**Al Equivalent:** GetFilters

**Gotcha:** Returns a readable string; GETFILTER(Field) returns one field's filter

**Function:** Rec.RESET

**Group:** Filtering

**Example:** SalesLine.RESET;

**Al Equivalent:** Reset

**Gotcha:** Clears filters and resets the key to the primary key

**Function:** Rec.SETCURRENTKEY(Field1 [, Field2, ...])

**Group:** Filtering

**Example:** CustLedgEntry.SETCURRENTKEY("Customer No.","Posting Date");

**Al Equivalent:** SetCurrentKey

**Gotcha:** In NAV the key must exist in the table's key list, or a runtime error occurs

**Function:** Rec.FINDSET([ForUpdate] [, UpdateKey])

**Group:** Reading

**Example:** IF SalesLine.FINDSET THEN REPEAT ... UNTIL SalesLine.NEXT = 0;

**Al Equivalent:** FindSet

**Gotcha:** Use FINDSET(TRUE) if you modify records in the loop; FINDSET(TRUE,TRUE) if you change key fields

**Function:** Rec.FINDFIRST

**Group:** Reading

**Example:** IF SalesLine.FINDFIRST THEN ...

**Al Equivalent:** FindFirst

**Gotcha:** Use only when you need one record; do not loop after FINDFIRST

**Function:** Rec.FINDLAST

**Group:** Reading

**Example:** IF GLEntry.FINDLAST THEN NextEntryNo := GLEntry."Entry No." + 1;

**Al Equivalent:** FindLast

**Gotcha:** Respects current key and filters

**Function:** Rec.FIND([Which])

**Group:** Reading

**Example:** IF Item.FIND('-') THEN ...

**Al Equivalent:** Find

**Gotcha:** Legacy pattern; FINDSET is cheaper for forward loops

**Function:** Rec.NEXT([Steps])

**Group:** Reading

**Example:** UNTIL SalesLine.NEXT = 0;

**Al Equivalent:** Next

**Gotcha:** Returns steps moved; 0 means end of set

**Function:** Rec.GET([Value1, ...])

**Group:** Reading

**Example:** Cust.GET('10000');

**Al Equivalent:** Get

**Gotcha:** Ignores filters; errors if not found unless you use IF Cust.GET(...) THEN

**Function:** Rec.ISEMPTY

**Group:** Reading

**Example:** IF SalesLine.ISEMPTY THEN ERROR('No lines');

**Al Equivalent:** IsEmpty

**Gotcha:** Cheaper than COUNT = 0

**Function:** Rec.COUNT

**Group:** Reading

**Example:** NoOfLines := SalesLine.COUNT;

**Al Equivalent:** Count

**Gotcha:** Exact count on SQL; COUNTAPPROX is cheaper for progress bars

**Function:** Rec.CALCFIELDS(Field1 [, Field2, ...])

**Group:** FlowFields

**Example:** Cust.CALCFIELDS("Balance (LCY)");

**Al Equivalent:** CalcFields

**Gotcha:** FlowFields are 0 or empty until calculated; also needed for BLOB fields

**Function:** Rec.CALCSUMS(Field1 [, Field2, ...])

**Group:** FlowFields

**Example:** CustLedgEntry.CALCSUMS(Amount);

**Al Equivalent:** CalcSums

**Gotcha:** Sums a normal decimal field over the filtered set

**Function:** Rec.SETAUTOCALCFIELDS([Field1, ...])

**Group:** FlowFields

**Example:** Cust.SETAUTOCALCFIELDS("Balance (LCY)");

**Al Equivalent:** SetAutoCalcFields

**Gotcha:** NAV 2013+. Calculates on each FIND/NEXT, avoiding one CALCFIELDS per row

**Function:** Rec.INIT

**Group:** Writing

**Example:** SalesLine.INIT;

**Al Equivalent:** Init

**Gotcha:** Does not clear primary key fields

**Function:** Rec.INSERT([RunTrigger])

**Group:** Writing

**Example:** SalesLine.INSERT(TRUE);

**Al Equivalent:** Insert

**Gotcha:** Without TRUE the OnInsert trigger does not run

**Function:** Rec.MODIFY([RunTrigger])

**Group:** Writing

**Example:** Item.MODIFY(TRUE);

**Al Equivalent:** Modify

**Gotcha:** MODIFY without TRUE skips OnModify, so last-modified dates and related logic do not run

**Function:** Rec.MODIFYALL(Field, NewValue [, RunTrigger])

**Group:** Writing

**Example:** SalesLine.MODIFYALL("Shipment Date",NewDate);

**Al Equivalent:** ModifyAll

**Gotcha:** Does not run OnValidate for the field

**Function:** Rec.DELETE([RunTrigger])

**Group:** Writing

**Example:** SalesHeader.DELETE(TRUE);

**Al Equivalent:** Delete

**Gotcha:** Without TRUE related lines are not deleted by OnDelete

**Function:** Rec.DELETEALL([RunTrigger])

**Group:** Writing

**Example:** TempBuffer.DELETEALL;

**Al Equivalent:** DeleteAll

**Gotcha:** Deletes the filtered set; check filters first

**Function:** Rec.LOCKTABLE

**Group:** Writing

**Example:** GLEntry.LOCKTABLE;

**Al Equivalent:** LockTable

**Gotcha:** Locks until COMMIT or end of the transaction

**Function:** Rec.TRANSFERFIELDS(FromRecord [, InitPrimaryKeyFields])

**Group:** Writing

**Example:** SalesInvLine.TRANSFERFIELDS(SalesLine);

**Al Equivalent:** TransferFields

**Gotcha:** Copies by field number, not name; custom fields need matching numbers

**Function:** Rec.VALIDATE(Field [, NewValue])

**Group:** Validation

**Example:** SalesLine.VALIDATE(Quantity,5);

**Al Equivalent:** Validate

**Gotcha:** Runs OnValidate; a direct assignment does not

**Function:** Rec.TESTFIELD(Field [, Value])

**Group:** Validation

**Example:** SalesHeader.TESTFIELD(Status,SalesHeader.Status::Open);

**Al Equivalent:** TestField

**Gotcha:** Without Value it errors if the field is empty or zero

**Function:** Rec.FIELDERROR(Field [, Text])

**Group:** Validation

**Example:** SalesLine.FIELDERROR(Quantity,'must be positive');

**Al Equivalent:** FieldError

**Gotcha:** Message includes the field caption and record key

**Function:** ERROR(String [, Value1, ...])

**Group:** Validation

**Example:** ERROR('Item %1 is blocked.',Item."No.");

**Al Equivalent:** Error

**Gotcha:** Rolls back the whole write transaction

**Function:** CONFIRM(String [, Default] [, Value1, ...])

**Group:** Validation

**Example:** IF NOT CONFIRM('Post %1?',FALSE,SalesHeader."No.") THEN EXIT;

**Al Equivalent:** Confirm

**Gotcha:** Fails under NAS or web services; check GUIALLOWED first

**Function:** MESSAGE(String [, Value1, ...])

**Group:** Validation

**Example:** MESSAGE('%1 lines updated.',Counter);

**Al Equivalent:** Message

**Gotcha:** Shown after the trigger ends, not immediately

**Function:** GUIALLOWED

**Group:** Validation

**Example:** IF GUIALLOWED THEN Window.OPEN('#1####');

**Al Equivalent:** GuiAllowed

**Gotcha:** FALSE in job queue, NAS and web service sessions

**Function:** COMMIT

**Group:** Writing

**Example:** COMMIT;

**Al Equivalent:** Commit

**Gotcha:** Required before RUNMODAL of a page or report inside a write transaction

**Function:** REPORT.RUN(Number [, ReqWindow] [, SystemPrinter] [, Record])

**Group:** Reports and pages

**Example:** REPORT.RUN(REPORT::"Sales - Invoice",TRUE,FALSE,SalesInvHeader);

**Al Equivalent:** Report.Run

**Gotcha:** Record passes its filters to the report's first data item

**Function:** REPORT.RUNMODAL(Number [, ReqWindow] [, SystemPrinter] [, Record])

**Group:** Reports and pages

**Example:** REPORT.RUNMODAL(206,FALSE,FALSE,SalesInvHeader);

**Al Equivalent:** Report.RunModal

**Gotcha:** Waits for the report to finish; needs COMMIT first inside a write transaction

**Function:** ReportVar.SETTABLEVIEW(Record) + USEREQUESTPAGE(Bool) + RUNMODAL

**Group:** Reports and pages

**Example:** SalesInvRpt.SETTABLEVIEW(SalesInvHeader); SalesInvRpt.USEREQUESTPAGE(FALSE); SalesInvRpt.RUNMODAL;

**Al Equivalent:** SetTableView, UseRequestPage, RunModal

**Gotcha:** Use a report variable when you also need to set globals through a custom function

**Function:** REPORT.SAVEASPDF(Number, FileName [, Record])

**Group:** Reports and pages

**Example:** REPORT.SAVEASPDF(206,'C:\Temp\SI-103001.pdf',SalesInvHeader);

**Al Equivalent:** Report.SaveAsPdf (OnPrem) or Report.SaveAs to an OutStream

**Gotcha:** Runs on the service tier; the path is on the server, not the client

**Function:** PAGE.RUNMODAL(Number [, Record] [, Field])

**Group:** Reports and pages

**Example:** IF PAGE.RUNMODAL(PAGE::"Customer List",Cust) = ACTION::LookupOK THEN ...

**Al Equivalent:** Page.RunModal = Action::LookupOK

**Gotcha:** Returns an Action; only LookupOK means the user picked a record

**Function:** CODEUNIT.RUN(Number [, Record])

**Group:** Reports and pages

**Example:** IF NOT CODEUNIT.RUN(CODEUNIT::"Sales-Post",SalesHeader) THEN MESSAGE(GETLASTERRORTEXT);

**Al Equivalent:** Codeunit.Run

**Gotcha:** Using the return value traps errors, but it cannot be called that way inside an open write transaction: COMMIT first

**Function:** STRSUBSTNO(String [, Value1, ...])

**Group:** Text

**Example:** Txt := STRSUBSTNO('%1 - %2',Cust."No.",Cust.Name);

**Al Equivalent:** StrSubstNo

**Gotcha:** Values are formatted with FORMAT, so decimals follow regional settings

**Function:** STRPOS(String, SubString)

**Group:** Text

**Example:** Pos := STRPOS(ExtDocNo,'-');

**Al Equivalent:** StrPos or Text.IndexOf

**Gotcha:** 1-based, case-sensitive, 0 if not found

**Function:** COPYSTR(String, Position [, Length])

**Group:** Text

**Example:** Prefix := COPYSTR(ExtDocNo,1,Pos - 1);

**Al Equivalent:** CopyStr

**Gotcha:** Common use: COPYSTR(Txt,1,MAXSTRLEN(Field)) to avoid overflow errors

**Function:** INCSTR(String)

**Group:** Text

**Example:** NextNo := INCSTR('PO-0099');

**Al Equivalent:** IncStr

**Gotcha:** Returns empty text if the string has no number

**Function:** DELCHR(String [, Where] [, Which])

**Group:** Text

**Example:** Clean := DELCHR(Phone,'=',' -()');

**Al Equivalent:** DelChr

**Gotcha:** Which is a set of characters, not a substring

**Notes:** Signatures follow the NAV 2018 C/AL reference. In AL the same methods exist with PascalCase names; report and page IDs can be referenced by name, e.g. Report::"Sales - Invoice". Report 206 is Sales - Invoice in the W1 database; NAV 2017 and 2018 report selections default to report 1306 Standard Sales - Invoice, and localized databases may use another invoice report.

## Content

**Intro:** This cheat sheet lists the C/AL record, validation, report and text functions NAV 2013 to 2018 developers use daily, with the signature, a one-line example, the AL equivalent for Business Central on-premises, and the gotcha behind most bugs. Search by function name or group.

**Sections:**

#### How to use it

**Body:** Search by function (SETFILTER, CALCFIELDS) or group (Filtering, FlowFields, Reports and pages). The Example column is real C/AL using standard W1 objects: Customer, Sales Header, Sales Line, Sales Invoice Header, Item, G/L Entry. Paste it into the C/AL editor, rename variables to yours, and read the Gotcha column before you ship. The AL column shows the name in Business Central so the same sheet helps when you convert code with Txt2Al and clean it up.

#### Worked example: run Sales Invoice report 206 for one posted invoice

**Body:** To print or save one posted invoice without the request page, filter a Sales Invoice Header record and pass it to the report. In C/AL: SalesInvHeader.SETRANGE("No.",'103001'); REPORT.RUNMODAL(REPORT::"Sales - Invoice",FALSE,FALSE,SalesInvHeader);. The second parameter FALSE hides the request page and the third FALSE uses the report's printer selection rather than the Windows default. To produce a PDF on the service tier, call REPORT.SAVEASPDF(206,'C:\Temp\SI-103001.pdf',SalesInvHeader);. If you are inside a posting routine, COMMIT before RUNMODAL or NAV raises an error about running modal objects inside a write transaction.

#### Gotchas that cause real bugs

**Body:** FINDSET is the right choice for forward loops; FIND('-') and FINDFIRST followed by NEXT fetch records less efficiently on SQL Server. MODIFY without TRUE skips the OnModify trigger, so fields like Last Date Modified and change-log logic are bypassed. Assigning a field directly (SalesLine.Quantity := 5) skips OnValidate, so amounts, reservations and dimensions are not recalculated; use VALIDATE. TESTFIELD without a value errors on empty or zero. SETFILTER with concatenated strings breaks when the value contains filter characters such as & or |.

**Bullets:**

- Use FINDSET(TRUE) when the loop modifies records
- Use ISEMPTY instead of COUNT = 0
- Use SETAUTOCALCFIELDS before a loop that reads FlowFields

#### Version differences

**Body:** SETAUTOCALCFIELDS, REPORT.SAVEASPDF on the service tier and the RDLC/Word report layouts belong to NAV 2013 and later; NAV 2009 classic code may use FORM.RUNMODAL and other functions that no longer exist. In Business Central AL the names are PascalCase, Report.SaveAsPdf is limited to on-premises scope, and Report.SaveAs to an OutStream is the cross-platform option. Events (subscribers) replace direct code changes in standard objects, so a C/AL modification usually becomes an event subscriber or a table or page extension.

**Cta:** If you want NAV users to ask questions about customers, orders and ledgers in plain English on your own servers, read-only and respecting NAV permissions, we can set that up.

**Faq:**

#### How do I use SETFILTER with multiple values in NAV?

**Answer:** Use placeholders: Cust.SETFILTER("No.",'%1..%2|%3','10000','20000','30000') filters customers 10000 to 20000 plus 30000. The .. operator is a range, | is or, & is and, <> is not equal, * is a wildcard and @ makes the comparison case-insensitive. Placeholders avoid errors when values contain filter characters.

#### What is the difference between TESTFIELD and VALIDATE?

**Answer:** TESTFIELD checks a field and raises an error if it is empty or not equal to the value you pass; it changes nothing. VALIDATE assigns a value and runs the field's OnValidate trigger, so dependent fields such as amounts or descriptions are recalculated. Use TESTFIELD for preconditions and VALIDATE for writes.

#### How do I run a report with parameters in Dynamics NAV?

**Answer:** For filters, set them on a record and pass it: REPORT.RUNMODAL(Number, ReqWindow, SystemPrinter, Record). For other options, declare a report variable, call SETTABLEVIEW and a custom public function on the report that sets its globals, then USEREQUESTPAGE(FALSE) and RUNMODAL. Report request page options cannot be set directly from outside without such a function.

#### Why does MODIFY not update my last modified date?

**Answer:** MODIFY without the TRUE parameter does not run the table's OnModify trigger, where fields like Last Date Modified are usually set. Call Rec.MODIFY(TRUE) when business logic should run. Use plain MODIFY only for technical updates where you deliberately want to bypass triggers, and document why.

## Search terms

dynamics nav setfilter, dynamics nav testfield, dynamics nav validate, dynamics nav run report with parameters, dynamics nav findset, c/al functions list, nav report.runmodal parameters

---
Talk to an engineer: https://netray.co/contact?intent=erp_ai | Machine catalog: https://netray.co/agent/catalog.json
