There are many secrets in Business Central, and some of them can be hard to keep secret. Introducing SecretText, a way to keep certain values away from prying eyes. Check out the video:

In this video, Erik explores the SecretText data type in Business Central — a relatively recent addition designed to prevent sensitive information like API keys, passwords, and tokens from being exposed through the debugger. He demonstrates the problem with storing secrets in regular text variables and shows how SecretText keeps values hidden, even during debugging sessions.
The Problem: Secrets Exposed in the Debugger
Erik starts by setting up a simple scenario where a secret value is retrieved from isolated storage and stored in a regular Text variable. Isolated storage ensures that only the app itself can read its own stored values — other apps cannot retrieve them. However, there’s a significant problem: anyone who can debug the extension can see the secret value in plain text.
When you step through the code with the debugger, the variable’s value is fully visible. This is a real security concern, especially in multi-tenant cloud environments where delegated admins or partners may have debugging access.
One workaround is to mark procedures as NonDebuggable, but this creates a frustrating situation. You end up making entire procedures non-debuggable just to protect a single variable, which makes troubleshooting legitimate issues much harder.
Enter SecretText
The SecretText data type solves this elegantly. Instead of declaring your variable as Text, you declare it as SecretText. When you debug code that uses a SecretText variable, the debugger shows “Hidden value” instead of the actual content — no matter where you inspect it.
Erik demonstrates that the hidden value protection follows the secret everywhere:
- In the procedure where it’s first assigned
- When passed as a parameter to other functions
- When inspected in watch windows or variable tooltips
- When added to HTTP headers using the secret-aware overloads
HTTP Client Integration
The AL HTTP client types have been updated to accept SecretText values directly. For example, when adding authorization headers, you can use overloads that accept SecretText parameters, so the secret never needs to be converted to plain text:
// The HTTP client has overloads that accept SecretText
// so you never need to unwrap the secret
client.DefaultRequestHeaders().Add('Authorization', mySecretToken);
Preventing Secret Spillover
One of the key design principles of SecretText is that you cannot accidentally spill a secret into an unprotected variable. If you try to assign a SecretText value to a regular Text variable, the compiler will block it:
// This will NOT compile — by design
var
t2: Text;
t: SecretText;
begin
t2 := t; // Compiler error: cannot convert SecretText to Text
end;
This prevents developers from inadvertently extracting secrets and storing them in variables that the debugger can expose. On-premises installations do have an Unwrap method available, but in the cloud, you’re intentionally restricted from converting SecretText back to plain text.
Creating SecretText Values in Code
You can create SecretText values programmatically. While you can’t directly assign a text literal to a SecretText variable, you can use the format function or return a SecretText from a NonDebuggable procedure:
// A non-debuggable procedure that generates a secret
[NonDebuggable]
procedure GenerateSecret(): SecretText
begin
exit('some-secret-value');
end;
When you call this function from debuggable code, the debugger will skip right over the non-debuggable procedure, and the returned value will show as “Hidden value” in the calling code. This pattern gives you a clean way to create secrets while keeping the rest of your code fully debuggable.
A Word on Events and Secrets
Erik also explores whether you can pass SecretText through integration events. While the compiler does allow it, he cautions against this practice. Exposing secrets through events means subscribers — potentially from other extensions — could receive the secret value. Even though the SecretText type provides debugger protection, broadcasting secrets through events undermines the principle of keeping sensitive data contained.
Related: Field Masking for Sensitive Data on Pages
While SecretText protects secrets at the code level, you may also need to mask sensitive data displayed on pages. The provided source code demonstrates a complementary technique — masking a credit card number on the Customer Card page:
tableextension 50100 MyCustomer extends Customer
{
fields
{
field(50100; CreditCardNo; Text[50])
{
Caption = 'Credit Card No.';
}
}
}
pageextension 50100 MyCustomer extends "Customer Card"
{
layout
{
addafter(Name)
{
field(CreditCardCtl; MaskedCreditCard)
{
Caption = 'Credit Card No.';
ApplicationArea = all;
trigger OnValidate()
begin
if strlen(MaskedCreditCard) < 5 then
error('Not a valid credit card');
Rec.CreditCardNo := MaskedCreditCard;
MaskedCreditCard := PerformMasking();
end;
}
}
}
trigger OnAfterGetRecord()
begin
MaskedCreditCard := PerformMasking();
end;
trigger OnAfterGetCurrRecord()
begin
MaskedCreditCard := PerformMasking()
end;
procedure PerformMasking(): Text
begin
if Strlen(Rec.CreditCardNo) > 4 then
exit('*************' + Rec.CreditCardNo.Substring(strlen(Rec.CreditCardNo) - 3))
else
exit('');
end;
var
MaskedCreditCard: Text[50];
}
This approach stores the full credit card number in the database but only displays the last four digits on the page, prefixed with asterisks. The field is bound to a page variable (MaskedCreditCard) rather than directly to the record field, so the masking logic runs on every record retrieval. Note that this is UI-level masking — for true security of secrets like API keys, the SecretText data type is the proper solution.
Summary
The SecretText data type is an important security feature for Business Central developers. Here are the key takeaways:
- Use
SecretTextfor any sensitive values like API keys, passwords, and tokens - The debugger cannot reveal
SecretTextvalues — they always show as “Hidden value” - You cannot assign a
SecretTextto a regularTextvariable, preventing accidental spillover - HTTP client methods have overloads that accept
SecretTextdirectly - Use
NonDebuggableprocedures to generate or initialize secret values - Avoid exposing secrets through events — keep them contained within your app
- On-premises has an
Unwrapmethod; cloud environments intentionally restrict this - For UI-level protection, consider field masking patterns to hide sensitive data on pages