Code 128 code sets and the Code 39 charset

Two of the 7 symbologies this site generates —Code 128 and Code 39 — carry no check digit you can type or see. Elsewhere the verification layer has arithmetic to do; for these two its whole job is character work: decide whether each character can be encoded, disclose any normalisation, and, for Code 128, report which of its code sets the content forces. This guide walks through that analysis in the order the engine performs it, with every figure computed at build time by the code the generators run in your browser; the other engine guides are listed on the guides hub.

Where these two sit in the validation pipeline

Before the renderer is called, the site normalises the input, enforces the selected symbology's length and character rules, and computes or verifies any check digit; a mismatch stops the flow and no bars are requested. For Code 128 and Code 39 the compute-or-verify step has nothing to do: asked whether the validated Code 128 input Part 4402 carries a check-digit field, the engine answers no, and the payload cross-check that re-derives GTIN check digits passes both symbologies trivially — true for that input, true for the Code 39 example below. In the engine's own words, their integrity comes from charset validation plus, for Code 128, the symbol's internal mod-103 check. That leaves three jobs: enforce the character set, disclose any normalisation in the notes the readout shows, and stop with a named reason when a character cannot be encoded.

The Code 39 charset, read from the engine

The engine defines standard Code 39 as one string of encodable characters, printed here (␣ marks the space):

ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789␣-.$/+%

Counted at build time, that is 43 characters: 26 uppercase letters, 10 digits, and 7 others — ␣ - . $ / + %. The asterisk used for the start and stop patterns is deliberately excluded from the data charset. No lowercase, no comma, no hash, no accented letter: each is a refusal, never a substitution.

What the charset check reports

validateCode39Charset walks the input once and returns whether every character is encodable plus the distinct offending characters in first-appearance order. For the fictional input A,B#C it reports valid: no, naming “,”, “#”. It does not uppercase: fed the fictional lowercase word part directly, it reports valid: no and names “p”, “a”, “r”, “t”.

The disclosed uppercase conversion

The Code 39 branch of validateInput uppercases first and checks the charset second, and when it uppercases it says so. For the fictional input part-4402:

Payload handed to the renderer: PART-4402

Note reported: “Code 39 has no lowercase letters — your input was converted to uppercase.”

This is the one normalisation either validator performs. Spaces are Code 39 data, including at the edges, so they are preserved rather than trimmed: the fictional input ␣PART␣4402␣ becomes the payload ␣PART␣4402␣ — 11 characters in, 11 out.

How a Code 39 refusal reads

When a character is outside the set, generation stops and the message names every offending character. For A,B#C the engine returns:

Code 39 cannot encode “,”, “#”. Its character set is A–Z, 0–9, space, and - . $ / + % — for anything richer, use Code 128.

The input rule under the field states the same limits in one line: “Uppercase A–Z, digits, space, and - . $ / + % only. 1–60 characters.” The length cap is this engine's rule, probed rather than typed: the longest accepted input is 60 characters, and one more returns “Code 39 is a wide symbology; this tool caps input at 60 characters because longer symbols become impractical to print and scan.” The optional mod-43 check and the full-ASCII extension are deliberately not added; the generator page states both with the reason.

Code 128's three code sets and what forces them

Code 128 has three code sets, A, B and C, and the encoder may switch between them inside one symbol. The analysis reads your text and reports which sets the content forces and where compression applies, starting with the alphabet: probed one code point at a time at build time, the accepted characters form one contiguous range of 95 characters — printable ASCII, from code point 32, the space, up to 126, ~.

  • Code set A holds the ASCII control characters, which lie outside the accepted range, so nothing this tool accepts can force set A.
  • Code set B is forced by any lowercase letter — the analysis sets needsSetB as soon as it meets one, because set A has no lowercase.
  • Code set C carries digits two per symbol character. The analysis reports pureSetC when the input is digits only with an even count, and otherwise tracks the longest run of consecutive digits; for uppercase text with digits, its guidance changes once a run reaches 4 digits — a threshold probed from the engine, not typed.

Four fictional inputs and the generator's own example value, every column the analyser's output:

Input Longest digit run Pure set C Needs set B Guidance reported
40456789000124
digits only, even count
14 yes no All digits with an even count — encoded in code set C, two digits per symbol character (the most compact case).
4045678900012
digits only, odd count
13 no no All digits with an odd count — mostly code set C, with one digit carried in code set A or B; adding a leading zero would make it fully paired.
BG-2026-0001
uppercase with a digit run
4 no no Uppercase text with digit runs — code set B (or A) for the text, switching to code set C where four or more digits run consecutively.
part-4402a
contains lowercase
4 no yes Contains lowercase letters, which only code set B provides — the encoder will use code set B (switching to C for long digit runs).
AB12-CD3
uppercase, short digit runs
2 no no Uppercase letters, digits, and punctuation — encodable in code set A or B; the encoder picks whichever minimizes symbol length.

