CPF/CNPJ

CPF/CNPJ is a Crystal shard for handling Brazilian CPF and CNPJ identifiers.

API Reference

What is CPF and CNPJ?

CPF (Cadastro de Pessoa Física) is Brazil’s individual taxpayer registry. It is an 11-digit identifier in the format 000.000.000-00, where the last two digits are check digits calculated from the first nine digits.

CNPJ (Cadastro Nacional de Pessoa Jurídica) is Brazil’s national registry of legal entities. It is a 14-digit identifier in the format XX.XXX.XXX/XXXX-00, where the last two digits are check digits calculated from the first twelve digits.

Installation

  1. Add the dependency to your shard.yml:

    dependencies:
      cpf_cnpj:
        codeberg: gunbolt/cpf_cnpj
  2. Run shards install

Usage

Require cpf_cnpj

require "cpf_cnpj"

CPF

You can instantiate CPF passing formatted or unformatted identifiers. Both #value and #to_s methods returns the identifier in the same form it was provided:

cpf = CPF.new("640.061.830-97")
cpf.value # => "640.061.830-97"
cpf.to_s  # => "640.061.830-97"

cpf = CPF.new("64006183097")
cpf.value # => "64006183097"
cpf.to_s  # => "64006183097"

A CPF object always represents a valid CPF identifier. This ensures you never deal with invalid, empty, or nil values. If you attempt to initialize a CPF with an invalid identifier, a CPF::InvalidValueError will be raised:

CPF.new("11111111111")    # => raises `CPF::InvalidValueError`
CPF.new("111.111.111-11") # => raises `CPF::InvalidValueError`

To create a CPF without raising an exception, use the .parse? method. It returns a CPF? (a CPF struct or nil) depending on whether the value is valid:

# With invalid value
CPF.parse?("11111111111") # => nil

# With valid value
CPF.parse?("640.061.830-97") # => #<CPF:0x104fe0ae0 @value="640.061.830-97">

To check whether an identifier is valid, use the CPF::Validator module:

CPF::Validator.valid?("11111111111")    # => false
CPF::Validator.valid?("640.061.830-97") # => true

Use the #formatted and #unformatted methods to get the identifier with or without punctuation:

cpf = CPF.new("64006183097")
cpf.formatted   # => "640.061.830-97"
cpf.unformatted # => "64006183097"

CNPJ

CNPJ behaves the same way as CPF. It also supports alphanumeric identifiers scheduled to take effect in 2026:

# With valid values
cnpj = CNPJ.new("24.485.147/0001-87")
cnpj.value # => "24.485.147/0001-87"
cnpj.to_s  # => "24.485.147/0001-87"

cnpj = CNPJ.new("VCZ83T1R000106")
cnpj.value # => "VCZ83T1R000106"
cnpj.to_s  # => "VCZ83T1R000106"

# With invalid values
CNPJ.new("11111111111111")     # => raises `CNPJ::InvalidValueError`
CNPJ.new("11.111.111/1111-11") # => raises `CNPJ::InvalidValueError`

# Safely instatiating a CNPJ
CNPJ.parse?("11111111111111")     # => nil
CNPJ.parse?("24.485.147/0001-87") # => #<CNPJ:0x104fe0ae0 @value="24.485.147/0001-87">

# Validating an identifier
CNPJ::Validator.valid?("11111111111111")     # => false
CNPJ::Validator.valid?("24.485.147/0001-87") # => true

# Formatting & unformatting values
cnpj = CNPJ.new("VCZ83T1R000106")
cnpj.value       # => "VCZ83T1R000106"
cnpj.formatted   # => "VC.Z83.T1R/0001-06"
cnpj.unformatted # => "VCZ83T1R000106"

The only additional feature in CNPJ is the #root method ("raiz" in Portuguese), which returns the first eight characters of the identifier. This is useful for verifying parent and subsidiary companies:

cnpj = CNPJ.new("VCZ83T1R000106")
cnpj.root # => "VCZ83T1R"

other_cnpj = CNPJ.new("VCZ83T1R000289")
other_cnpj.root # => "VCZ83T1R"

CadastroID

CadastroID is an abstract struct, so you cannot instantiate it directly. It is useful when a variable may hold a CPF or a CNPJ value. It provides the .new and .parse? methods, just like CPF and CNPJ:

id_a = CadastroID.new("640.061.830-97")
id_a.value    # => "640.061.830-97"
id_a.class    # => CPF
typeof(id_a)  # => CadastroID

