The System Application is getting better and better, in the video, I take a look at a codeunit that enables you to manipulate images, directly in AL. Check it out:

In this video, Erik explores the image manipulation capabilities that have been added to the AL System Application. The Image codeunit provides functions for cropping, resizing, rotating, flipping, and converting image formats — all directly within AL code, no external libraries required. Erik walks through each function with live demos using his collection of cute animal photos, and also highlights that the System Application is now open source and accepting contributions on GitHub.
The Image Codeunit: What’s Available?
One of the things that has changed over the last couple of years — and even months — is that the System Application (the non-accounting part of the AL platform) has become way more capable. Among the additions is a codeunit for working with images directly in AL.
When you create a variable of type Codeunit Image, you get access to the following functions:
- Clear — Clear the image data
- Crop — Crop the image based on a rectangle
- FromBase64 / FromStream — Load an image from Base64 string or stream
- GetFormat / GetFormatAsText — Get the image format (as enum or text)
- GetHeight / GetWidth — Get image dimensions
- GetRotateFlipType — Get the current rotation/flip state
- Resize — Resize the image to new dimensions
- RotateFlip — Rotate and/or flip the image
- Save — Save the image to a stream
- SetFormat — Change the image format (JPEG, PNG, GIF, etc.)
- ToBase64 — Export the image as a Base64 string
Setting Up the Extension
Erik starts by creating a fresh extension with a simple page extension on the Customer List. The action uploads an image, manipulates it, and then downloads the result:
pageextension 56100 CustomerListExt extends "Customer List"
{
actions
{
addfirst(processing)
{
action(OmageToImages)
{
Caption = 'Image stuff';
ApplicationArea = all;
trigger OnAction()
var
Image: Codeunit Image;
InS: InStream;
FileName: Text;
TmpBlob: Codeunit "Temp Blob";
OutS: OutStream;
begin
if UploadIntoStream('Upload Image', '', '', FileName, InS) then begin
Image.FromStream(InS);
// Manipulate the image here...
TmpBlob.CreateOutStream(OutS);
Image.Save(OutS);
TmpBlob.CreateInStream(InS);
FileName := FileName.Replace('.', '2.');
DownloadFromStream(InS, '', '', '', FileName);
end;
end;
}
}
}
}
Codeunits Are Objects, Not Just Code Libraries
Erik makes an important point here: if you’re a “dinosaur” (his words), you might think of a codeunit as just a code library. But in modern AL, a codeunit is an object — it has functions, data, and memory. When you call Image.FromStream(InS), you’re loading the image into that codeunit instance. You could create a second variable Image2: Codeunit Image and have two separate images loaded simultaneously. It’s more like an object with state than a static code library.
Loading and Inspecting an Image
The first step is uploading an image using UploadIntoStream. The way streams work is that the file gets uploaded into a temporary buffer managed by Microsoft behind the scenes — think of it as Microsoft’s own temp blob. The instream gives you a pipe to read data from that buffer.
Once loaded, you can inspect the image properties:
message('Type %1\Height %2\Width %3\RotateFlipType %4',
Image.GetFormat(),
Image.GetHeight(),
Image.GetWidth(),
Image.GetRotateFlipType());
A quick tip: using the backslash character (\) in a message string creates a line break. This is an old NAV — even Navision — trick that still works.
Erik tests this with several images: a PNG of puppies returns format “Png” with its dimensions, and a JPEG of a koala returns “Jpeg”. The GetRotateFlipType returns “Rotate None, Flip None” for the test images, even for iPhone photos taken in landscape vs. portrait.
Cropping an Image
The Crop function takes four parameters that define a rectangle: X position, Y position, width, and height. To trim 50 pixels from each edge:
Image.Crop(50, 50, Image.GetWidth() - 100, Image.GetHeight() - 100);
This cuts 50 pixels from the left and top (the starting position), and then the width and height are each reduced by 100 total (50 from each side).
Saving and Downloading the Result
To get the manipulated image back out, you need to work with streams and a Temp Blob. This is where it gets a bit confusing — and Erik acknowledges there’s even a dedicated video about how these “weird names” work:
- An OutStream is a stream you write data into something
- An InStream is a stream you read data out of something
So to save an image and then download it:
// Create an OutStream connected to the Temp Blob, then write the image into it
TmpBlob.CreateOutStream(OutS);
Image.Save(OutS);
// Create an InStream connected to the same Temp Blob, then download from it
TmpBlob.CreateInStream(InS);
FileName := FileName.Replace('.', '2.');
DownloadFromStream(InS, '', '', '', FileName);
The stream itself holds no data — it’s just a pipe. The Temp Blob is the actual memory. You can reuse the same InStream variable because it’s just a connector, not a data container.
The cropping worked perfectly — Erik’s baby pig photo came out neatly trimmed with no visible JPEG artifacts from the re-encoding.
Resizing an Image
The Resize function takes width and height as integer parameters. You can scale proportionally or non-proportionally:
// Double the size
Image.Resize(Image.GetWidth() * 2, Image.GetHeight() * 2);
// Non-proportional resize (stretch/squish)
Image.Resize(round(Image.GetWidth() * 1.5, 1), round(Image.GetHeight() * 0.8, 1));
One gotcha Erik ran into: when you multiply an integer by a decimal like 1.5, the result is a decimal, not an integer. Since Resize expects integers, you need to wrap the calculation with round(..., 1). Without it, you’ll get an “Overflow of conversion runtime decimal value to integer” error.
Rotating and Flipping
The RotateFlip function accepts a Rotate Flip Type enum with various combinations:
Image.RotateFlip("Rotate Flip Type"::Rotate270FlipX);
Available options include combinations like Rotate90FlipNone, Rotate180FlipX, Rotate270FlipNone, and so on. Erik experiments with several values and confirms the rotations work as expected, even if some of the enum combinations can be a bit confusing to reason about visually.
Changing Image Format
You can convert between image formats using SetFormat:
Image.SetFormat("Image Format"::Gif);
Erik converts a JPEG photo to GIF format. The result is noticeably more pixelated because GIF has a limited color palette — typically 256 colors — so a full-color photograph has to be downsampled. But it works! He also updates the filename extension accordingly:
FileName := FileName.Replace('.', '2.');
FileName := FileName.Replace('.jpg', '.gif');
The Complete Code
Here’s the final version of the extension showing all the techniques combined. In practice you’d use whichever operations you need — the commented lines show the various options:
pageextension 56100 CustomerListExt extends "Customer List"
{
actions
{
addfirst(processing)
{
action(OmageToImages)
{
Caption = 'Image stuff';
ApplicationArea = all;
trigger OnAction()
var
Image: Codeunit Image;
InS: InStream;
FileName: Text;
TmpBlob: Codeunit "Temp Blob";
OutS: OutStream;
begin
if UploadIntoStream('Upload Image', '', '', FileName, InS) then begin
Image.FromStream(InS);
// Inspect image properties
// message('Type %1\Height %2\Width %3\RotateFlipType %4',
// Image.GetFormat(), Image.GetHeight(),
// Image.GetWidth(), Image.GetRotateFlipType());
// Crop: remove 50px from each edge
// Image.Crop(50, 50, Image.GetWidth() - 100, Image.GetHeight() - 100);
// Resize: non-proportional scaling
// Image.Resize(round(Image.GetWidth() * 1.5, 1),
// round(Image.GetHeight() * 0.8, 1));
// Rotate and flip
Image.RotateFlip("Rotate Flip Type"::Rotate270FlipX);
// Change format
Image.SetFormat("Image Format"::Gif);
// Save and download
TmpBlob.CreateOutStream(OutS);
Image.Save(OutS);
TmpBlob.CreateInStream(InS);
FileName := FileName.Replace('.', '2.');
FileName := FileName.Replace('.jpg', '.gif');
DownloadFromStream(InS, '', '', '', FileName);
end;
end;
}
}
}
}
It’s Open Source — You Can Contribute
Here’s the bonus information: the System Application is open source. The BCApps repository on GitHub is where Microsoft is actively working on this code. You can see commits from Microsoft developers, review open pull requests (there were 11 at the time of recording), and even participate:
- Comment on pull requests
- Review proposed changes
- Make suggestions
- Submit your own pull requests
This is the real working repo — not the old “ALApps” shadow repository that was a pretend copy. Microsoft has also started creating a “Business Foundation” layer where functionality is being pulled out of the base app and made more generic and reusable.
If you go to the source code under System Application > App > Image, you can see the implementation. It uses .NET’s Image and Graphics classes under the hood. The resize function, for example, calls into .NET’s resize with smoothing and interpolation modes.
What’s Missing?
Erik notes some potentially useful features that aren’t there yet — like the ability to draw text on an image, draw lines, or control smoothing/interpolation modes during resize. These could be great candidates for a pull request!
Summary
The Image codeunit in the System Application provides surprisingly capable image manipulation directly in AL. You can crop, resize, rotate, flip, and convert formats — all without leaving the AL environment. The stream-based workflow (InStream, OutStream, Temp Blob) takes some getting used to, but once you understand that streams are just pipes and blobs are the memory, it all clicks. And since the whole thing is open source on GitHub, you can inspect the implementation, suggest improvements, or even contribute new image manipulation features yourself.