Code 39 Barcode Generator
Code 39 is the elder statesman of alphanumeric barcodes — simple, forgiving to print, readable by practically anything ever sold with a scan head, and still required by plenty of industrial and legacy systems. Its price is a hard 43-character alphabet and a wide footprint. Both are stated below, not glossed over.
Read next: Code 128 code sets and the Code 39 charset walks through the analysis this validator performs — which characters force a Code 128 code set, when set C compression applies, and how the Code 39 table is enforced. More in the guides to the verification layer.
The character set — all of it
Standard Code 39 encodes exactly these 43 characters (␣ marks the space):
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789␣-.$/+%
Counted from the validator's own table: 26 letters, 10 digits,
1 space, and 6 symbols — -.$/+% — so
26 + 10 + 1 + 6 =
43. The start/stop asterisk is absent because it is a delimiter, not data; a typed
* is just another unencodable character, as the walk-through below shows.
No lowercase, no commas, no #, no accents. When your input contains lowercase
letters this tool uppercases them and reports the change in the readout; when it contains
anything else, generation stops and the offending characters are named. A "full ASCII" Code 39
variant exists that pairs characters to reach the rest of the ASCII table, but it needs a
scanner configured to expect it — this tool sticks to the standard set that every reader
understands.
What the validator does before a single bar is drawn
Every generator on this site follows the same order — normalise the input, enforce the symbology's length and character rules, compute or verify any check digit, stop on a mismatch — before an accepted payload reaches the renderer. Code 39 has no check digit, so here the pipeline has these stops. First, an empty box is refused. Second, if any lowercase letter is present, the whole input is converted to uppercase and a note is recorded. Third, every character is checked against the 43-character table; each distinct offender is collected once, in the order it first appears, and a non-empty list stops the flow with those characters named. Fourth, the length cap is enforced. A payload that clears every stop is passed on exactly as it stands — nothing dropped, substituted, or trimmed in silence.
The readout beneath the generator prints the notes from that pipeline.
PART-4402 needs no normalisation, produces 0 notes, and reaches
the renderer as PART-4402, byte for byte.
part-4402 produces 1 note, quoted here exactly as the engine
writes it — “Code 39 has no lowercase letters — your input was converted to uppercase.” — and becomes PART-4402. That note is the
disclosure policy in one line: something changed, and the readout says what.
Case is the only substitution this validator makes, because it is the only one whose intent is unambiguous: a lowercase letter has exactly one Code 39 counterpart. A comma, a hash, an accented letter, or a stray asterisk has none, and choosing a replacement for you would launder an input error into a valid wrong label — the same reasoning that makes the GTIN generators report a wrong check digit instead of repairing it. The charset check itself knows nothing of case: run on the raw lowercase text it reports “p”, “a”, “r”, “t” as unencodable, so the conversion must happen one step earlier, in the validator, and that is where the disclosure is written.
Worked walk-through: fictional inputs
None of these is a real part number. Each row is computed at build time by the validator that runs in your browser, so the messages are the engine's own words.
| Input | What the validator does | What the readout or error says |
|---|---|---|
PART-4402 |
Accepted unchanged — 9 characters, 0 notes. | Payload PART-4402; no note. |
part-4402 |
Uppercased; the change is disclosed. | Payload PART-4402; note: “Code 39 has no lowercase letters — your input was converted to uppercase.” |
␣PAD␣ |
Accepted exactly; both edge spaces kept — 5 characters, 0 notes. | Payload ␣PAD␣; no note. |
AB#CD |
Refused; the offender is named. | “Code 39 cannot encode “#”. Its character set is A–Z, 0–9, space, and - . $ / + % — for anything richer, use Code 128.” |
*AB* |
Refused; the delimiter is not data. | “Code 39 cannot encode “*”. Its character set is A–Z, 0–9, space, and - . $ / + % — for anything richer, use Code 128.” |
CAFÉ |
Refused; no Code 39 counterpart exists. | “Code 39 cannot encode “É”. Its character set is A–Z, 0–9, space, and - . $ / + % — for anything richer, use Code 128.” |
a!a! |
Uppercased first, then refused; the strict charset check on the raw text would have listed “a” and “!”. | “Code 39 cannot encode “!”. Its character set is A–Z, 0–9, space, and - . $ / + % — for anything richer, use Code 128.” |
A repeated 61 times |
Passes the character check, then refused by the length cap — the last rule. | “Code 39 is a wide symbology; this tool caps input at 60 characters because longer symbols become impractical to print and scan.” |
If the validator's rules changed, this table would change with them or the build would fail.
Nine elements, three wide
The name is the design: each character is nine elements — five bars, four spaces — of which exactly three are wide. That fixed 3-of-9 structure makes every character self-checking (a single misread element cannot produce another valid character), which is why Code 39 traditionally ships without a check digit. It is also why the symbology is so wide: nine elements plus an inter-character gap per character adds up fast, which is why this tool enforces a cap of 60 characters. The validator's own words: “Code 39 is a wide symbology; this tool caps input at 60 characters because longer symbols become impractical to print and scan.”
Reading the readout: common misreadings
- “The tool changed my data.” Only lowercase letters, and only to their uppercase counterparts; the note quoted above is written every time it happens. A payload with no note is your input, byte for byte.
-
“A space at the edge does not count.” It does. The GTIN generators strip the spaces and
hyphens people paste into digit strings because a GTIN contains neither; here the space is one
of the 43 characters, so
␣PAD␣stays 5 characters with 0 notes, and only a genuinely empty input is refused. A legacy system that expects a padded field gets the padding you typed; one that does not will see a leading space you did not notice. Read the payload in the readout, not the input box. -
“The error named one character, but I typed several bad ones.” Each distinct offender
is listed once, in first-appearance order, after the uppercase step — which is why
a!a!is refused for “!” alone.
What this generator does not add
No mod-43 check digit (optional in the standard, surprising to systems that don't expect it), no full-ASCII extension, no start/stop asterisks in your data — the renderer applies the delimiters itself. What you type, uppercased if needed, is exactly what a scanner will beep back. The downloads include a ten-module quiet zone on each side; keep it.
Because no check digit exists, the payload cross-check the GTIN generators run after
validation — re-deriving the check digit with independent arithmetic — has nothing to
recompute here and passes by construction: for PART-4402 it returns
true. The symbol's integrity rests on the charset check plus the self-checking
3-of-9 structure, and the readout shows no check-digit line because there is none.
The ZPL download keeps the same discipline: the exporter re-runs the charset check on the
validated payload before building the label document, and its first note for
PART-4402 reads “Code 39 is printed without a check digit, matching the on-site generator.” How it treats the GTIN symbologies
differently — stripping the check digit for the printer to recompute, and saying so — is in
ZPL and EPS export explained.
Verified after rendering — and where that stops
Validation is the first of two checks. After the renderer draws the accepted payload, the page
rasterises the SVG you are looking at and asks an independent decoder to read the pixels. For
Code 39 the comparison is strict: the decoded text must equal the validated payload exactly.
Computed at build time, a decode of PART-4402 matches (true) and a decode of PART-4401 does not (
false); the only equivalences the matcher tolerates are the leading-zero
spellings of UPC-A and EAN-13, which cannot arise here. Two outcomes withhold the preview and
keep the downloads disabled: the decoder cannot read the bars, or it reads a different
payload. The PNG is rendered to its own canvas and decoded again before export; the vector PDF
is translated from the decoded SVG's rectangles and caption rather than embedding a
screenshot.
Where that stops is where every page on this site stops: math, not optics. The decoder reads a clean rasterisation on your screen; no browser check can certify ink, substrate, scaling, contrast, or quiet zones after printing, and a browser cannot stand in for the legacy reader that is usually the reason Code 39 was chosen — the production sample must be scanned by it. How the verifier decodes what you see covers the reasons the decoder can report and the bulk-PDF exception; the methodology page draws the full boundary.
Code 39 or Code 128?
The two symbologies share one validation layer with different tables, and the difference shows
in what it says. Through the Code 128 analyser, PART-4402 is accepted as typed with
the advice “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.” The lowercase form is accepted as typed too — “Contains lowercase letters, which only code set B provides — the encoder will use code set B (switching to C for long digit runs).” — where Code 39 could only uppercase it and say so. Code 128 also carries an internal check
character inside its bars, never typed or displayed; standard Code 39 has none. When you
control both label and reader, choose by the data: lowercase, punctuation beyond the
6 symbols, or anything near the 60-character cap points to
Code 128; a system that expects Code 39 gets Code 39. The guide
Code 128 code sets and the Code 39 charset
walks through the analysis behind both verdicts; all guides lists the series.
Code 39 FAQ
Exactly which characters does Code 39 support?
43: the 26 uppercase letters A–Z, the 10 digits 0–9, space, and the 6 symbols - . $ / + %. That is the whole menu. This tool converts lowercase to uppercase (and tells you it did); any other character is rejected by name rather than silently dropped.
Why is my Code 39 so wide?
Each character spends nine elements, three of them wide — plus a gap — so Code 39 typically needs half again to twice the width of Code 128 for the same text. That inefficiency is structural; the fix, when you control both ends, is switching symbology, not squeezing the print.
What are the asterisks I see in some Code 39 text?
Start and stop delimiters. The symbol begins and ends with a reserved * pattern so scanners know the boundaries and direction. The renderer adds them automatically — never type them into your data.
Does Code 39 have a check digit?
Not by default — most Code 39 deployments rely on the symbology’s self-checking bars alone. An optional mod-43 check digit exists for systems that demand it; this tool does not append one, and says so, because a check digit the receiving system does not expect reads as a stray extra character.
Why use Code 39 at all in 2026?
Compatibility. Decades of industrial, logistics, and government systems (including formats like the DoD’s LOGMARS conventions) were built on Code 39, and labels that feed them must match. For anything new, Code 128 encodes more, in less space, with a mandatory internal check.
Why does this tool stop at 60 characters?
Because every Code 39 character spends nine elements and a gap, long payloads become very wide symbols. The validator enforces the cap as its last rule, after the character check, and its message says why: “Code 39 is a wide symbology; this tool caps input at 60 characters because longer symbols become impractical to print and scan.” It is a limit of this tool, not of the symbology; longer data belongs in Code 128.
Does the independent decode prove the printed label will scan?
No. The decoder reads the pixels of the SVG rendered on this page and withholds the preview when it cannot read them or reads a different value — that catches rendering errors, not printing problems. Contrast, size, substrate, and the quiet zones that survive printing are outside what any browser check can certify, so test the printed label with the scanner that will actually read it.
Verify before production: test the printed label with the scanner that will actually read it — especially with legacy hardware, which is the usual reason Code 39 is chosen. Everything runs locally in your browser; see the methodology page.