Your Business Central extension successfully connects to an external API. Authentication works, requests are processed, and everything looks fine.
But that doesn’t necessarily mean the API key is secure.
Where is the credential stored? Can someone see it while debugging? Could another extension access it? And what happens when your code sends it to an external service?
Microsoft provides features such as SecretText, IsolatedStorage, and Azure Key Vault to help protect sensitive information.
The important part is knowing what each feature protects and when to use it.
Let’s explore them through a simple Business Central integration scenario.
Why Credentials Need Special Handling
Imagine you’re developing a Business Central extension that connects to a shipping provider.
The provider gives you an API key, which the extension must use whenever it sends a request.
A common approach is to create a setup table containing the API URL and API key.
Storing configuration in a setup table is normal. However, storing credentials in an ordinary Text field creates potential exposure through application data, AL code, or debugging, depending on permissions and implementation.
Masking a field on a page doesn’t solve the storage problem.
For example:
ExtendedDatatype = Masked;
This displays the value as dots, but does not encrypt the underlying data.
Business Central also provides MaskType = Concealed starting with runtime 16.0 (BC 27). This offers UI masking with a reveal button, but Microsoft describes it as UI-level protection because the underlying value still reaches the browser.
The key distinction: credentials require additional protection beyond how they are displayed.
What Is SecretText in Business Central?
SecretText is an AL data type introduced in Business Central 23 (2023 release wave 2).
It protects sensitive values from exposure through regular and snapshot debugging.
Consider this example:
var
ApiKey: SecretText;
NormalText: Text;
begin
ApiKey := NormalText; // Allowed
NormalText := ApiKey; // Not allowed
end;
AL allows an ordinary Text value to be assigned to SecretText, but prevents assigning a SecretText value back to ordinary Text.
Why?
Because converting protected values into ordinary text would make them easier to expose through debugging.
The compiler also prevents directly assigning a hardcoded text literal to SecretText. However, that doesn’t protect a credential previously stored in an ordinary variable or committed to source code.
When Should You Use SecretText?
Use it when handling sensitive values such as API keys, passwords, and authentication tokens.
For example, when constructing an HTTP authorization header:
Headers.Add(
'Authorization',
SecretStrSubstNo('Bearer %1', AccessToken));
Here, AccessToken is a SecretText variable.
SecretStrSubstNo constructs the header without converting the token into ordinary text.
Another useful feature is [NonDebuggable], which prevents sensitive procedures or variables from being inspected through the AL debugger. This is particularly useful when a credential initially arrives as ordinary text.
Remember: SecretText protects sensitive information during AL processing. It is not a storage mechanism.
What Is IsolatedStorage?
IsolatedStorage provides key-value storage isolated between Business Central extensions.
Imagine two extensions installed in the same environment:
- Extension A connects to a shipping provider.
- Extension B connects to a payment gateway.
If Extension A stores a value using IsolatedStorage, Extension B cannot directly retrieve that value through its own IsolatedStorage context.
This makes it useful for extension-specific settings and credentials.
Set vs. SetEncrypted
Business Central provides different methods for storing values.
| Method | Purpose |
|---|---|
IsolatedStorage.Set | Stores a key-value pair |
IsolatedStorage.SetEncrypted | Encrypts and stores a key-value pair |
For credentials supported by the method, prefer SetEncrypted.
For example:
procedure SaveApiKey(ApiKey: SecretText)
var
SaveFailedErr: Label 'Unable to save the API key.';
begin
if not IsolatedStorage.SetEncrypted(
'ShippingApiKey',
ApiKey,
DataScope::Company)
then
Error(SaveFailedErr);
end;
The example stores the API key encrypted within the current company’s extension context.
It assumes the credential has already been entered through a protected setup process accessible only to authorized users.
There is an important limitation: SetEncrypted supports a maximum of 215 plain characters, and some special characters consume additional space.
Therefore, it may not be suitable for long tokens, certificates, or private keys.
Both the SetEncrypted and Get overloads accepting SecretText require AL runtime 13.0 (Business Central 24).
Understanding DataScope
DataScope determines the context in which IsolatedStorage values are available.
| DataScope | Meaning | Example |
|---|---|---|
Module | Extension-level scope | One API key shared across companies |
Company | Extension and company | Different API keys per company |
User | Extension and user | Personal credentials |
CompanyAndUser | Extension, company, and user | Personal credentials within a company |
For company-specific integrations, DataScope::Company is often appropriate.
But consider Job Queue processing. If a credential is saved with DataScope::User, a background session running under another user may not be able to retrieve it.
Choosing the correct scope is important for both security and integration functionality.
How SecretText and IsolatedStorage Work Together
Let’s return to our shipping integration.
The process can be understood in four stages:
| Stage | What happens |
|---|---|
| Configuration | An authorized user enters the API key through a protected, write-only setup field |
| Storage | The extension saves it using IsolatedStorage.SetEncrypted |
| Retrieval | The extension retrieves it into SecretText |
| API request | The extension sends it to the approved provider using a secret-aware HTTP method |
For example, retrieving the credential:
procedure GetApiKey(): SecretText
var
ApiKey: SecretText;
MissingKeyErr: Label 'The shipping API key is not configured.';
begin
if not IsolatedStorage.Get(
'ShippingApiKey',
DataScope::Company,
ApiKey)
then
Error(MissingKeyErr);
exit(ApiKey);
end;
The credential is returned as SecretText, so the calling procedure can pass it to a supported HTTP authentication method without converting it into ordinary text.
These examples demonstrate the documented methods, not a complete integration. A production implementation must also control who can configure credentials and which destination receives them.
Where Can Credentials Still Leak?
Using SecretText and encrypted IsolatedStorage improves credential handling, but other implementation decisions remain important.
| Situation | Potential problem | Recommended approach |
|---|---|---|
| Hardcoded credentials | API keys appear in source code or Git history | Keep real credentials outside source code |
| Ordinary setup fields | Credentials are retained as application data | Use appropriate protected storage |
| Debugging | Sensitive Text variables may be inspected | Use SecretText and [NonDebuggable] |
| HTTP requests | Credentials reach unintended destinations | Restrict requests to trusted endpoints |
| Logging | Secrets appear in errors or diagnostics | Log non-sensitive information |
Can Another Extension Access the Credential?
IsolatedStorage separates stored values between extensions, but developers must also review what their public procedures allow other extensions to do.
Imagine exposing a procedure that sends a stored API key to any URL supplied by its caller.
Even though the key is encrypted in storage, another extension could potentially invoke that procedure with an unintended destination.
Declaring a procedure internal, or setting a codeunit’s Access = Internal, reduces what other extensions can reference at compile time.
However, Microsoft states that access modifiers are not runtime security boundaries. For example, an internal codeunit’s OnRun can still be invoked using Codeunit.Run, while reflection-based mechanisms such as RecordRef can access internal tables.
Avoid placing credential-sending logic in OnRun, and don’t allow untrusted callers to choose the destination URL.
What Happens When Production Is Copied to a Sandbox?
Imagine copying a production environment to a sandbox to investigate an integration issue.
Business Central takes several precautions during environment copying, including stopping scheduled tasks and blocking outbound HTTP calls from extensions by default.
To re-enable those requests, Allow HttpClient Requests must be selected in Extension Settings for each extension involved, including library apps.
However, developers must still consider their own integration credentials.
If a production credential remains available after copying, re-enabling outgoing requests could allow the sandbox to communicate with a live external service.
Microsoft’s documented cleanup list doesn’t establish that every custom extension credential is removed. Developers should therefore review their own credential handling rather than assume everything has been cleared.
The System Application provides codeunit 1886 “Environment Cleanup”, including two events:
OnClearCompanyConfig— for cleaning company-specific configuration.OnClearDatabaseConfig— for cleaning environment-wide configuration.
Extensions can subscribe to these events to handle their configuration when environments are copied.
What About Azure Key Vault?
Azure Key Vault is a Microsoft service for centrally managing secrets, keys, and certificates.
Business Central provides a built-in App Key Vault integration that allows supported extensions to retrieve secrets.
However, there is an important limitation:
In Business Central Online, the built-in App Key Vault feature supports Marketplace extensions, not per-tenant or development extensions.
For per-tenant extensions handling short customer-provided API keys, encrypted IsolatedStorage may be suitable.
For more complex requirements, such as centrally managed secrets or long private keys, a separately designed external secret-management service may be appropriate.
Quick Reference: Which Feature Should You Use?
| Requirement | Recommended approach |
|---|---|
| Protect credentials during AL processing | SecretText |
| Store a short credential encrypted | IsolatedStorage.SetEncrypted |
| Maintain separate credentials for each company | DataScope::Company |
| Protect sensitive retrieval code from debugging | [NonDebuggable] |
| Send credentials through supported HTTP headers | Secret-aware HttpHeaders.Add |
| Reduce the publicly exposed AL API | internal procedures or Access = Internal, alongside runtime controls |
| Centrally manage Marketplace extension secrets | Built-in App Key Vault |
| Handle large private keys or certificates | Evaluate dedicated secret-management or cryptographic services |
| Protect copied environments | Review credentials and use environment-cleanup mechanisms |
Final Thoughts
The most useful distinction is simple:
SecretText protects how AL handles sensitive values. IsolatedStorage.SetEncrypted protects how supported credentials are stored.
DataScope determines where those stored values are available, while Azure Key Vault provides additional options for supported architectures.
Together, these features help Business Central developers handle credentials more securely. The remaining responsibility is to design the integration carefully, especially its configuration, exposed procedures, external requests, and diagnostic information.
Before building your next API integration, ask yourself three questions:
Where is the credential stored? How does AL handle it? Where can the extension send it?
Those questions provide a practical starting point for preventing credential leaks in Business Central extensions.




