How to Control UTF-8 ECI Tagging for 2D Barcodes in C#
A 2D barcode that came out a different size than expected, a legacy scanner that refuses a symbol it used to read, and a standard demanding that every symbol declare its encoding all come down to the same question: does the barcode carry a UTF-8 ECI (Extended Channel Interpretation) header?
That header tells a scanner to interpret the payload as UTF-8, which matters for Chinese, Arabic, Thai or any other content outside plain ASCII. It is not free. The header consumes codewords, enough that a payload already near a size boundary can spill into a larger symbol, and scanners built before ECI existed reject a symbol carrying one. IronBarcode therefore writes the header only when the data contains non-ASCII characters, and the EciMode setting overrides that decision in either direction.
Assign the policy once, then generate barcodes as you normally would. Every 2D format follows it, with no further arguments.
-
1Install IronBarcode with NuGet Package Manager
-
2Copy and run this code snippet.
IronBarCode.BarcodeWriter.DefaultEciMode = IronBarCode.EciMode.ForceUtf8; IronBarCode.BarcodeWriter.CreateBarcode("BK005|8389|0001", IronBarCode.BarcodeEncoding.QRCode).SaveAsPng("part-qr.png");IronBarCode.BarcodeWriter.DefaultEciMode = IronBarCode.EciMode.ForceUtf8 IronBarCode.BarcodeWriter.CreateBarcode("BK005|8389|0001", IronBarCode.BarcodeEncoding.QRCode).SaveAsPng("part-qr.png") -
3Deploy to test on your live environment
Start using IronBarcode in your project today with a free trial
Minimal Workflow (5 steps)
- Download the C# library to control barcode ECI encoding
- Generate a 2D barcode with
CreateBarcodeorCreateDataMatrixas you do today - Check whether the payload is pure ASCII, since that decides whether a header is written
- Pass an
EciModetoCreateDataMatrixwhere one symbol needs different behaviour - Set
BarcodeWriter.DefaultEciModeat startup to apply one policy across every 2D format
When Does IronBarcode Add an ECI Header?
Nothing about the call you already write changes. Generate a DataMatrix from an ASCII part number and the symbol carries no header; hand the same method a payload with an umlaut in it and the header appears, because the value needs it. An EciMode argument on the call overrides that judgement.
using IronBarCode;
// Pure ASCII: no ECI header, smallest symbol
var ascii = DataMatrixWriter.CreateDataMatrix("BK005|8389|0001");
ascii.SaveAsPng("part-ascii.png");
// Non-ASCII: header written automatically
var unicode = DataMatrixWriter.CreateDataMatrix("BK005|8389|Bauteil-Größe");
unicode.SaveAsPng("part-unicode.png");
// Same ASCII value, header forced on
var tagged = DataMatrixWriter.CreateDataMatrix("BK005|8389|0001", 500, EciMode.ForceUtf8);
tagged.SaveAsPng("part-tagged.png");Imports IronBarCode
' Pure ASCII: no ECI header, smallest symbol
Dim ascii = DataMatrixWriter.CreateDataMatrix("BK005|8389|0001")
ascii.SaveAsPng("part-ascii.png")
' Non-ASCII: header written automatically
Dim unicode = DataMatrixWriter.CreateDataMatrix("BK005|8389|Bauteil-Größe")
unicode.SaveAsPng("part-unicode.png")
' Same ASCII value, header forced on
Dim tagged = DataMatrixWriter.CreateDataMatrix("BK005|8389|0001", 500, EciMode.ForceUtf8)
tagged.SaveAsPng("part-tagged.png")The three symbols above differ only in payload and in whether a mode was supplied. The images below are drawn at a fixed module size, so the symbols differ in overall size the way they would on a printed label.

ASCII, Auto - 16 × 16 modules, no header

Non-ASCII, Auto - 22 × 22 modules, header added

