A lightweight Elixir library for working with ISBN (International Standard Book Number) identifiers. Supports ISBN-10 and ISBN-13 validation, conversion, and metadata extraction.
- Validation — Check if an ISBN-10 or ISBN-13 is valid
- Type Detection — Identify whether an ISBN is ISBN-10, ISBN-13, or invalid
- Conversion — Convert between ISBN-10 and ISBN-13 formats
- Hyphenation — Format ISBNs with correct hyphens
- Check Digits — Calculate and verify ISBN check digits
- Metadata — Extract publisher zones, country codes, registrant elements, and publication elements
- Normalization — Strip separators and canonicalize ISBN strings
- Metadata — Fetch all ISBN metadata in a single call
- Flexible Input — Accepts ISBNs with or without hyphens
Add exisbn to your dependencies in mix.exs:
def deps do
[
{:exisbn, "~> 2.2"}
]
endThen run mix deps.get to fetch the dependency.
# Validate an ISBN
Exisbn.valid?("978-85-359-0277-8")
# => true
# Detect ISBN type
Exisbn.isbn_type("978-85-359-0277-8")
# => :isbn13
Exisbn.isbn_type("85-359-0277-5")
# => :isbn10
# Convert ISBN-10 to ISBN-13
Exisbn.isbn10_to_13("85-359-0277-5")
# => {:ok, "9788535902778"}
# Hyphenate an ISBN
Exisbn.hyphenate("9788535902778")
# => {:ok, "978-85-359-0277-8"}
# Get the publisher zone
Exisbn.publisher_zone("9788535902778")
# => {:ok, "Brazil"}
# Get the ISO 3166-1 alpha-2 country code
Exisbn.publisher_country_code("9788535902778")
# => {:ok, "BR"}
# Get the GS1 prefix group (978 or 979)
Exisbn.isbn13_prefix_group("9788535902778")
# => {:ok, "978"}
# Fetch all metadata at once
Exisbn.fetch_metadata("9788535902778")
# => {:ok, %{prefix: "978-85", zone: "Brazil", country_code: "BR",
# registrant: "359", publication: "0277", checkdigit: "8"}}Returns :isbn10, :isbn13, or :invalid. Never raises.
Exisbn.isbn_type("978-85-359-0277-8") # => :isbn13
Exisbn.isbn_type("85-359-0277-5") # => :isbn10
Exisbn.isbn_type("invalid") # => :invalidReturns true if the ISBN is valid (correct format, length, and check digit), false otherwise.
# Valid ISBNs
Exisbn.valid?("978-5-12345-678-1") # => true
Exisbn.valid?("85-359-0277-5") # => true
Exisbn.valid?("9788535902778") # => true
# Invalid ISBNs
Exisbn.valid?("978-5-12345-678") # => false (invalid check digit)
Exisbn.valid?("85-359-0277") # => false (incomplete)
Exisbn.valid?("invalid") # => falseReturns true if the ISBN's check digit is correct, false otherwise. Does not validate format or length comprehensively.
Exisbn.checkdigit_correct?("85-359-0277-5") # => true
Exisbn.checkdigit_correct?("978-5-12345-678-1") # => true
Exisbn.checkdigit_correct?("978-5-12345-678") # => falseReturns true if the ISBN is valid and has correct hyphenation, false otherwise.
Exisbn.correct_hyphens?("978-85-359-0277-8") # => true
Exisbn.correct_hyphens?("97-8853590277-8") # => false (incorrect hyphens)
Exisbn.correct_hyphens?("0-306-40615-2") # => true
Exisbn.correct_hyphens?("03-064-06152") # => falseReturns the check digit for an ISBN-10. The check digit may be a digit or X (representing 10).
# Standard form
Exisbn.isbn10_checkdigit("85-359-0277") # => {:ok, "5"}
Exisbn.isbn10_checkdigit("5-02-013850") # => {:ok, "9"}
Exisbn.isbn10_checkdigit("887385107") # => {:ok, "X"}
Exisbn.isbn10_checkdigit("0str") # => {:error, :invalid_isbn}
# Bang form (raises on error)
Exisbn.isbn10_checkdigit!("85-359-0277") # => "5"
Exisbn.isbn10_checkdigit!("invalid") # ** (ArgumentError) Invalid ISBNReturns the check digit for an ISBN-13 (always a digit 0-9).
# Standard form
Exisbn.isbn13_checkdigit("978-5-12345-678") # => {:ok, "1"}
Exisbn.isbn13_checkdigit("978-0-306-40615") # => {:ok, "7"}
Exisbn.isbn13_checkdigit("0str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.isbn13_checkdigit!("978-5-12345-678") # => "1"Converts a valid ISBN-10 to ISBN-13 format by prefixing 978 and recalculating the check digit.
# Standard form
Exisbn.isbn10_to_13("85-359-0277-5") # => {:ok, "9788535902778"}
Exisbn.isbn10_to_13("0306406152") # => {:ok, "9780306406157"}
Exisbn.isbn10_to_13("invalid") # => {:error, :invalid_isbn}
# Bang form
Exisbn.isbn10_to_13!("85-359-0277-5") # => "9788535902778"
Exisbn.isbn10_to_13!("invalid") # ** (ArgumentError) Invalid ISBN
# Verify conversion result
Exisbn.valid?("9788535902778") # => trueConverts a valid ISBN-13 to ISBN-10 format by removing the prefix and recalculating the check digit. Only works for ISBN-13s with 978 prefix.
# Standard form
Exisbn.isbn13_to_10("9788535902778") # => {:ok, "8535902775"}
Exisbn.isbn13_to_10("9780306406157") # => {:ok, "0306406152"}
Exisbn.isbn13_to_10("str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.isbn13_to_10!("9788535902778") # => "8535902775"
Exisbn.isbn13_to_10!("invalid") # ** (ArgumentError) Invalid ISBN
# Verify conversion result
Exisbn.valid?("8535902775") # => trueNote: ISBN-13s starting with 979 cannot be converted to ISBN-10 and will return {:error, :no_isbn10_equivalent}.
The bang form raises ** (ArgumentError) No ISBN-10 equivalent in this case.
Returns the ISBN formatted with correct hyphens according to its publisher zone.
# Standard form
Exisbn.hyphenate("9788535902778") # => {:ok, "978-85-359-0277-8"}
Exisbn.hyphenate("0306406152") # => {:ok, "0-306-40615-2"}
Exisbn.hyphenate("str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.hyphenate!("9788535902778") # => "978-85-359-0277-8"
Exisbn.hyphenate!("0306406152") # => "0-306-40615-2"Returns "978" or "979" for a valid ISBN-13. Returns {:error, :invalid_isbn} for anything
that is not a valid ISBN-13 (including valid ISBN-10s — use isbn10_to_13/1 first if needed).
# Standard form
Exisbn.isbn13_prefix_group("9788535902778") # => {:ok, "978"}
Exisbn.isbn13_prefix_group("9798893031355") # => {:ok, "979"}
Exisbn.isbn13_prefix_group("978-85-359-0277-8")# => {:ok, "978"}
Exisbn.isbn13_prefix_group("85-359-0277-5") # => {:error, :invalid_isbn} # ISBN-10
# Bang form
Exisbn.isbn13_prefix_group!("9788535902778") # => "978"
Exisbn.isbn13_prefix_group!("str") # ** (ArgumentError) Invalid ISBNUseful for quickly checking whether an ISBN-13 can be converted to ISBN-10 (only "978" prefix):
case Exisbn.isbn13_prefix_group(isbn) do
{:ok, "978"} -> Exisbn.isbn13_to_10(isbn)
{:ok, "979"} -> {:error, :no_isbn10_equivalent}
error -> error
endReturns the ISBN prefix including group identifier (e.g., 978-85 for Brazil).
# Standard form
Exisbn.fetch_prefix("9788535902778") # => {:ok, "978-85"}
Exisbn.fetch_prefix("2-1234-5680-2") # => {:ok, "978-2"}
Exisbn.fetch_prefix("str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.fetch_prefix!("9788535902778") # => "978-85"
Exisbn.fetch_prefix!("str") # ** (ArgumentError) Invalid ISBN
Exisbn.fetch_prefix!("9799012345674") # ** (ArgumentError) Unknown registration groupReturns the geographic zone or language group associated with the ISBN prefix.
# Standard form
Exisbn.publisher_zone("9788535902778") # => {:ok, "Brazil"}
Exisbn.publisher_zone("2-1234-5680-2") # => {:ok, "French language"}
Exisbn.publisher_zone("str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.publisher_zone!("9788535902778") # => "Brazil"
Exisbn.publisher_zone!("2-1234-5680-2") # => "French language"Returns the two-letter ISO 3166-1 alpha-2 country code for the ISBN's registration group.
Returns {:ok, nil} for groups that cover multiple countries or language areas
(e.g. 978-0/978-1 — English language, 978-2 — French language, 978-3 — German language,
978-5 — former U.S.S.R., 978-92 — International NGO Publishers, 978-976 — Caribbean Community).
# Standard form
Exisbn.publisher_country_code("9788535902778") # => {:ok, "BR"}
Exisbn.publisher_country_code("9784065393987") # => {:ok, "JP"}
Exisbn.publisher_country_code("9780306406157") # => {:ok, nil} # English language group
Exisbn.publisher_country_code("str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.publisher_country_code!("9788535902778") # => "BR"
Exisbn.publisher_country_code!("9784065393987") # => "JP"
Exisbn.publisher_country_code!("9780306406157") # => nilReturns the check digit character from the ISBN (as a string). For ISBN-10, this may be X.
# Standard form
Exisbn.fetch_checkdigit("9788535902778") # => {:ok, "8"}
Exisbn.fetch_checkdigit("2-1234-5680-2") # => {:ok, "2"}
Exisbn.fetch_checkdigit("887385107X") # => {:ok, "X"}
Exisbn.fetch_checkdigit("str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.fetch_checkdigit!("9788535902778") # => "8"
Exisbn.fetch_checkdigit!("887385107X") # => "X"Returns the registrant element (publisher identifier) of the ISBN.
# Standard form
Exisbn.fetch_registrant_element("9788535902778") # => {:ok, "359"}
Exisbn.fetch_registrant_element("978-1-86197-876-9") # => {:ok, "86197"}
Exisbn.fetch_registrant_element("9789529351787") # => {:ok, "93"}
Exisbn.fetch_registrant_element("str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.fetch_registrant_element!("9788535902778") # => "359"
Exisbn.fetch_registrant_element!("978-1-86197-876-9") # => "86197"
Exisbn.fetch_registrant_element!("9799012345674") # ** (ArgumentError) Unknown registration group
Exisbn.fetch_registrant_element!("9786110000000") # ** (ArgumentError) Unknown publisherReturns a map with all ISBN metadata in a single call. More efficient than calling individual metadata functions separately, since the prefix and registrant lookups are performed only once.
The returned map contains:
prefix— registration group prefix (e.g.,"978-85")zone— publisher zone or language group namecountry_code— ISO 3166-1 alpha-2 code, ornilfor multi-country groupsregistrant— registrant (publisher) identifierpublication— publication (title) identifiercheckdigit— the check digit character
# Standard form
Exisbn.fetch_metadata("9788535902778")
# => {:ok, %{prefix: "978-85", zone: "Brazil", country_code: "BR",
# registrant: "359", publication: "0277", checkdigit: "8"}}
Exisbn.fetch_metadata("9780306406157")
# => {:ok, %{prefix: "978-0", zone: "English language", country_code: nil,
# registrant: "306", publication: "40615", checkdigit: "7"}}
Exisbn.fetch_metadata("str")
# => {:error, :invalid_isbn}
# Bang form
Exisbn.fetch_metadata!("9788535902778")
# => %{prefix: "978-85", zone: "Brazil", country_code: "BR",
# registrant: "359", publication: "0277", checkdigit: "8"}
Exisbn.fetch_metadata!("str")
# ** (ArgumentError) Invalid ISBNWorks with ISBN-10 input as well — metadata is derived from the equivalent ISBN-13, while the check digit reflects the original ISBN-10:
Exisbn.fetch_metadata("85-359-0277-5")
# => {:ok, %{prefix: "978-85", zone: "Brazil", country_code: "BR",
# registrant: "359", publication: "0277", checkdigit: "5"}}fetch_publication_element(isbn) / fetch_publication_element!(isbn) — Get publication/title identifier
Returns the publication element (title/publication identifier) of the ISBN.
# Standard form
Exisbn.fetch_publication_element("978-1-86197-876-9") # => {:ok, "876"}
Exisbn.fetch_publication_element("9789529351787") # => {:ok, "5178"}
Exisbn.fetch_publication_element("str") # => {:error, :invalid_isbn}
# Bang form
Exisbn.fetch_publication_element!("978-1-86197-876-9") # => "876"
Exisbn.fetch_publication_element!("9789529351787") # => "5178"Returns a bare string of digits (plus X for ISBN-10 check digit). Removes
hyphens, spaces, and any non-digit characters, and upcases the result.
This function does not validate the ISBN. Use valid?/1 for validation.
Exisbn.normalize("978-85-359-0277-8") # => "9788535902778"
Exisbn.normalize("85-359-0277-5") # => "8535902775"
Exisbn.normalize("978 85 359 0277 8") # => "9788535902778"
Exisbn.normalize("887385107x") # => "887385107X"
Exisbn.normalize("9788535902778") # => "9788535902778"Useful for normalizing ISBNs before storing in a database or comparing values.
The library is flexible with input formatting. Validation operates on the normalized form
(digits + optional trailing X), so any separator character is accepted as long as the
extracted digits form a valid ISBN.
# All these are equivalent:
Exisbn.valid?("9788535902778") # => true
Exisbn.valid?("978-85-359-0277-8") # => true
Exisbn.valid?("978 85 359 0277 8") # => true
Exisbn.valid?("978.85.359.0277.8") # => true (dots work too)
Exisbn.valid?("978.853590277.8") # => true (any grouping)
# ISBN-10 with check digit X
Exisbn.valid?("887385107X") # => true (uppercase X)
Exisbn.valid?("887385107x") # => true (lowercase x is normalized to X)Normalization strips everything except digits and X, then upcases the result.
normalize/1 can be used explicitly before storing or comparing ISBNs.
- 10 characters total
- Last character may be a digit (0-9) or
X(representing 10) - Uses modulo 11 checksum algorithm
- Convertible to ISBN-13 by prefixing
978
- 13 digits total
- Common prefixes:
978or979 - Uses modulo 10 checksum algorithm
- ISBN-13 with
978prefix can be converted back to ISBN-10 - ISBN-13 with
979prefix cannot be converted to ISBN-10
Standard functions return tagged error tuples:
| Error atom | Meaning | Functions that can return it |
|---|---|---|
:invalid_isbn |
ISBN is structurally invalid (wrong length, bad check digit, non-digit chars) | all |
:unknown_group |
ISBN is valid but its registration group is not in the dataset | fetch_prefix, publisher_zone, publisher_country_code, fetch_registrant_element, fetch_publication_element, fetch_metadata, hyphenate |
:unknown_publisher |
Registration group is known but has no publisher ranges defined | fetch_registrant_element, fetch_publication_element, fetch_metadata, hyphenate |
:no_isbn10_equivalent |
ISBN-13 with 979 prefix has no ISBN-10 equivalent |
isbn13_to_10 |
case Exisbn.publisher_zone("9799012345674") do
{:ok, zone} -> IO.puts("Zone: #{zone}")
{:error, :invalid_isbn} -> IO.puts("Invalid ISBN")
{:error, :unknown_group} -> IO.puts("Registration group not in dataset")
endBang functions raise ArgumentError with a descriptive message:
| Error atom | ArgumentError message |
|---|---|
:invalid_isbn |
"Invalid ISBN" |
:unknown_group |
"Unknown registration group" |
:unknown_publisher |
"Unknown publisher" |
:no_isbn10_equivalent |
"No ISBN-10 equivalent" |
try do
Exisbn.isbn13_to_10!("9798893031355")
rescue
e in ArgumentError -> IO.puts(e.message) # "No ISBN-10 equivalent"
endisbn = "978-85-359-0277-8"
if Exisbn.valid?(isbn) do
{:ok, formatted} = Exisbn.hyphenate(isbn)
IO.puts("Valid ISBN: #{formatted}")
else
IO.puts("Invalid ISBN")
endisbn10 = "85-359-0277-5"
with {:ok, isbn13} <- Exisbn.isbn10_to_13(isbn10),
{:ok, zone} <- Exisbn.publisher_zone(isbn13),
{:ok, country_code} <- Exisbn.publisher_country_code(isbn13),
{:ok, prefix} <- Exisbn.fetch_prefix(isbn13) do
IO.puts("ISBN-10: #{isbn10}")
IO.puts("ISBN-13: #{isbn13}")
IO.puts("Publisher Zone: #{zone}")
IO.puts("Country Code: #{country_code}")
IO.puts("Prefix: #{prefix}")
else
{:error, :invalid_isbn} -> IO.puts("Invalid ISBN")
endisbn = "978-1-86197-876-9"
with true <- Exisbn.valid?(isbn),
{:ok, zone} <- Exisbn.publisher_zone(isbn),
{:ok, registrant} <- Exisbn.fetch_registrant_element(isbn),
{:ok, publication} <- Exisbn.fetch_publication_element(isbn) do
IO.puts("Zone: #{zone}")
IO.puts("Registrant: #{registrant}")
IO.puts("Publication: #{publication}")
else
_ -> IO.puts("Could not extract ISBN details")
endThe bundled registration-group dataset is generated from the official ISBN International Range Message and covers all groups published at the time of the last release.
As of the current release, the ISBN standard defines 5 registration groups under the 979 prefix:
| Prefix | Zone |
|---|---|
979-8 |
United States |
979-10 |
France |
979-11 |
Korea, Republic |
979-12 |
Italy |
979-13 |
Spain |
ISBNs from any other 979-* group will return {:error, :unknown_group} from metadata
and hyphenation functions — the ISBN is still structurally valid (passes valid?/1),
but its registration group is not yet assigned in the standard.
Full API documentation with more examples is available at HexDocs.
This project is licensed under the MIT License. See the LICENSE file for details.
Contributions are welcome! Please feel free to submit issues or pull requests on GitHub.