Lately, I have been thinking about inter-environment data exchange in AL and BC, so I decided to build a prototype. Check out the video:

In this episode of “From the Wizard’s Lair,” Erik explores an experimental concept he calls Remote Records — the ability to directly read from (and potentially write to) records in a different Business Central environment using AL code. Instead of relying on configuration packages, RapidStart, or manual export/import workflows, what if you could simply point your code at another environment and work with records as if they were local?
The Problem: Moving Data Between Environments
If you’ve worked with Business Central for any length of time, you’ve almost certainly encountered the need to move data between environments — production to sandbox, sandbox to sandbox, or between multiple production tenants. The typical approaches involve:
- RapidStart / Configuration Packages (export, import, deal with errors)
- Tools like PCCL2 or other third-party utilities
- Manual data entry (the worst option)
All of these are cumbersome, especially when you realize you need to go back and grab additional records. Erik’s idea: what if we could connect environments directly at the AL level and treat remote records almost like local ones?
The Toolbox: A Live AL Playground
Erik demonstrates the concept using his Toolbox — an AppSource app that lets you write and execute AL code directly in Business Central. Think of it as an interactive AL REPL. Here’s a simple example that loops through Customer records:
// Declare a Customer record variable
var C: Record Customer;
begin
if C.FindSet() then
repeat
Message(C.Name);
until C.Next() = 0;
end;
Running this in the Toolbox outputs customer names on the right-hand side of the screen. The Toolbox already supports ChangeCompany to switch between companies within the same environment. But now Erik has added something new: ChangeEnvironment.
Introducing ChangeEnvironment
With a new setup page where you can configure credentials (tenant ID, authentication details) for another environment, the Toolbox now supports switching environments entirely:
var C: Record Customer;
begin
C.ChangeEnvironment('ACS'); // Switch to the 'ACS' sandbox
if C.FindSet() then
repeat
Message(C.Name);
until C.Next() = 0;
end;
When Erik runs this, the customer names change — they now reflect the data from the remote sandbox environment (where he had prefixed all item names with “BC21”). Filters work too:
var C: Record Customer;
begin
C.ChangeEnvironment('ACS');
C.SetFilter("No.", '10000|30000|50000'); // 45000 doesn't exist
if C.FindSet() then
repeat
Message(C.Name);
until C.Next() = 0;
end;
This returns only three records from the remote environment. You could then copy those records into a local table:
var
C: Record Customer; // Remote
C2: Record Customer; // Local
begin
C.ChangeEnvironment('ACS');
if C.FindSet() then
repeat
C2.Copy(C);
C2.Insert();
until C.Next() = 0;
end;
How It Works Under the Hood
The implementation is a fascinating exercise in serialization and web service communication. Here’s the architectural overview:
The Interpreter Side (Caller)
Inside the Toolbox’s interpreter (a substantial codeunit), there’s a function called RecordFunction that handles any time you call a method on a record variable (like FindSet, Find, Next, etc.). When the record has been tagged as a remote variable (via ChangeEnvironment), the interpreter routes the call to a RemoteRecordFunction instead of executing it locally.
The process on the calling side:
- Serialize the RecordRef to JSON — The local record (which exists only in memory, never touching the local database) is packed into a JSON payload containing the table information, the current record position, the operation to perform (
Find,FindSet,Next, etc.), and all active filters. - Evaluate parameters — The interpreter resolves any parameters (e.g., the parameter to
Nextmight be a computed expression). - Send to remote — The JSON payload is POSTed to an exposed codeunit on the remote environment using service-to-service (S2S) OAuth authentication, inspired by AJ Kauffmann’s authentication patterns.
- Process the reply — The response JSON is deserialized back into the local RecordRef. The return value (e.g.,
true/falseforFindSet, or an integer forNext) is extracted and returned to the interpreter.
The Remote Side (Receiver)
On the remote environment, a separate extension exposes a codeunit as an OData web service. It has a function called RemoteRecord that accepts the JSON payload:
- Deserialize the JSON into a RecordRef — A parallel RecordRef is built on the remote side from the incoming JSON.
- Apply filters — All filters from the caller are applied to the remote RecordRef.
- Execute the operation:
- For
Find/FindSet: Execute the find operation and return the result plus the found record. - For
Next: First position to the current record (the one sent over), then callNextto advance, and return the new record.
- For
- Serialize and return — The result record and return value are serialized back to JSON, along with timing information showing how much time was spent inside the web service.
The Key Insight: RecordRef as In-Memory Proxy
The elegant part of this design is that the local RecordRef never touches the local database. It exists purely in memory as a proxy for the remote record. All actual database operations happen on the remote side. The serialization/deserialization cycle (RecordRef → JSON → HTTP → JSON → RecordRef) is the bridge between environments.
Performance Observations
Erik’s testing (from a local Docker container to a cloud sandbox) revealed notable latency:
- First call: ~1.5 seconds (includes OAuth token acquisition), with only 15ms spent in the remote web service
- Subsequent calls: ~600-1000ms each (OAuth cached), with 16-100ms spent remotely
- The vast majority of time is spent in HTTP overhead, not in the actual database operations
Erik notes that cloud-to-cloud performance (where both environments are within Microsoft’s infrastructure) could be significantly faster and warrants further testing.
The Transaction Problem
Erik raises an important architectural concern: transactions. For read-only operations, transactions aren’t really an issue — you’re just querying data. But if you implement Modify, Insert, or Delete on remote records, you run into a fundamental problem:
- Each web service call is its own session on the remote side
- This means each operation commits independently — there’s no way to wrap multiple remote operations in a single transaction
- This is a significant departure from how AL normally works, where you can modify multiple records within a single transaction and roll back if something goes wrong
Erik’s current thinking is that read-only remote records might be the sweet spot. You run the Toolbox in the environment where you want to write data, pull records from the remote environment, and insert them locally — where normal transaction handling applies.
Reference: Source Code Structure
For context, here’s what a typical AL extension skeleton looks like in Business Central — though the Toolbox and Remote Records implementation is far more complex than this starter template:
namespace DefaultPublisher.Agents;
using Microsoft.Sales.Customer;
using System.Utilities;
using System.Agents;
pageextension 50100 CustomerListExt extends "Customer List"
{
trigger OnOpenPage();
var
Ref: RecordRef;
TB: Codeunit "Temp Blob";
FR: FieldRef;
InS: InStream;
An: Text;
begin
// Extension logic here
end;
}
The Remote Records feature leverages RecordRef and FieldRef extensively — they’re essential for working with records generically (without knowing the table at compile time), which is exactly what you need when building a general-purpose remote record bridge.
Conclusion
Erik’s Remote Records experiment demonstrates a genuinely novel approach to cross-environment data access in Business Central. By serializing RecordRef data to JSON, sending it over authenticated web service calls, executing operations on the remote side, and deserializing the results back, he’s created a working prototype that lets AL code transparently work with records from another environment.
The key takeaways:
- It works — Reads, filters, and iteration across environments are functional
- Performance needs investigation — HTTP overhead dominates; cloud-to-cloud testing is needed
- Read-only is the safe path — Transaction semantics make remote writes architecturally challenging
- Even at half a second per record, it could beat configuration packages for many real-world scenarios
Erik acknowledges this might appeal to a niche audience, but for those who regularly shuttle data between Business Central environments, this could be a significant quality-of-life improvement. He’s eager for community feedback — whether this is genuinely useful or a fascinating experiment destined for “Never Never Land.”