Generate Barcodes in Apex

Calling the Barcode Actions from Apex

Each of the six Barcode Flow actions is also a global Apex class in the BarQR namespace. Call them from your own triggers, Queueable and Batch jobs, controllers and REST services to generate barcode files on the server.

Apex class Use it to Result field
BarQR.BarcodeAttachConfigFlowAction Attach a barcode file to a record, using a Barcode Config contentVersionId
BarQR.BarcodeAttachDataFlowAction Attach a barcode file to a record, using your own data contentVersionId
BarQR.BarcodeInternalUrlConfigFlowAction Get an internal file URL, using a Barcode Config url
BarQR.BarcodeInternalUrlDataFlowAction Get an internal file URL, using your own data url
BarQR.BarcodeExternalUrlConfigFlowAction Get a public file URL, using a Barcode Config url
BarQR.BarcodeExternalUrlDataFlowAction Get a public file URL, using your own data url

All six classes follow the same pattern:

  1. Create one or more Request objects (new BarQR.<ClassName>.Request()) and set their fields.
  2. Pass them as a list to the static execute method.
  3. Read the Result list. It has one result per request, in the same order, with success, errorMessage and either contentVersionId or url.

Request fields

Field Type Used by Description
recordId Id Config + Attach Record to read the configured field from, and/or attach the file to.
configName String Config actions Name of the Barcode Config record.
barcodeType String Data actions Barcode type API value, e.g. qrcode. See the supported types.
text String Data actions Text to encode.
scaleX, scaleY Integer Data actions Optional, 1–20. Default 2.
usePng Boolean All Optional. true (default) for PNG, false for SVG.

Important: execute does not throw an exception for invalid input. Always check success and log or handle errorMessage.


Attach a Barcode Using a Config

Generate a barcode from the field set in a Barcode Config and attach it to the record:

BarQR.BarcodeAttachConfigFlowAction.Request req = new BarQR.BarcodeAttachConfigFlowAction.Request();
req.recordId = assetId;
req.configName = 'AssetSerialQR';
req.usePng = true;

BarQR.BarcodeAttachConfigFlowAction.Result res = BarQR.BarcodeAttachConfigFlowAction.execute(
    new List<BarQR.BarcodeAttachConfigFlowAction.Request>{ req }
)[0];

if (res.success) {
    System.debug('Attached barcode file, ContentVersion Id: ' + res.contentVersionId);
} else {
    System.debug(LoggingLevel.ERROR, 'Barcode generation failed: ' + res.errorMessage);
}

Get a Public URL for Your Own Data

Encode any value and get a public download link — for example, to save on a record and use in an email template or an external system. Public URLs require Content Deliveries and Public Links to be enabled in Setup.

Ticket__c ticket = [SELECT Id, Name FROM Ticket__c WHERE Id = :ticketId];

BarQR.BarcodeExternalUrlDataFlowAction.Request req = new BarQR.BarcodeExternalUrlDataFlowAction.Request();
req.barcodeType = 'qrcode';
req.text = 'https://example.com/tickets/' + ticket.Name;
req.scaleX = 4;
req.scaleY = 4;

BarQR.BarcodeExternalUrlDataFlowAction.Result res = BarQR.BarcodeExternalUrlDataFlowAction.execute(
    new List<BarQR.BarcodeExternalUrlDataFlowAction.Request>{ req }
)[0];

if (res.success) {
    ticket.QR_Code_URL__c = res.url;
    update ticket;
}

You can then use the URL field as an image source in an email template or Visualforce page, for example <img src="{!Ticket__c.QR_Code_URL__c}" /> in a Visualforce email template.

Tip: For links only Salesforce users should open, use BarQR.BarcodeInternalUrlDataFlowAction instead. It returns a /sfc/servlet.shepherd/version/download/… URL that requires login.


Email a Barcode as an Attachment

The attach actions return a ContentVersion Id. Query its VersionData to get the image as a Blob and use it anywhere, for example as an email attachment:

BarQR.BarcodeAttachDataFlowAction.Request req = new BarQR.BarcodeAttachDataFlowAction.Request();
req.recordId = orderId;
req.barcodeType = 'code128';
req.text = orderNumber;
req.scaleX = 3;
req.scaleY = 3;

BarQR.BarcodeAttachDataFlowAction.Result res = BarQR.BarcodeAttachDataFlowAction.execute(
    new List<BarQR.BarcodeAttachDataFlowAction.Request>{ req }
)[0];

if (res.success) {
    ContentVersion cv = [SELECT VersionData FROM ContentVersion WHERE Id = :res.contentVersionId];

    Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
    attachment.setFileName(orderNumber + '.png');
    attachment.setContentType('image/png');
    attachment.setBody(cv.VersionData);

    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
    mail.setToAddresses(new List<String>{ customerEmail });
    mail.setSubject('Your order ' + orderNumber);
    mail.setPlainTextBody('Your order barcode is attached.');
    mail.setFileAttachments(new List<Messaging.EmailFileAttachment>{ attachment });
    Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ mail });
}

Generate Barcodes from a Trigger

Image generation runs queries and inserts files, so run it asynchronously instead of inside the trigger transaction. This Queueable attaches a QR code to each new Asset:

