Dependencies in AL are always hard-defined during the app compilation process. But what if you don’t want the hard dependencies, what can you do without a dependency. In this video I explore one case and build a helpful solution. Check it out:

In this video, Erik explores a creative technique for calling functionality from another AL extension without declaring it as a dependency. This is what he calls “runtime dependencies” — a concept that doesn’t formally exist in AL, but can be achieved using dynamic constructs like RecordRef, FieldRef, and Variant. The practical use case: optionally uploading a file to SharePoint via his SharePoint Connector app, but only when that app happens to be installed.
What Is a Dependency in AL?
In AL, the fundamental rule is that you can only use tables, codeunits, and other objects that the compiler knows about. The only way the compiler knows about something is through declared dependencies.
Microsoft’s first-party apps get special treatment — you just specify a platform and application version in your app.json, and you automatically get access to the System Application, Base Application, Business Foundation, and so on. For anything else, you have to explicitly list it in the dependencies array:
{
"id": "40286137-e535-45e5-ab8f-e5c1c616b97e",
"name": "RuntimeDependencies",
"publisher": "Default Publisher",
"version": "1.0.0.0",
"dependencies": [
],
"platform": "1.0.0.0",
"application": "24.0.0.0",
"idRanges": [
{
"from": 50100,
"to": 50149
}
],
"runtime": "13.0",
"features": [
"NoImplicitWith"
]
}
Notice the empty dependencies array — this is key. There is no reference to the SharePoint Connector app at all.
The catch with normal dependencies is that your app will only install on environments where all declared dependencies are already present. That’s fine most of the time, but what about optional functionality?
The Problem: Fraction of a Fraction
Erik’s real-world scenario illustrates this perfectly. He’s building a new app where:
- A fraction of users will use a specific corner of the app
- Within that corner, a fraction of those users might want to upload a file to SharePoint
The SharePoint Connector is a wonderful app, but it’s not universally installed. If Erik adds it as a hard dependency, every customer who wants his new app would be forced to also install the SharePoint Connector — even if they’ll never use the SharePoint upload feature. That’s unacceptable for a “fraction of a fraction” use case.
The goal: if the SharePoint Connector happens to be installed, the upload functionality should be available. If it’s not installed, everything else still works fine.
The Solution: Dynamic Runtime Calls
Since we can’t reference the SharePoint Connector’s types at compile time, we need to use AL’s dynamic constructs: RecordRef, FieldRef, Variant, and Codeunit.Run.
The Overall Approach
Instead of writing the natural, strongly-typed code you’d normally write:
// This is what we'd LIKE to write, but can't without a dependency:
// var SharePoint: Codeunit "SharePoint Uploader";
// SharePoint.UploadFile(...);
We instead:
- Open an unknown table by its ID using
RecordRef - Populate unknown fields by their field numbers using
FieldRef - Call an unknown codeunit by its ID using
Codeunit.Run - Pass the data as a
Variant
The Complete Implementation
namespace DefaultPublisher.RuntimeDependencies;
using Microsoft.Sales.Customer;
using System.Utilities;
using Microsoft.Inventory.Reports;
pageextension 50100 CustomerListExt extends "Customer List"
{
actions
{
addfirst(processing)
{
action(Upload)
{
Caption = 'Upload to SharePoint';
ApplicationArea = all;
trigger OnAction()
var
TempBlob: Codeunit "Temp Blob";
OutS: OutStream;
ParmVar: Variant;
ParmRef: RecordRef;
ParmField: FieldRef;
begin
TempBlob.CreateOutStream(OutS);
Report.SaveAs(Report::"Item List", '', ReportFormat::Pdf, OutS);
ParmRef.Open(70319494);
// 1 Guid
ParmField := ParmRef.Field(1);
ParmField.Value := CreateGuid();
ParmRef.Insert();
// 2 Blob
ParmField := ParmRef.Field(2);
TempBlob.ToFieldRef(ParmField);
// 3 Filename
ParmField := ParmRef.Field(3);
ParmField.Value := 'youtube-item-list.pdf';
// 4 RecordID
ParmField := ParmRef.Field(5);
ParmField.Value := Rec.RecordId;
// 5 Table
ParmField := ParmRef.Field(4);
ParmField.Value := DATABASE::Customer;
// 6 Overwrite?
ParmField := ParmRef.Field(6);
ParmField.Value := true;
ParmRef.Modify();
ParmVar := ParmRef;
Codeunit.Run(70319499, ParmVar);
end;
}
}
}
}
Step-by-Step Breakdown
1. Generate the Report as a PDF
First, we create a PDF of the Item List report and store it in a Temp Blob:
TempBlob.CreateOutStream(OutS);
Report.SaveAs(Report::"Item List", '', ReportFormat::Pdf, OutS);
A quick aside on stream naming in AL: an OutStream is something you write into, and an InStream is something you read from. It’s counterintuitive — Erik has an older video called something like “In, Out, Confusing Directions” that covers this topic.
2. Open the Unknown Parameter Table
We open the SharePoint Connector’s parameter table using its ID (70319494) via RecordRef:
ParmRef.Open(70319494);
This table has six fields that we need to populate:
- Field 1 — GUID (unique identifier for the upload)
- Field 2 — Blob (the file content)
- Field 3 — Filename (text)
- Field 4 — Table number (integer — which table the hosting record belongs to)
- Field 5 — Record ID (identifies which record the file is attached to)
- Field 6 — Overwrite flag (boolean)
3. Populate the Fields
Each field is accessed by number through FieldRef and assigned a value:
// GUID
ParmField := ParmRef.Field(1);
ParmField.Value := CreateGuid();
The blob field requires special handling. You can’t directly assign a blob through FieldRef.Value — there’s no CreateInStream or CreateOutStream on FieldRef. Instead, the Temp Blob codeunit has a ToFieldRef method that handles this (Erik notes this was essentially a “dirty hack” Microsoft created when they needed to support blobs in config packages):
ParmField := ParmRef.Field(2);
TempBlob.ToFieldRef(ParmField);
The remaining fields are straightforward value assignments — the filename, the record ID of the customer we’re on (Rec.RecordId), the table number (DATABASE::Customer), and the overwrite flag.
4. The Blob Survival Trick: Insert and Modify
This is a critical detail. When passing a RecordRef as a Variant to Codeunit.Run, all field values survive the transfer — except blobs. The blob field will arrive empty on the other side.
The workaround is to actually persist the record into the (temporary) table:
ParmRef.Insert(); // Save after setting the GUID and before setting the blob
// ... populate remaining fields including the blob ...
ParmRef.Modify(); // Save everything including the blob
Since the target table is a temporary table, this doesn’t affect any real data — it just ensures the blob content is available when the receiving codeunit reads the record.
5. Call the Unknown Codeunit
Finally, we package the RecordRef into a Variant and call the SharePoint Connector’s simplified uploader codeunit by its ID:
ParmVar := ParmRef;
Codeunit.Run(70319499, ParmVar);
The Codeunit.Run method has an overload that accepts a Variant instead of a specific record type. The Variant type in AL is a generic value holder — it’s not strongly typed, which is exactly what we need here since we can’t reference the actual record type at compile time.
The Demo
Erik demonstrates this working end-to-end: navigating to a customer record, clicking the “Upload to SharePoint” action, and watching the PDF appear in the customer’s SharePoint folder. The file “youtube-item-list.pdf” shows up and contains the expected Item List report.
What Can Go Wrong
Quite a lot, actually. Erik demonstrates this by intentionally swapping some field numbers in the code. The extension still compiles perfectly — from this app’s perspective, everything looks fine because there’s no compile-time validation of field numbers or types.
But at runtime, you get errors. In the demo, swapping the field assignments causes a type mismatch — trying to put a Record ID into an integer field — which throws a runtime error. This is the fundamental trade-off: you gain flexibility and optional dependencies, but you lose compile-time safety.
Design Considerations
This approach works because Erik designed the SharePoint Connector with this exact scenario in mind. The connector includes a codeunit (70319499) that accepts parameters through its OnRun trigger via a well-known parameter table structure. If you’re building apps that might need to be called dynamically like this, you need to design that entry point intentionally.
Things to keep in mind:
- There is zero compile-time validation — wrong field numbers, wrong types, and wrong codeunit IDs will all compile but fail at runtime
- Blob fields require the insert/modify workaround to survive the
Varianttransfer - The receiving codeunit needs to be designed to accept a
Variantparameter on itsOnRuntrigger - You should add appropriate error handling for cases where the target app isn’t installed
Summary
Runtime dependencies don’t formally exist in AL, but using RecordRef, FieldRef, Variant, and Codeunit.Run, you can call into another extension’s objects without declaring a compile-time dependency. This is ideal for scenarios where functionality should be optional — available when a companion app is installed, but not requiring it. The trade-off is clear: you sacrifice compile-time safety and IntelliSense for the flexibility of truly optional integrations. For a “fraction of a fraction” use case like optional SharePoint uploads, that’s a trade-off worth making.