ASCII, ForceUtf8 - 16 × 16 modules, header forced
The ASCII payload encodes to a 16 × 16 grid whether or not a header is forced onto it: this particular value has enough spare capacity that the extra codewords cost nothing. The non-ASCII payload needs a full size tier more, 22 × 22, because the header and the multi-byte content together no longer fit at 16 × 16. A header only grows the symbol when the payload is already close to a size boundary, and whether that is true for a given value is not visible from the string itself.
What Are the Three ECI Modes?
Auto: Writes the UTF-8 ECI header only when the data contains non-ASCII characters. This is the default, and it produces the smallest symbol for a pure-ASCII payload.ForceUtf8: Always writes the header, including for pure ASCII. Use it for strict standards compliance, or where every symbol must declare its encoding.Off: Never writes the header. Use it only where the data is known to be ASCII or Latin-1 and the decoder does not understand ECI.
EciMode has no effect on 1D barcodes such as Code 128, EAN or UPC.How Do I Set the ECI Mode?
How Do I Apply One Policy Across the Application?
BarcodeWriter.DefaultEciMode is a public static field on BarcodeWriter, defaulting to EciMode.Auto. Assign it once and every 2D barcode the application generates follows it, whichever format and whichever part of the codebase produced it. It mirrors the existing BarcodeWriter.DefaultCharacterEncoding field:
using IronBarCode;
// Set once at startup
BarcodeWriter.DefaultEciMode = EciMode.Off;
// No mode argument passed anywhere below
GeneratedBarcode label = CreatePartLabel("BK005|8389|0001");
label.SaveAsPng("part-label.png");
GeneratedBarcode ticket = BarcodeWriter.CreateBarcode("TKT-77120", BarcodeEncoding.Aztec);
ticket.SaveAsPng("gate-ticket.png");
static GeneratedBarcode CreatePartLabel(string partNumber)
{
// Follows DefaultEciMode
return DataMatrixWriter.CreateDataMatrix(partNumber, 300);
}Imports IronBarCode
' Set once at startup
BarcodeWriter.DefaultEciMode = EciMode.Off
' No mode argument passed anywhere below
Dim label As GeneratedBarcode = CreatePartLabel("BK005|8389|0001")
label.SaveAsPng("part-label.png")
Dim ticket As GeneratedBarcode = BarcodeWriter.CreateBarcode("TKT-77120", BarcodeEncoding.Aztec)
ticket.SaveAsPng("gate-ticket.png")
Private Shared Function CreatePartLabel(partNumber As String) As GeneratedBarcode
' Follows DefaultEciMode
Return DataMatrixWriter.CreateDataMatrix(partNumber, 300)
End FunctionHow Do I Override the Mode for a Single DataMatrix?
A per-call EciMode on a CreateDataMatrix overload wins over BarcodeWriter.DefaultEciMode, and applies to that call only.
using IronBarCode;
// Application-wide policy
BarcodeWriter.DefaultEciMode = EciMode.Auto;
// Per-call override wins
var compliance = DataMatrixWriter.CreateDataMatrix("LOT-4471-A", 200, EciMode.ForceUtf8);
compliance.SaveAsPng("compliance-label.png");
// No override; follows DefaultEciMode
var standard = DataMatrixWriter.CreateDataMatrix("LOT-4471-B", 200);
standard.SaveAsPng("standard-label.png");Imports IronBarCode
' Application-wide policy
BarcodeWriter.DefaultEciMode = EciMode.Auto
' Per-call override wins
Dim compliance = DataMatrixWriter.CreateDataMatrix("LOT-4471-A", 200, EciMode.ForceUtf8)
compliance.SaveAsPng("compliance-label.png")
' No override; follows DefaultEciMode
Dim standard = DataMatrixWriter.CreateDataMatrix("LOT-4471-B", 200)
standard.SaveAsPng("standard-label.png")Every CreateDataMatrix overload that takes an EciMode follows the same pattern. The data goes in first, as a string, a byte[] or a Stream. Sizing comes next, either as a single pixel size or as a DataMatrixWriter.DataMatrixShape with an explicit width and height. EciMode is always the final parameter. The example above passes a size; the shape overload takes the same EciMode in the same final position:
using IronBarCode;
// Rectangular symbol, ECI forced on
var wide = DataMatrixWriter.CreateDataMatrix(
"BK005|8389|0001",
DataMatrixWriter.DataMatrixShape.Rectangular,
400,
100,
EciMode.ForceUtf8);
wide.SaveAsPng("part-wide.png");Imports IronBarCode
' Rectangular symbol, ECI forced on
Dim wide = DataMatrixWriter.CreateDataMatrix("BK005|8389|0001", DataMatrixWriter.DataMatrixShape.Rectangular, 400, 100, EciMode.ForceUtf8)
wide.SaveAsPng("part-wide.png")What Controls QR, Aztec and PDF417?
These three formats have no EciMode argument. There is no such parameter on QRCodeWriter, and none on the generic BarcodeWriter.CreateBarcode overloads, so BarcodeWriter.DefaultEciMode is the only way to control them.
using IronBarCode;
// QR, Aztec, PDF417: no per-call EciMode
BarcodeWriter.DefaultEciMode = EciMode.ForceUtf8;
var qr = BarcodeWriter.CreateBarcode("ORDER-88213", BarcodeEncoding.QRCode);
qr.SaveAsPng("order-qr.png");
var aztec = BarcodeWriter.CreateBarcode("ORDER-88213", BarcodeEncoding.Aztec);
aztec.SaveAsPng("order-aztec.png");
// New value applies to later calls
BarcodeWriter.DefaultEciMode = EciMode.Auto;
var qrAuto = BarcodeWriter.CreateBarcode("ORDER-88214", BarcodeEncoding.QRCode);
qrAuto.SaveAsPng("order-qr-auto.png");Imports IronBarCode
' QR, Aztec, PDF417: no per-call EciMode
BarcodeWriter.DefaultEciMode = EciMode.ForceUtf8
Dim qr = BarcodeWriter.CreateBarcode("ORDER-88213", BarcodeEncoding.QRCode)
qr.SaveAsPng("order-qr.png")
Dim aztec = BarcodeWriter.CreateBarcode("ORDER-88213", BarcodeEncoding.Aztec)
aztec.SaveAsPng("order-aztec.png")
' New value applies to later calls
BarcodeWriter.DefaultEciMode = EciMode.Auto
Dim qrAuto = BarcodeWriter.CreateBarcode("ORDER-88214", BarcodeEncoding.QRCode)
qrAuto.SaveAsPng("order-qr-auto.png")Where a QR code and a DataMatrix need different ECI behaviour in the same run, the DataMatrix takes the per-call argument and the QR code follows the static default.
How Does the Setting Behave at Run Time?
When Does DefaultEciMode Take Effect?
CreateBarcode, CreateDataMatrix and CreateQrCode return an object whose mode is not yet fixed. DefaultEciMode is read when the barcode is saved or otherwise rendered, so assigning a new value mid-run affects every barcode saved after that point, including objects created before the change.
This catches out the common batch shape: create several barcodes while changing DefaultEciMode between calls, then save them together at the end. All of them take the mode that was set last. Save each barcode before changing the mode for the next one, or pass EciMode per call to CreateDataMatrix, which the static default does not reach.
DefaultEciMode is a mutable process-global static and is not thread-safe. A reassignment is visible to every thread immediately, so changing it while another thread is generating or saving means that thread observes the change mid-run. Treat it as application-startup policy, exactly as with BarcodeWriter.DefaultCharacterEncoding.When Does EciMode.Off Throw?
Off is a promise that the data is ASCII. Break that promise and the call throws IronBarCodeEncodingException, under either BarcodeWriter.DefaultCharacterEncoding setting. The UTF-8 default gives the more useful message:
IronBarCode Encoding Error for Barcode Format DataMatrix : EciMode.Off cannot be used with non-ASCII data while BarcodeWriter.DefaultCharacterEncoding is UTF-8, because the UTF-8 bytes require the ECI header to be decoded correctly and would otherwise be read as ISO-8859-1. Use EciMode.Auto or EciMode.ForceUtf8, or set BarcodeWriter.DefaultCharacterEncoding to a matching single-byte encoding.
Under ISO_8859_1 the same combination surfaces as a lower-level native encoder error.
The payload is often not fully under the application's control: user input, an upstream feed, a product name from a supplier catalogue. A defensive wrapper covers it:
using IronBarCode;
using IronBarCode.Exceptions;
using System;
public class LabelGenerator
{
// Payload not guaranteed ASCII
public bool TryCreateLabel(string payload, string outputPath)
{
try
{
var barcode = DataMatrixWriter.CreateDataMatrix(payload, 200, EciMode.Off);
barcode.SaveAsPng(outputPath);
return true;
}
catch (IronBarCodeEncodingException ex)
{
// Non-ASCII cannot round-trip without ECI
Console.WriteLine("Payload is not ASCII, cannot generate with ECI disabled: {0}", ex.Message);
return false;
}
}
// Fall back to Auto
public void CreateLabelWithFallback(string payload, string outputPath)
{
if (TryCreateLabel(payload, outputPath))
{
return;
}
var barcode = DataMatrixWriter.CreateDataMatrix(payload, 200, EciMode.Auto);
barcode.SaveAsPng(outputPath);
}
}Imports IronBarCode
Imports IronBarCode.Exceptions
Public Class LabelGenerator
' Payload not guaranteed ASCII
Public Function TryCreateLabel(ByVal payload As String, ByVal outputPath As String) As Boolean
Try
Dim barcode = DataMatrixWriter.CreateDataMatrix(payload, 200, EciMode.[Off])
barcode.SaveAsPng(outputPath)
Return True
Catch ex As IronBarCodeEncodingException
' Non-ASCII cannot round-trip without ECI
Console.WriteLine("Payload is not ASCII, cannot generate with ECI disabled: {0}", ex.Message)
Return False
End Try
End Function
' Fall back to Auto
Public Sub CreateLabelWithFallback(ByVal payload As String, ByVal outputPath As String)
If TryCreateLabel(payload, outputPath) Then
Return
End If
Dim barcode = DataMatrixWriter.CreateDataMatrix(payload, 200, EciMode.Auto)
barcode.SaveAsPng(outputPath)
End Sub
End ClassThe fallback here switches that symbol to Auto, on the view that a correct barcode reaching an ECI-aware scanner beats no barcode. The right recovery depends on the deployment: where the scanners genuinely cannot read ECI, rejecting the record for manual handling is the safer choice.
The type is also listed in the error handling and debugging guide, which covers the exception family without this specific cause.
How Do I Choose a Mode?
How Do I Tag Every Symbol Again?
One line, at startup:
using IronBarCode;
// Restore pre-2026.9.2 always-tagged output
BarcodeWriter.DefaultEciMode = EciMode.ForceUtf8;
// Both symbols carry the header
var dataMatrix = DataMatrixWriter.CreateDataMatrix("SKU-10045", 200);
dataMatrix.SaveAsPng("sku-datamatrix.png");
var qr = BarcodeWriter.CreateBarcode("SKU-10045", BarcodeEncoding.QRCode);
qr.SaveAsPng("sku-qr.png");Imports IronBarCode
' Restore pre-2026.9.2 always-tagged output
BarcodeWriter.DefaultEciMode = EciMode.ForceUtf8
' Both symbols carry the header
Dim dataMatrix = DataMatrixWriter.CreateDataMatrix("SKU-10045", 200)
dataMatrix.SaveAsPng("sku-datamatrix.png")
Dim qr = BarcodeWriter.CreateBarcode("SKU-10045", BarcodeEncoding.QRCode)
qr.SaveAsPng("sku-qr.png")ForceUtf8 returns every 2D format to always-tagged behaviour, producing byte-identical output to 2026.8.6 and earlier for the same input. This is the right choice for an application whose downstream systems were built against the tagged output, for a standard that requires an explicit encoding declaration on every symbol, or simply as a way to defer the change while its effects are assessed.
The reverse case is worth naming too. An application that upgraded, found its symbols got smaller, and is happy about it needs no action at all: Auto is already the default.
Which Mode Fits Which Situation?
Recommended ECI mode by scenario, and where each one is configured
| Scenario | ECI Mode | Where to Set |
|---|---|---|
| General-purpose generation, mixed content | Auto | Nothing to do; it is the default |
| Fleet of older scanners that fail on ECI | Off | DefaultEciMode at startup |
| Standard requires explicit encoding on every symbol | ForceUtf8 | DefaultEciMode at startup |
| One compliance symbol among otherwise normal output | ForceUtf8 | Per-call, DataMatrix only |
| Payload contents not guaranteed ASCII | Auto, or Off with a catch | Either, with exception handling |
Where to Go Next
The behaviour described here is one setting with three values, and most applications will set it once and never revisit it. The two decisions worth making deliberately are whether the scanner fleet understands ECI, and whether any downstream standard requires a declaration on every symbol.
For which barcode formats support Unicode content in the first place, see the Write UTF-8 and Unicode Barcodes how-to, which covers format-level Unicode support and links here for the ECI detail. For generating the 2D formats themselves, the Create 2D Barcodes how-to covers DataMatrix, QR, Aztec and PDF417 generation. The complete class documentation is in the IronBarcode API reference.
Ahmad is a full-stack developer with a strong foundation in C#, Python, and web technologies. He has a deep interest in building scalable software solutions and enjoys exploring how design and functionality meet in real-world applications.