The analyser decides in a fixed order: a character outside the alphabet ends the analysis with a refusal; digits-only input is pure set C when the count is even and mostly set C with one unpaired digit when it is odd; a lowercase letter makes the guidance about set B; a digit run at or above the threshold makes it about switching to C; anything else is encodable in A or B.

Advice, not encoding

The methodology draws this line precisely: the code-set analysis is validation and advice, and the actual code-set optimisation inside the symbol is the renderer's, following the same rules. The payload therefore reaches the renderer unchanged: the fictional input Part 4402 yields the payload Part 4402, case and space intact, with the guidance “Contains lowercase letters, which only code set B provides — the encoder will use code set B (switching to C for long digit runs).” carried in the notes beside it.

What the Code 128 validator refuses

Full Code 128 can carry ASCII control characters in set A and Latin-1 text via the FNC4 convention. Both are deliberate omissions here, stated with the reason: they need conventions this tool does not expose. A control character is named by its code point — for the fictional input AB, a tab, CD, the engine returns:

This tool encodes printable ASCII only — it cannot encode control character U+0009. Full Code 128 can carry control characters and accented text via special conventions, but supporting them halfway would produce codes that scan differently across devices.

An accented letter is named as itself: for the fictional input café the same message names “é” in place of the code point. The length cap is likewise this engine's rule, probed rather than typed: the longest accepted Code 128 input is 80 characters, and one more returns “This tool caps Code 128 input at 80 characters — longer symbols become too dense to print and scan reliably at ordinary sizes.” The input rule under the field reads “Printable ASCII characters (letters, digits, punctuation, space). 1–80 characters.”

Reading the notes, the errors and the misreadings

The readout under either generator has two channels. Notes describe what validation did or observed: for Code 39, the uppercase conversion when it happened; for Code 128, the code-set guidance. Errors mean the flow stopped and no bars were requested. Three misreadings account for most confusion:

  1. “The guidance says set B, so the symbol was encoded in set B.” The guidance describes what the content forces; the renderer performs the selection, and the payload it receives is unaltered.
  2. “Where is the check digit?” There is no visible one in either symbology and nothing is appended. A number that needs a GTIN check digit belongs in an EAN, UPC or ITF-14 symbol — see how GTIN check digits are computed.
  3. “An accepted payload means the label will scan.” Acceptance means the characters are encodable and the length is within the cap — a property of the text, not of a print.

Where the checks stop

Once the payload is accepted and rendered, the interactive generator rasterises the SVG actually shown on the page and asks an independent decoding engine to read those pixels; the preview and download stay withheld when the decoder cannot read the image or reads a different payload (see the verifier guide). That is a check of the generated pixels, not of anything printed: no browser check can certify ink, substrate, scaling, contrast or quiet zones after printing, so a physical production sample must still be scanned; the Barcode Verifier exposes the same reader for an uploaded image. Because Code 128 and Code 39 encode whatever text you give them, encoding a product number in either does not allocate or license that number — see Do I need a GS1 barcode?

Frequently asked questions

Why did the Code 39 generator change my lowercase letters?

Because the Code 39 charset has no lowercase letters — 43 characters, 26 of them uppercase letters. The validator uppercases first, then checks the charset, and says so in the notes: “Code 39 has no lowercase letters — your input was converted to uppercase.”

Does the code-set guidance change what gets encoded?

No. The analysis is validation and advice; the actual code-set optimisation inside the symbol is the renderer’s, following the same rules, and the payload it receives is exactly the text you typed.

The analyser suggests a leading zero. Why does it not add one?

Because that would alter your data, and the Code 128 validator never alters a payload. Code set C carries digits in pairs, so an odd count leaves one digit unpaired; the guidance says so. The validated payload for the fictional input 4045678900012 is still 13 characters long — the advice is in the notes, not the payload.

Should I type the Code 39 start and stop asterisks myself?

No. The asterisk is excluded from the data charset, so a typed one is refused by name: for the fictional input *ABC* the charset check reports “*”. The start and stop patterns belong to the symbol, not to your data.

Why are control characters and accented letters refused in Code 128?

This tool accepts printable ASCII only — 95 characters, from the space to “~”. Control characters (code set A) and Latin-1 text (the FNC4 convention) need conventions this tool does not expose, so they are refused with an explanation instead of half-supported.

Math, not optics: this guide describes how the validator checks characters and code sets, nothing more. Whether a printed Code 128 or Code 39 symbol scans depends on contrast, size, substrate and preserved quiet zones, so test with your own scanner before production or print use. See the methodology page for how the verification layer works, and the guides hub for the rest of the series.