id_b = CadastroID.new("VCZ83T1R000106")
id_b.value    # => "VCZ83T1R000106"
id_b.class    # => CNPJ
typeof(id_b)  # => CadastroID

# With invalid value
CadastroID.new("1234") # => raises `CadastroID::InvalidValueError`

# Safely instantiating a CPF or CNPJ
CadastroID.parse?("1234") # => nil
CadastroID.parse?("24.485.147/0001-87") # => #<CNPJ:0x104fe0ae0 @value="24.485.147/0001-87">

Database

CPF/CNPJ integrates with crystal-db shard. Simply require cpf_cnpj/db and use the @[DB::Field] annotation to set up the converter.

require "cpf_cnpj/db"

class Client
  include DB::Serializable

  @[DB::Field(converter: CadastroID)]
  property identifier : CadastroID
end

class Company
  include DB::Serializable

  @[DB::Field(converter: CNPJ)]
  property cnpj : CNPJ
end

class User
  include DB::Serializable

  @[DB::Field(converter: CPF)]
  property cpf : CPF
end

# CPF
users = User.from_rs(database.query("SELECT cpf FROM users"))
users[0].cpf.value # => "640.061.830-97"

# CNPJ
companies = Company.from_rs(database.query("SELECT cnpj FROM companies"))
companies[0].cnpj.value # => "UP.FVU.R5W/0001-07"

# CadastroID
clients = Client.from_rs(database.query("SELECT identifier FROM clients"))
clients[0].identifier.value # => "UP.FVU.R5W/0001-07"
clients[1].identifier.value # => "640.061.830-97"

Database values should be stored as text/string types and must be valid identifiers (formatted or unformatted). If a table column can contain NULL, empty, invalid values, the corresponding property should be declared as nilabe. For example:

class User
  include DB::Serializable

  @[DB::Field(converter: CPF)]
  property cpf : CPF?
end

# returns invalid values from database
users = User.from_rs(database.query("SELECT cpf FROM users"))
users[0].cpf # => nil

JSON

CPF/CNPJ also integrates with JSON::Serializable. You need to require "cpf_cnpj/json" to enable JSON serialization and deserialization:

require "cpf_cnpj/json"

class Client
  include JSON::Serializable

  property identifier : CadastroID
end

class Company
  include JSON::Serializable

  property cnpj : CNPJ
end

class User
  include JSON::Serializable

  property cpf : CPF
end

client = Client.from_json(%({"identifier": "7B.N1F.Y9N/0001-98"}))
client.identifier.value # => "7B.N1F.Y9N/0001-98"
client.identifier.to_json # => "\"7B.N1F.Y9N/0001-98\""

company = Company.from_json(%({"cnpj": "7B.N1F.Y9N/0001-98"}))
company.cnpj.to_s # => "7B.N1F.Y9N/0001-98"
company.cnpj.to_json # => "\"7B.N1F.Y9N/0001-98\""

user = User.from_json(%({"cpf": "64006183097"}))
user.cpf.to_s # => "64006183097"
user.cpf.to_json # => "\"64006183097\""

YAML

CPF/CNPJ can be serialized and deserialized with YAML::Serializable. To use this feature, you need to require "cpf_cnpj/json".

require "cpf_cnpj/yaml"

class Client
  include YAML::Serializable

  property identifier : CadastroID
end

class Company
  include YAML::Serializable

  property cnpj : CNPJ
end

class User
  include YAML::Serializable

  property cpf : CPF
end

client = Client.from_yaml("identifier: 7B.N1F.Y9N/0001-98")
client.identifier.value # => "7B.N1F.Y9N/0001-98"
client.identifier.to_yaml # => "--- 7B.N1F.Y9N/0001-98\n"

company = Company.from_yaml("cnpj: 7B.N1F.Y9N/0001-98")
company.cnpj.to_s # => "7B.N1F.Y9N/0001-98"
company.cnpj.to_yaml # => "--- 7B.N1F.Y9N/0001-98\n"

user = User.from_yaml("cpf: 64006183097")
user.cpf.to_s # => "64006183097"
user.cpf.to_yaml # => "--- 64006183097\n"

Development

  1. shards install to install dependencies
  2. crystal spec to run tests
  3. crystal tool format && bin/ameba to format/lint code

Contributing

  1. Fork it (https://codeberg.org/gunbolt/cpf_cnpj/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors