logistics.sscc
Build an 18-digit SSCC pallet label number from extension digit, GS1 company prefix and serial, with its check digit.
1.1.0 · published 2026-10-03 by charlie · Anterra
Pinned by 16 tests, run in TypeScript, Python and Rust.
What it does
Builds a Serial Shipping Container Code, the 18-digit number on a GS1 logistics label that identifies one pallet, cage or parcel through the supply chain. It is:
| digits | part | |---|---| | 1 | extension digit, 0-9, the company's own choice | | 4-12 | GS1 company prefix, as allocated by GS1 | | the rest, to make 17 | serial reference, zero-padded | | 1 | GS1 mod-10 check digit |
For example
build_sscc(1, 0614141, 123,456,789)→ 106141411234567897 GS1's own example: (00) 1 0614141 123456789 7build_sscc(0, 5012345, 0)→ 050123450000000008 serial zero is padded to fill the nine digits a 7-digit prefix leavesbuild_sscc(3, 5012345678, 42)→ 350123456780000420 a 10-digit prefix leaves six serial digits; check digit 0
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
def build_sscc(extension_digit: int, company_prefix: str, serial_reference: int) -> str
| extension_digit | int | 0 to 9, chosen by the company to widen its number range |
| company_prefix | string | the GS1 company prefix GS1 allocated, 4 to 12 digits, leading zeros kept |
| serial_reference | int | 0 or more; left-padded with zeros to fill the 16 digits after the extension digit |
| returns | string | 18 digits, check digit last, no "(00)" application identifier |
Your code names it in one line, in the file that uses it
from fune.logistics.sscc import build_sscc # logistics.sscc@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import re
from .validation_gs1_check_digit import gs1_check_digit ← from validation.gs1-check-digit ^1.0.0 · built alongside by fune
_PREFIX = re.compile(r"^[0-9]{4,12}$")
def _whole(value: object) -> bool:
return isinstance(value, int) and not isinstance(value, bool)
def build_sscc(extension_digit: int, company_prefix: str, serial_reference: int) -> str:
"""Build an 18-digit SSCC: extension digit, GS1 company prefix, zero-padded
serial reference and GS1 mod-10 check digit."""
if not _whole(extension_digit) or extension_digit < 0 or extension_digit > 9:
raise ValueError("extensionDigit must be 0 to 9, received %r" % (extension_digit,))
# fullmatch, not match: "$" in re also matches before a trailing newline.
if not isinstance(company_prefix, str) or not _PREFIX.fullmatch(company_prefix):
raise ValueError('companyPrefix must be 4 to 12 digits, received "%s"' % (company_prefix,))
if not _whole(serial_reference):
raise TypeError("serialReference must be an integer, received %r" % (serial_reference,))
if serial_reference < 0:
raise ValueError("serialReference must not be negative, received %d" % serial_reference)
# Prefix and serial share 16 digits, so a longer prefix leaves less room.
# Too large is an error: truncating would reuse another unit's number.
room = 16 - len(company_prefix)
serial = str(serial_reference)
if len(serial) > room:
raise ValueError(
"serialReference must fit in %d digits after a %d-digit company prefix, received %d"
% (room, len(company_prefix), serial_reference)
)
data = str(extension_digit) + company_prefix + serial.rjust(room, "0")
return data + str(gs1_check_digit(data))Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add logistics.sscc
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./logistics.sscc-1.1.0-python.fune, or fetch it from a terminal with fune pull logistics.sscc@1.1.0:python.
The whole function, every language, is one file too: logistics.sscc-1.1.0.fune, 10,910 bytes, sha256 aea2a0319e89e237e8778fea97ac8cd01600de11bd63409a4546fd35791a5e55. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
# fune: before logistics.sscc
after — your function gets the result and the arguments, and returns the final result.
# fune: after logistics.sscc
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
# fune: replace validation.gs1-check-digit in logistics.sscc
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show logistics.sscc --steps.
# fune: step logistics.sscc after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| GS1's own example: (00) 1 0614141 123456789 7 | 1, 0614141, 123,456,789 | → | 106141411234567897 |
| serial zero is padded to fill the nine digits a 7-digit prefix leaves | 0, 5012345, 0 | → | 050123450000000008 |
| a 10-digit prefix leaves six serial digits; check digit 0 | 3, 5012345678, 42 | → | 350123456780000420 |
| leading zeros in the prefix are kept | 9, 0000123, 5 | → | 900001230000000058 |
| largest serial a 12-digit prefix allows | 0, 123456789012, 9,999 | → | 012345678901299996 |
| a 4-digit prefix leaves twelve serial digits | 1, 1234, 999,999,999,999 | → | 112349999999999999 |
| serial one too large for a 12-digit prefix | 0, 123456789012, 10,000 | → | error: serialReference must fit in 4 digits |
| serial too large for a 7-digit prefix | 1, 0614141, 1,000,000,000 | → | error: serialReference must fit in 9 digits |
| negative serial | 1, 0614141, -1 | → | error: serialReference must not be negative |
| extension digit above 9 | 10, 0614141, 1 | → | error: extensionDigit must be 0 to 9 |
Show the other 6 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| negative extension digit | -1, 0614141, 1 | → | error: extensionDigit must be 0 to 9 |
| prefix too short | 1, 123, 1 | → | error: companyPrefix must be 4 to 12 digits |
| prefix too long | 1, 1234567890123, 1 | → | error: companyPrefix must be 4 to 12 digits |
| prefix with a letter | 1, 06141A1, 1 | → | error: companyPrefix must be 4 to 12 digits |
| prefix with a space is not tidied | 1, 0614 141, 1 | → | error: companyPrefix must be 4 to 12 digits |
| GS1's check digit worked example: SSCC 37610425002123456 takes 9 | 3, 7610425, 2,123,456 | → | 376104250021234569 |
More from the author
So a 7-digit prefix leaves 9 digits of serial reference and a 10-digit prefix leaves 6. A serial reference too large for the room left is an error rather than being truncated, because a truncated serial silently re-uses another pallet's number.
## Check digit
The standard GS1 mod-10 over the 17 data digits, from `validation.gs1-check-digit` since 1.1.0 (1.0.0 repeated the arithmetic here, because `validation.gtin` only accepted GTIN lengths). The answers are the same. GS1's own example, `(00) 1 0614141 123456789 7`, is a vector, and so is the SSCC from GS1's check digit worked example, `376104250021234569`.
## What it does not do
- It returns the bare 18 digits. The label's human-readable line shows them after the application identifier as `(00) 106141411234567897`; the barcode (GS1-128) encodes `00` followed by the 18 digits. - It does not allocate serial references. GS1 asks that an SSCC is not re-used for at least a year after the unit was shipped; keeping the counter is the caller's job. - It cannot tell whether a prefix was really allocated to you.
## Source
GS1 General Specifications, the SSCC (logistic units) and check digit calculation sections, https://www.gs1.org/standards/barcodes-epcrfid-id-keys/gs1-general-specifications ; GS1 UK, "How do I create SSCCs and logistics labels?", https://www.gs1uk.org/knowledge-hub/barcodes/how-do-i-create-ssccs-and-logistics-labels
Files
| Path | Bytes |
|---|---|
| README.md | 1,817 |
| impl/python.py | 1,694 |
| impl/rust.rs | 1,651 |
| impl/typescript.ts | 1,501 |
| vectors.json | 2,065 |