public with sharing class AttachAssetQrCodes implements Queueable {
    private final List<Id> assetIds;

    public AttachAssetQrCodes(List<Id> assetIds) {
        this.assetIds = assetIds;
    }

    public void execute(QueueableContext context) {
        List<BarQR.BarcodeAttachConfigFlowAction.Request> requests = new List<BarQR.BarcodeAttachConfigFlowAction.Request>();
        for (Id assetId : assetIds) {
            BarQR.BarcodeAttachConfigFlowAction.Request req = new BarQR.BarcodeAttachConfigFlowAction.Request();
            req.recordId = assetId;
            req.configName = 'AssetSerialQR';
            requests.add(req);
        }

        List<BarQR.BarcodeAttachConfigFlowAction.Result> results = BarQR.BarcodeAttachConfigFlowAction.execute(requests);
        for (Integer i = 0; i < results.size(); i++) {
            if (!results[i].success) {
                System.debug(LoggingLevel.ERROR, assetIds[i] + ': ' + results[i].errorMessage);
            }
        }
    }
}
trigger AssetBarcodeTrigger on Asset (after insert) {
    List<Id> assetIds = new List<Id>();
    for (Asset a : Trigger.new) {
        if (String.isNotBlank(a.SerialNumber)) {
            assetIds.add(a.Id);
        }
    }
    if (!assetIds.isEmpty()) {
        System.enqueueJob(new AttachAssetQrCodes(assetIds));
    }
}

Note: Each request runs its own queries and inserts. Config-based actions use up to five SOQL queries per request, so a single transaction can handle only a limited number of requests. For large inserts, split the Ids into several Queueable jobs of about 20 records each, or use Batch Apex.


Backfill Existing Records with Batch Apex

To create barcodes for records that already exist, use Batch Apex with a small scope:

public with sharing class BackfillAssetQrCodes implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator([SELECT Id FROM Asset WHERE SerialNumber != null]);
    }

    public void execute(Database.BatchableContext context, List<Asset> scope) {
        List<BarQR.BarcodeAttachConfigFlowAction.Request> requests = new List<BarQR.BarcodeAttachConfigFlowAction.Request>();
        for (Asset a : scope) {
            BarQR.BarcodeAttachConfigFlowAction.Request req = new BarQR.BarcodeAttachConfigFlowAction.Request();
            req.recordId = a.Id;
            req.configName = 'AssetSerialQR';
            requests.add(req);
        }
        BarQR.BarcodeAttachConfigFlowAction.execute(requests);
    }

    public void finish(Database.BatchableContext context) {
    }
}

Run it from Developer Console → Debug → Open Execute Anonymous Window with a scope of 20:

Database.executeBatch(new BackfillAssetQrCodes(), 20);

Note: Running the batch again attaches another copy of the barcode to each record. Add a filter to the query, such as a checkbox you set after generation, if you may run it more than once.


Read a Config and Its Value

BarQR.BarcodeController.getData returns a Barcode Config’s settings together with the field value it resolves for a record. Use it to check a config before generating, or to build your own output:

BarQR.BarcodeConfigData data = BarQR.BarcodeController.getData('AssetSerialQR', assetId);
if (String.isNotBlank(data.error)) {
    System.debug(LoggingLevel.ERROR, data.error);
} else {
    System.debug(data.barcodeType + ' will encode: ' + data.value);
}

BarcodeConfigData contains barcodeType, value, scaleX, scaleY, showTextAlso, imageRotation, fontRendering, saveFormats and error.


Writing Tests

The actions run normally in Apex tests. Create the Barcode Config in your test data, using the BarQR__ prefix on the object and its fields:

@IsTest
private class AttachAssetQrCodesTest {
    @IsTest
    static void attachesQrCode() {
        insert new BarQR__Barcode_Config__c(
            Name = 'AssetSerialQR',
            BarQR__Object_API_Name__c = 'Asset',
            BarQR__Field_API_Name__c = 'SerialNumber',
            BarQR__Barcode_Type__c = 'qrcode',
            BarQR__Font_Rendering__c = 'A',
            BarQR__Image_Rotation__c = 'N',
            BarQR__Scale_X__c = 2,
            BarQR__Scale_Y__c = 2
        );
        Account acct = new Account(Name = 'Test');
        insert acct;
        Asset asset = new Asset(Name = 'Scanner', SerialNumber = 'SN-1001', AccountId = acct.Id);
        insert asset;

        Test.startTest();
        System.enqueueJob(new AttachAssetQrCodes(new List<Id>{ asset.Id }));
        Test.stopTest();

        Assert.areEqual(1, [SELECT COUNT() FROM ContentDocumentLink WHERE LinkedEntityId = :asset.Id]);
    }
}

Notes

  • Apex calls check the running user’s access, so the user needs the Barcode & QR Code Permissions permission set and read access to the configured object and field. See Assign Permissions.
  • Server-side generation uses only the barcode type and scale. See Where Each Setting Applies.
  • The classes, fields and method signatures above are the package’s supported public API. Other Apex classes in the package are internal and cannot be called from your code.