Recently, I found myself in need of a massive amount of customer demo data. That got me thinking: There’s probably a web service out there that will generate some random customer data. And sure, https://randomuser.me/ was the top result on google. So join me in this video to consume the data:

In this video, Erik demonstrates how to generate demo customer data in Business Central by consuming a free public API called randomuser.me. Instead of manually creating test records or hunting down old CSV files, he builds a quick AL extension that calls the API, parses the JSON response, and creates customer records on the fly. Along the way, he covers the BC24 REST client, JSON parsing patterns, common pitfalls with Record.Init(), and field length truncation.
The Problem: Not Enough Demo Data
Business Central’s default Cronus database ships with only a handful of customers — nowhere near enough when you need to test features at scale. Using real customer data isn’t an option, and tracking down old demo CSV files can be a dead end. Erik’s solution: find a web service that generates random but realistic user data and import it directly into Business Central.
After a quick search, he landed on randomuser.me — a free API that returns random user profiles complete with names, addresses, emails, and even photos. The API is simple: hit the endpoint and you get back a JSON object containing an array of random users.
Understanding the API Response
The randomuser.me API returns a JSON structure like this:
{
"results": [
{
"gender": "male",
"name": {
"title": "Mr",
"first": "Andrew",
"last": "Pit"
},
"location": {
"street": {
"number": 1234,
"name": "Main Street"
},
"city": "Some City",
"state": "Some State",
"postcode": "12345"
},
"email": "andrew.pit@example.com",
"picture": {
"large": "https://...",
"medium": "https://...",
"thumbnail": "https://..."
}
}
]
}
The key points for parsing: the top level is a JSON object with a results property that contains a JSON array. Each element in that array is an object representing a person, and within each person, name, location, and location.street are nested objects. Fields like email and gender sit at the person level.
Project Setup
Erik started with a new AL app. The source code includes a simple BigData table and page that were part of the initial project scaffolding:
table 59300 BigData
{
Caption = 'BigData';
DataClassification = ToBeClassified;
fields
{
field(1; LineNo; Integer)
{
Caption = 'LineNo';
DataClassification = ToBeClassified;
}
field(2; Description; Text[1000])
{
Caption = 'Description';
DataClassification = ToBeClassified;
}
}
keys
{
key(PK; LineNo)
{
Clustered = true;
}
}
}
And the corresponding list page with a bulk create action:
page 59300 BigDataTable
{
ApplicationArea = All;
Caption = 'BigDataTable';
PageType = List;
SourceTable = BigData;
UsageCategory = Administration;
layout
{
area(content)
{
repeater(General)
{
field(LineNo; Rec.LineNo)
{
ApplicationArea = All;
}
field(Description; Rec.Description)
{
ApplicationArea = All;
}
}
}
}
actions
{
area(Processing)
{
action(Create)
{
Caption = 'Create Data';
Promoted = true;
PromotedCategory = Process;
PromotedOnly = true;
ApplicationArea = all;
trigger OnAction()
var
d: Record BigData;
i: Integer;
i2: Integer;
begin
if Rec.FindLast() then
i := Rec.LineNo;
repeat
i += 1;
i2 += 1;
d.Init();
d.LineNo := i;
d.Description := 'Hafnarfjörður Blåbærgrød Garðabær Höfuðborgarsvæðið Ísafjarðarbær';
d.Insert();
until i2 = 100000;
end;
}
}
}
}
The real action, however, happens in the customer import logic built on top of the customer list page.
Building the Import Action
The first step is adding an action to a page (such as the Customer List) that triggers the import:
actions
{
area(Processing)
{
action(Import)
{
Caption = 'Create Customers';
ApplicationArea = All;
Promoted = true;
PromotedCategory = Process;
PromotedOnly = true;
trigger OnAction()
begin
ImportCustomers();
end;
}
}
}
Using the BC24 REST Client
Business Central 24 introduced a built-in REST client codeunit, which makes HTTP calls much simpler than rolling your own with HttpClient. Erik uses it to call the randomuser.me API:
procedure ImportCustomers()
var
Rest: Codeunit "Rest Client";
JsonTools: Codeunit "Json Tools";
Data: JsonObject;
Results: JsonArray;
Tkn: JsonToken;
Person: JsonObject;
NameObj: JsonObject;
Location: JsonObject;
Street: JsonObject;
Customer: Record Customer;
begin
Data := Rest.GetAsJSon('https://randomuser.me/api/?results=1000').AsObject();
Results := JsonTools.GetArray(Data, 'results');
foreach Tkn in Results do begin
Person := Tkn.AsObject();
// Extract sub-structures
NameObj := JsonTools.GetObject(Person, 'name');
Location := JsonTools.GetObject(Person, 'location');
Street := JsonTools.GetObject(Location, 'street');
// Create the customer
Customer.Init();
Customer.Insert(true);
// Fill out fields
Customer.Validate(Name,
JsonTools.GetText(NameObj, 'first') + ' ' +
JsonTools.GetText(NameObj, 'last'));
Customer.Validate(Address,
JsonTools.GetText(Street, 'name') + ' ' +
JsonTools.GetText(Street, 'number'));
Customer.Validate(City,
CopyStr(JsonTools.GetText(Location, 'city'),
1, MaxStrLen(Customer.City)));
Customer.Validate("Post Code",
CopyStr(JsonTools.GetText(Location, 'postcode'),
1, MaxStrLen(Customer."Post Code")));
Customer.Validate("E-Mail",
JsonTools.GetText(Person, 'email'));
Customer.Modify(true);
end;
end;
A few things to note about this approach:
Rest.GetAsJSon()returns aJsonToken, so you call.AsObject()to convert it to aJsonObject.- The URL
https://randomuser.me/api/?results=1000requests 1,000 random users in a single call. - The pattern of
Init() → Insert(true) → Validate fields → Modify(true)mimics how a user would interact with the customer card — the insert triggers the number series assignment, and the modify persists all the field values.
JSON Helper Functions
Erik uses a small helper codeunit called Json Tools to simplify extracting values from JSON. The GetArray function, for example, looks like this:
procedure GetArray(Obj: JsonObject; Key: Text): JsonArray
var
Tkn: JsonToken;
begin
if Obj.Get(Key, Tkn) then
exit(Tkn.AsArray());
Error('Key %1 not found', Key);
end;
Similar helper functions exist for GetObject and GetText. The philosophy here is deliberate: if a key is missing, the function throws an error immediately rather than silently returning an empty value. This is appropriate because missing structural elements (like name or results) indicate something is fundamentally wrong with the API response, not a recoverable situation.
Erik also mentions that you could use JSON Path syntax to navigate deeply nested structures instead of extracting objects level by level, but for this quick-and-dirty approach, navigating the structure manually is straightforward enough.
Pitfall: Record.Init() and Primary Keys
Erik ran into an interesting issue during the demo. When looping through the results, the customer record’s primary key wasn’t being reset between iterations. This is because Record.Init() does not reset the primary key or timestamp fields — it only initializes non-key fields to their default values.
From the official documentation:
“This method assigns default values to each field in a record… Primary key and timestamp fields are not initialized.”
In this case, the issue was mitigated because Insert(true) with a number series assigns a new primary key. But it’s a subtle trap: if you’re working with tables where you manually manage the primary key, you’d need to explicitly clear or reassign it in each loop iteration.
Pitfall: Field Length Overflow
Another issue Erik encountered was that some values from the API exceeded the maximum length of Business Central fields. For example, a city name might be longer than the City field allows. The fix is to use CopyStr with MaxStrLen:
Customer.Validate(City,
CopyStr(JsonTools.GetText(Location, 'city'),
1, MaxStrLen(Customer.City)));
This pattern should really be applied to all text fields being populated from external data. You never know how long the incoming strings might be, and a runtime error in the middle of importing 1,000 records is not ideal.
Notes on Post Codes, States, and Countries
Since randomuser.me generates data for people from all over the world, you’ll get a mix of countries, states, and postal code formats. In Business Central, the City field is effectively a lookup into the Post Code table, and County (state) and Country/Region Code have their own validation logic.
For a truly complete solution, you’d want to:
- Populate the Post Code table with matching entries
- Map the country from the API response to a valid Country/Region Code
- Handle state/province values appropriately
For demo data purposes, Erik chose to skip this complexity and just fill out the fields directly — a pragmatic choice for test data that doesn’t need to pass all validation rules.
Performance Observations
During the demo, Erik noticed that the first REST call was unexpectedly slow. This was likely due to a combination of factors: the Docker container needing to wake up, DLLs being loaded into memory for the first time, and the REST client initializing. Subsequent calls were significantly faster. This is a common pattern when working with Docker-based development environments — the first publish and first HTTP call carry extra overhead.
Bonus Idea: Random Profile Photos
The randomuser.me API actually includes URLs for profile photos (thumbnail, medium, and large). Erik mentioned that there are also AI-powered services that generate photos of non-existing people, which could be used to populate customer profile images for even more realistic demo data. That’s a topic for a future video.
Summary
This approach gives you a quick and effective way to populate Business Central with realistic demo data:
- Use the randomuser.me API to get random user profiles in JSON format
- Use the BC24 REST Client codeunit to make the HTTP call with minimal boilerplate
- Parse the JSON response using helper functions that navigate the nested structure
- Create customers using the Init → Insert → Validate → Modify pattern
- Always use CopyStr/MaxStrLen when mapping external text data to Business Central fields
- Remember that Record.Init() does not reset primary key fields
With a single API call requesting 1,000 results, you can populate your test environment with realistic customer data in seconds — far faster and easier than manually creating records or importing CSV files.