IRONSOFTWAREHOME

How to Control UTF-8 ECI Tagging for 2D Barcodes in C#

Ahmad Sohail
Ahmad Sohail
Updated: September 9, 2026

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.

Please note: Requires IronBarcode 2026.9.2 or later. The API does not exist in 2026.8.6 or earlier, where the call produces a compile error rather than a runtime failure.
Quickstart: Set the ECI Policy for Every 2D Barcode

Assign the policy once, then generate barcodes as you normally would. Every 2D format follows it, with no further arguments.

  1. 1Install IronBarcode with NuGet Package Manager

    PM > Install-Package BarCode

  2. 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");
    C#
  3. 3Deploy to test on your live environment

    Start using IronBarcode in your project today with a free trial
    arrow pointer


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
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");
C#

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.

DataMatrix encoding an ASCII part number under EciMode.Auto, a 16 by 16 module symbol with no ECI header

ASCII, Auto — 16 × 16 modules, no header

DataMatrix encoding a part number containing a German umlaut under EciMode.Auto, a 22 by 22 module symbol carrying a UTF-8 ECI header

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

DataMatrix encoding the same ASCII part number under EciMode.ForceUtf8, a 16 by 16 module symbol the same size as the plain ASCII one

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.
Please note: ECI applies to 2D formats only. 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);
}
C#

How 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");
C#

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");
C#

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");
C#

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.

Warning: 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.
Text

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);
    }
}
C#

The 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");
C#

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

ScenarioECI ModeWhere to Set
General-purpose generation, mixed contentAutoNothing to do; it is the default
Fleet of older scanners that fail on ECIOffDefaultEciMode at startup
Standard requires explicit encoding on every symbolForceUtf8DefaultEciMode at startup
One compliance symbol among otherwise normal outputForceUtf8Per-call, DataMatrix only
Payload contents not guaranteed ASCIIAuto, or Off with a catchEither, 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 Sohail
Full Stack Developer

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.

...
Read More

Ready to Get Started?

Nuget Downloads 2,436,310Version:2026.9just released

Get your free 30-day Trial Key instantly.
No credit card or account creation required
Key in blue circle

Get your free 30-day Trial Key instantly.

Your trial license will be sent to your email address

No limitations. 100% unlocked. No credit card.

OR
bullet_checkedNo credit card or account creation requiredNo limitations. 100% unlocked. No credit card.
  • Logo Aetna
  • Logo NASA
  • Logo GE
  • Logo Porsche
  • Logo USDA
  • Logo Qatar
Join Millions of Engineers who’ve tried IronPDF
Book your free Live Demo
Booking Badge

Trusted by Millions of Engineers Worldwide

Iron Software's customer logos
Get Your No-Obligation Consult
Complete the form below or email sales@ironsoftware.com
Your details will always be kept confidential.
Trusted by Millions of Engineers Worldwide
Iron Software's customer logos
Get your free 30-day Trial Key instantly.
No credit card or account creation required