Modules and CPAN¶
Code Organization and the Perl Ecosystem¶
Version: 1.1 Year: 2026
Copyright Notice¶
Copyright (c) 2025-2026 Ryan Thomas Robson / Robworks Software LLC. Licensed under CC BY-NC-ND 4.0. You may share this material for non-commercial purposes with attribution, but you may not distribute modified versions.
Modules are Perl's unit of code organization - a file that declares a namespace, exports functions, and can be loaded by any script or other module. The Comprehensive Perl Archive Network (CPAN) hosts over 200,000 modules covering everything from date parsing to web frameworks. Knowing how to write modules, find the right CPAN library, and manage dependencies is what separates scripts from maintainable software.
use vs. require¶
Perl provides two mechanisms for loading external code: use and require. They look similar but behave differently.
use¶
use loads a module at compile time - before your program's runtime code executes. Under the hood, use Module is equivalent to BEGIN { require Module; Module->import(); }:
use File::Basename; # loads and imports at compile time
use Carp qw(croak confess); # imports only croak and confess
The BEGIN block forces execution at compile time, and import() brings the module's exported symbols into your namespace.
require¶
require loads a module at runtime without calling import(). You must use fully-qualified names to access its functions:
Use require for conditional or optional loading:
if ($needs_xml) {
require XML::LibXML;
my $parser = XML::LibXML->new();
}
# Optional dependency with fallback
my $has_json_xs = eval { require JSON::XS; 1 };
my $json = $has_json_xs ? JSON::XS->new() : do { require JSON::PP; JSON::PP->new() };
@INC and Module Search Path¶
When you write use Some::Module, Perl needs to find the file Some/Module.pm on disk. It searches through the directories listed in the special array @INC.
Default @INC¶
Perl populates @INC from these sources (searched in order): -I command-line flags, the PERL5LIB environment variable, site-specific directories (where cpanm installs), vendor directories, and core Perl library directories.
Modifying @INC¶
The use lib pragma is the standard way to add paths inside scripts. It prepends paths to @INC at compile time:
Other approaches:
perl -I/home/user/lib script.pl # command-line flag
export PERL5LIB=/home/user/lib # environment variable
Current directory removed from @INC
Since Perl 5.26, . is no longer in @INC by default. If your script loads modules from the current directory, you must add use lib '.' explicitly. This was a security fix to prevent malicious .pm files in the working directory from hijacking module loading.
Perl converts :: separators to directory separators and appends .pm - so File::Basename becomes File/Basename.pm.
The Module Loading Process¶
flowchart TD
A["use Some::Module"] --> B["Convert :: to /\nSome::Module becomes Some/Module.pm"]
B --> C["Check %INC\nAlready loaded?"]
C -->|Yes| D["Skip loading\nReturn cached result"]
C -->|No| E["Search @INC directories\nin order"]
E --> F{Found?}
F -->|No| G["die: Can't locate\nSome/Module.pm in @INC"]
F -->|Yes| H["Compile and execute\nthe .pm file"]
H --> I["Record in %INC:\n$INC{'Some/Module.pm'} = '/path/to/Some/Module.pm'"]
I --> J["Call Some::Module->import()\n(for use only, not require)"]
Perl checks %INC before searching @INC, so each module is loaded only once per interpreter.
Writing Modules¶
A Perl module is a .pm file that declares a package (namespace) and returns a true value when loaded.
Minimal Module¶
# File: lib/Greeting.pm
package Greeting;
use strict;
use warnings;
sub hello {
my $name = shift // 'World';
return "Hello, $name!";
}
1;
Three essential elements: package Greeting declares the namespace (all subs after this line belong to Greeting); 1; at the end returns a true value (without it, loading fails); and use strict; use warnings for safety.
Using Your Module¶
# File: script.pl
use lib './lib';
use Greeting;
print Greeting::hello("Alice"), "\n"; # Hello, Alice!
Without Exporter (covered below), you must use the fully-qualified name Greeting::hello().
Packages and Namespaces¶
A package creates a separate namespace. Identically-named items in different packages do not collide:
package Database;
sub connect { ... } # Database::connect
package WebServer;
sub connect { ... } # WebServer::connect - no conflict
A single file can contain multiple package declarations, but the convention is one package per file, with the file path matching the package name.
Nested Namespaces¶
Use :: to create hierarchy. The file path must match:
| Package name | File path |
|---|---|
My::App::Config |
lib/My/App/Config.pm |
Database::Pool |
lib/Database/Pool.pm |
The 1; Return Value
The 1; at the end of a module is required because Perl evaluates the file and checks whether it returned a true value. If you forget it, you get the error "Module.pm did not return a true value." Some developers use __END__ after the code and put documentation there, but 1; must come before __END__.
Exporter¶
When you write use File::Basename, the basename and dirname functions become available in your script without a package prefix. This happens through the Exporter module, which provides the mechanism for injecting symbols into the caller's namespace.
@EXPORT vs. @EXPORT_OK¶
Exporter uses two arrays to control what gets exported:
| Array | Behavior | Best practice |
|---|---|---|
@EXPORT |
Symbols exported automatically with use Module |
Avoid - pollutes the caller's namespace without consent |
@EXPORT_OK |
Symbols exported only when explicitly requested | Preferred - caller chooses what to import |
package MathUtils;
use strict;
use warnings;
use Exporter 'import';
our @EXPORT = qw(add); # auto-exported (avoid this)
our @EXPORT_OK = qw(subtract multiply divide); # exported on request
sub add { $_[0] + $_[1] }
sub subtract { $_[0] - $_[1] }
sub multiply { $_[0] * $_[1] }
sub divide { $_[1] != 0 ? $_[0] / $_[1] : undef }
1;
use MathUtils; # imports add() via @EXPORT
use MathUtils qw(subtract multiply); # imports only subtract() and multiply()
Export Tags¶
Group related exports with %EXPORT_TAGS. Callers import tags with a colon prefix:
our @EXPORT_OK = qw(read_file write_file slurp_dir list_files);
our %EXPORT_TAGS = (
io => [qw(read_file write_file)],
dir => [qw(slurp_dir list_files)],
all => \@EXPORT_OK,
);
# Caller:
use FileUtils qw(:io); # imports read_file, write_file
use FileUtils qw(:all); # imports everything in @EXPORT_OK
Avoid @EXPORT for new modules
Putting symbols in @EXPORT means every user of your module gets those names injected into their namespace whether they want them or not. This can cause name collisions and makes it hard to trace where a function came from. Use @EXPORT_OK and let callers explicitly request what they need.
cpanm: Installing CPAN Modules¶
cpanm (also called cpanminus) is the preferred tool for installing modules from CPAN. It is simpler and faster than the older cpan shell.
Installing and Using cpanm¶
# Install cpanm itself
curl -L https://cpanmin.us | perl - App::cpanminus
# Install a module
cpanm JSON::XS
# Install a specific version
cpanm JSON::XS@4.03
# Install without running tests (faster)
cpanm --notest DBI
# Install from a cpanfile (dependency file)
cpanm --installdeps .
# Install to a local directory (no root required)
cpanm -l ~/perl5 DateTime
Finding Modules on MetaCPAN¶
MetaCPAN is the primary search interface for CPAN. It provides source browsing, documentation rendering, dependency graphs, and quality metrics.
Evaluating Module Quality¶
Not all CPAN modules are equal. Search by task ("parse CSV", "send email"), check the Task::Kensho curated collection, and look at reverse dependencies to gauge popularity. Before committing to a dependency, check these indicators:
| Indicator | Where to find it | What it tells you |
|---|---|---|
| CPAN Testers | MetaCPAN sidebar | Pass/fail across platforms and Perl versions |
| GitHub/GitLab activity | Repository link on MetaCPAN | Recent commits, open issues, maintainer responsiveness |
| Reverse dependencies | MetaCPAN "Reverse Dependencies" tab | How many other modules rely on this one |
| Last release date | MetaCPAN module page | Whether the module is actively maintained |
| Documentation quality | MetaCPAN POD rendering | Clear SYNOPSIS, complete API docs, examples |
| License | META file or POD | Whether it is compatible with your project |
The CPAN Testers Matrix
CPAN Testers runs automated tests of every CPAN upload across hundreds of platform/Perl-version combinations. A module with all green results is reliable. Sporadic failures on obscure platforms are normal. Widespread failures are a red flag.
cpanfile for Dependency Management¶
A cpanfile declares your project's module dependencies in a single file. It serves the same purpose as requirements.txt in Python or package.json in Node.js.
Basic cpanfile¶
# cpanfile
requires 'perl', '5.020';
requires 'Mojolicious', '>= 9.0';
requires 'DBI';
requires 'JSON::XS';
requires 'Try::Tiny';
on 'test' => sub {
requires 'Test2::Suite';
};
on 'develop' => sub {
requires 'Perl::Tidy';
requires 'Perl::Critic';
};
Install with cpanm --installdeps . for runtime deps, add --with-test for test deps, or --with-develop for development tools.
local::lib for User-Space Installs¶
When you do not have root access or want to keep module installations isolated from the system Perl, local::lib sets up a personal module directory.
Setup¶
# Install local::lib
cpanm local::lib
# Activate it (add to ~/.bashrc or ~/.zshrc)
eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)"
Once activated, cpanm installs modules into ~/perl5/, Perl adds ~/perl5/lib/perl5 to @INC automatically, and no sudo is required.
You can also create project-specific libraries:
# Install modules into ./local/ instead of ~/perl5
eval "$(perl -Mlocal::lib=./local)"
cpanm --installdeps .
Combine with cpanfile
The workflow of cpanfile + local::lib + cpanm --installdeps . gives you reproducible, isolated dependency management. New developers clone the repository, run one command, and have everything they need.
CPAN Ecosystem Relationships¶
The CPAN toolchain is a set of interconnected components that work together to author, distribute, install, and test Perl modules:
flowchart TD
subgraph "Author Side"
A["Module Author"] --> B["Distribution Tools\nDist::Zilla / ExtUtils::MakeMaker"]
B --> C["Upload to PAUSE"]
end
subgraph "Infrastructure"
C --> D["CPAN\n(master archive)"]
D --> E["CPAN Mirrors\n(worldwide)"]
D --> F["MetaCPAN\n(search and docs)"]
D --> G["CPAN Testers\n(automated testing)"]
end
subgraph "User Side"
E --> H["cpanm / cpan\n(install tools)"]
H --> I["local::lib / system perl\n(install targets)"]
F --> J["Developer\n(searches for modules)"]
J --> H
end
Authors upload to PAUSE, which distributes to CPAN mirrors worldwide. MetaCPAN provides search and documentation. CPAN Testers runs automated cross-platform tests. Users install via cpanm.
Dist::Zilla Overview¶
Dist::Zilla (commonly called dzil) is a distribution authoring tool. Packaging a module as a proper CPAN distribution requires Makefile.PL, META.json, MANIFEST, a LICENSE file, and consistent version numbers. Dist::Zilla generates all of this from a dist.ini configuration file.
Minimal dist.ini¶
Common Workflow¶
dzil new My::Utils # create a new distribution
dzil build # generate the tarball
dzil test # run tests
dzil release # upload to CPAN
The standard project layout is lib/ for module code and t/ for tests. Dist::Zilla manages everything else.
Dist::Zilla vs. Minilla
Dist::Zilla is powerful but has a steep learning curve. Minilla is a lighter alternative that follows conventions over configuration. For a first CPAN upload, Minilla is often the faster path.
Exercises¶
cpanm Command Builder¶
Quick Reference¶
| Concept | Syntax |
|---|---|
| Load at compile time | use Module |
| Load at runtime | require Module |
| Add search path | use lib './lib' |
| Declare package | package My::Module; |
| Return true | 1; (end of every .pm file) |
| Export on request | our @EXPORT_OK = qw(func) |
| Export tags | our %EXPORT_TAGS = (tag => [...]) |
| Install module | cpanm Module::Name |
| Install from cpanfile | cpanm --installdeps . |
| Declare dependency | requires 'Module' in cpanfile |
| User-space install | eval "$(perl -Mlocal::lib)" |
Further Reading¶
- perlmod - Perl modules, packages, and symbol tables
- perlnewmod - preparing a new module for distribution
- Exporter - Exporter module documentation
- cpanfile specification - format reference for dependency files
- local::lib - create and use a local lib directory
- MetaCPAN - search and browse the CPAN archive
- Task::Kensho - curated list of recommended CPAN modules
- Intermediate Perl - the "Alpaca Book" covers modules, references, and distribution authoring
Previous: File I/O and System Interaction | Next: Object-Oriented Perl | Back to Index