Perl modules and CPAN
last modified November 7, 2026
CPAN, the Comprehensive Perl Archive Network, holds tens of thousands of
distributions of Perl modules. Using them involves two questions: how Perl finds
a module when a program says use Some::Module, and how to install
modules so that each program gets the versions it needs. This article answers
both. It covers the @INC search path, use lib and
PERL5LIB, and the toolchain built around CPAN: the
cpanm installer, local::lib for private module
directories, the cpanfile dependency list, and Carton for
reproducible installs. Writing modules of our own is covered in the
Perl packages article.
The examples were run with Perl 5.44 installed by perlbrew on Linux, with
cpanm 1.7049, local::lib 2.000029, and Carton
1.0.35. The shell on this machine has local::lib enabled for
~/perl5, which shows up in some of the paths below.
The @INC search path
When Perl loads a module, it looks through the directories listed in the
@INC array. Each module it loads is recorded in the
%INC hash, together with the file it came from.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use Path::Tiny;
use List::Util;
say 'Search path (@INC):';
say " $_" for @INC;
say 'Loaded modules (%INC):';
for my $name (sort keys %INC) {
next unless $name =~ m{^(Path/Tiny|List/Util|strict)\.pm$};
say " $name => $INC{$name}";
}
The first two directories come from the PERL5LIB environment
variable set by local::lib; modules installed by the user go there.
The next ones belong to the perl installation: site_perl for
modules installed into the perl itself and the plain lib directory
for the core modules that ship with Perl. The
x86_64-linux directories hold modules with compiled C parts. The
keys of %INC are relative file names and the values show where
each module was actually found: Path::Tiny in the user directory,
List::Util and strict in the core. Unlike older
versions, Perl no longer includes the current directory, ., in
@INC, for security reasons.
$ perl inc.pl Search path (@INC): /home/jano/perl5/lib/perl5/x86_64-linux /home/jano/perl5/lib/perl5 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0 Loaded modules (%INC): List/Util.pm => /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux/List/Util.pm Path/Tiny.pm => /home/jano/perl5/lib/perl5/Path/Tiny.pm strict.pm => /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/strict.pm
How a module is found
Perl turns a module name into a relative path by replacing each
:: with a slash and appending .pm; thus
My::Greeting becomes My/Greeting.pm. It then tries
the directories of @INC in order and loads the first file that
exists. To see this, we create two versions of the same module in two
directories.
package My::Greeting;
use strict;
use warnings;
our $VERSION = '1.00';
sub hello { return 'hello from the first directory' }
1;
The module in the second directory is identical except that its
$VERSION is 2.00 and its message says second directory. The
program loads the module and reports which one it got.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use My::Greeting;
say My::Greeting::hello();
say 'version ', My::Greeting->VERSION;
say 'loaded from ', $INC{'My/Greeting.pm'};
The -I option adds directories to the front of @INC,
in the order given, so the first -I wins. The
PERL5LIB variable does the same from the environment. Without
either, the module cannot be found and Perl lists all the directories it tried.
Because the search stops at the first match, a stale copy of a module early in
@INC silently hides a newer one later in the list; checking
%INC is the quickest way to find out which file was really
loaded.
$ perl -Ifirst -Isecond greet.pl hello from the first directory version 1.00 loaded from first/My/Greeting.pm $ perl -Isecond -Ifirst greet.pl hello from the second directory version 2.00 loaded from second/My/Greeting.pm $ PERL5LIB=second perl greet.pl hello from the second directory version 2.00 loaded from second/My/Greeting.pm $ perl greet.pl Can't locate My/Greeting.pm in @INC (you may need to install the My::Greeting module) (@INC entries checked: /home/jano/perl5/lib/perl5/x86_64-linux /home/jano/perl5/lib/perl5 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0) at greet.pl line 6. BEGIN failed--compilation aborted at greet.pl line 6.
use lib and FindBin
A program that ships with its own modules should not rely on the user setting
-I or PERL5LIB. The lib pragma adds
directories to @INC at compile time, and FindBin
finds the directory of the running script, so the path works from anywhere.
#!/usr/bin/perl use strict; use warnings; use v5.34.0; use FindBin qw($RealBin); use lib "$RealBin/second"; use My::Greeting 2.00; say My::Greeting::hello(); say "version ", My::Greeting->VERSION;
The $RealBin variable holds the script's directory with symbolic
links resolved. A relative path such as use lib 'second' would be
resolved against the current working directory, which breaks when the program
is started from elsewhere. The use lib line must come before the
use statements that depend on it, since both run at compile
time.
$ perl uselib.pl hello from the second directory version 2.00
Minimum versions
A version number after the module name in use demands at least
that version. Perl calls the module's VERSION method, which
compares it with the module's $VERSION variable.
#!/usr/bin/perl use strict; use warnings; use v5.34.0; use lib 'first'; use My::Greeting 2.00; say My::Greeting::hello();
The first directory holds version 1.00, so the program stops at
compile time with a clear message. Requiring a minimum version is better than
failing later with an obscure error because a method or an option is
missing.
$ perl minver.pl My::Greeting version 2 required--this is only version 1.00 at minver.pl line 7. BEGIN failed--compilation aborted at minver.pl line 7.
Optional modules
The use statement loads a module at compile time and dies if it
is missing. The require function loads it at run time, so it can be
wrapped in eval to make a module optional.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
for my $module (qw(JSON::PP Cpanel::JSON::XS JSON::MaybeXS)) {
(my $file = "$module.pm") =~ s{::}{/}g;
if (eval { require $file; 1 }) {
say "$module ", $module->VERSION, " is installed";
} else {
say "$module is not installed";
}
}
When require receives a string, it treats it as a file name, so
the program converts the module name to a path first. The
eval { ...; 1 } idiom returns true only if the block finished. The
pattern is common for faster XS modules with a pure-Perl fallback: the program
here finds only JSON::PP, which ships with Perl.
$ perl optional.pl JSON::PP 4.16 is installed Cpanel::JSON::XS is not installed JSON::MaybeXS is not installed
Finding installed modules
A few one-liners answer the usual questions about an installed module: where it lives, which version it is, and what the latest release on CPAN is.
$ perldoc -l Path::Tiny List::Util /home/jano/perl5/lib/perl5/Path/Tiny.pm /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux/List/Util.pm $ perl -MPath::Tiny -E 'say Path::Tiny->VERSION' 0.150 $ cpanm --info Text::CSV ISHIGAKI/Text-CSV-2.06.tar.gz
Installing modules with cpanm
Perl ships with the cpan client, but most developers use
cpanm from the App::cpanminus distribution. It needs no
configuration, prints little, and installs dependencies automatically. It can
be installed with cpan App::cpanminus, through the system package
manager, or with curl -L https://cpanmin.us | perl - App::cpanminus.
Then cpanm Module::Name downloads, builds, tests, and installs a
module; the -n option skips the tests.
Where modules go depends on the environment. With the system perl, a plain
cpanm needs root access to write to the system directories, which
can conflict with the distribution's own packages; with a perlbrew perl or with
local::lib enabled, modules go to directories owned by the user.
The -L option installs into a project directory instead. We create a
small project that uses Text::Table::Tiny.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use Text::Table::Tiny qw(generate_table);
my @rows = (
['Module', 'Purpose'],
['Path::Tiny', 'file paths'],
['Text::CSV', 'CSV files'],
['Mojolicious', 'web framework'],
);
say generate_table(rows => \@rows, header_row => 1);
say 'Text::Table::Tiny ', Text::Table::Tiny->VERSION;
The program renders a list of rows as a text table. Running
cpanm -L extlib in the project directory installs the module and
all of its dependencies into extlib; modules already present in the
user's ~/perl5 are deliberately ignored, so the directory is
self-contained. The output shows how cpanm resolves the dependency
tree, installing the dependencies before the module that needs them.
$ cpanm -L extlib Text::Table::Tiny --> Working on Text::Table::Tiny Fetching http://www.cpan.org/authors/id/N/NE/NEILB/Text-Table-Tiny-1.03.tar.gz ... OK Configuring Text-Table-Tiny-1.03 ... OK ==> Found dependencies: Ref::Util, Test::Fatal, String::TtyLength --> Working on Ref::Util Fetching http://www.cpan.org/authors/id/A/AR/ARC/Ref-Util-0.204.tar.gz ... OK Configuring Ref-Util-0.204 ... OK ==> Found dependencies: Ref::Util::XS --> Working on Ref::Util::XS Fetching http://www.cpan.org/authors/id/X/XS/XSAWYERX/Ref-Util-XS-0.117.tar.gz ... OK Configuring Ref-Util-XS-0.117 ... OK Building and testing Ref-Util-XS-0.117 ... OK Successfully installed Ref-Util-XS-0.117 Building and testing Ref-Util-0.204 ... OK Successfully installed Ref-Util-0.204 --> Working on Test::Fatal Fetching http://www.cpan.org/authors/id/R/RJ/RJBS/Test-Fatal-0.019.tar.gz ... OK Configuring Test-Fatal-0.019 ... OK ==> Found dependencies: Try::Tiny --> Working on Try::Tiny Fetching http://www.cpan.org/authors/id/E/ET/ETHER/Try-Tiny-0.32.tar.gz ... OK Configuring Try-Tiny-0.32 ... OK Building and testing Try-Tiny-0.32 ... OK Successfully installed Try-Tiny-0.32 Building and testing Test-Fatal-0.019 ... OK Successfully installed Test-Fatal-0.019 --> Working on String::TtyLength Fetching http://www.cpan.org/authors/id/N/NE/NEILB/String-TtyLength-0.03.tar.gz ... OK Configuring String-TtyLength-0.03 ... OK ==> Found dependencies: Unicode::EastAsianWidth --> Working on Unicode::EastAsianWidth Fetching http://www.cpan.org/authors/id/A/AU/AUDREYT/Unicode-EastAsianWidth-12.0.tar.gz ... OK Configuring Unicode-EastAsianWidth-12.0 ... OK Building and testing Unicode-EastAsianWidth-12.0 ... OK Successfully installed Unicode-EastAsianWidth-12.0 Building and testing String-TtyLength-0.03 ... OK Successfully installed String-TtyLength-0.03 Building and testing Text-Table-Tiny-1.03 ... OK Successfully installed Text-Table-Tiny-1.03 7 distributions installed
local::lib
Modules in extlib are not in @INC, so the program
cannot find them yet. The local::lib module knows the directory
layout that cpanm -L creates and sets up both @INC
and the environment for it. Loading it with -M and the directory
name adds extlib/lib/perl5 and its architecture subdirectory to
@INC for one run.
$ perl report.pl Can't locate Text/Table/Tiny.pm in @INC (you may need to install the Text::Table::Tiny module) (@INC entries checked: /home/jano/perl5/lib/perl5/x86_64-linux /home/jano/perl5/lib/perl5 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0) at report.pl line 6. BEGIN failed--compilation aborted at report.pl line 6. $ perl -Mlocal::lib=extlib report.pl +-------------+---------------+ | Module | Purpose | +-------------+---------------+ | Path::Tiny | file paths | | Text::CSV | CSV files | | Mojolicious | web framework | +-------------+---------------+ Text::Table::Tiny 1.03
Setting up the shell with local::lib
Run without a program, local::lib prints shell commands instead.
Evaluating them with eval "$(perl -Mlocal::lib=extlib)"
sets PERL5LIB and PATH for the current shell and makes
cpanm and cpan install into the directory. The same
line with the default ~/perl5 directory in
~/.bashrc is the usual way to give a user a private module
directory without root access.
$ perl -Mlocal::lib=extlib
PATH="/home/jano/report/extlib/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="/home/jano/report/extlib/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="/home/jano/report/extlib${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"/home/jano/report/extlib\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=/home/jano/report/extlib"; export PERL_MM_OPT;
The cpanfile
Listing dependencies in a file is better than remembering them. A
cpanfile declares the modules a project needs, with minimum
versions, in a small Perl DSL.
requires 'perl', '5.034';
requires 'Text::Table::Tiny', '1.03';
requires 'Path::Tiny', '0.150';
on 'test' => sub {
requires 'Test::More', '1.302';
};
The requires function names a module and an optional minimum
version; perl itself can be required as well. The
on 'test' block lists modules needed only to run the tests;
there are also develop, build, and
configure phases, and recommends for optional
modules. Running cpanm --installdeps . installs everything the
file lists; here, with -L extlib, only Path::Tiny was
still missing.
$ cpanm -L extlib --installdeps . --> Working on . Configuring /home/jano/report ... OK ==> Found dependencies: Path::Tiny --> Working on Path::Tiny Fetching http://www.cpan.org/authors/id/D/DA/DAGOLDEN/Path-Tiny-0.150.tar.gz ... OK Configuring Path-Tiny-0.150 ... OK Building and testing Path-Tiny-0.150 ... OK Successfully installed Path-Tiny-0.150 <== Installed dependencies for .. Finishing. 1 distribution installed
Carton
A cpanfile states minimum versions, so two installs at different
times can end up with different module versions. Carton, Perl's equivalent of
Bundler or npm, records the exact versions that were installed and can reproduce
them on another machine. It is installed with cpanm Carton. The
carton install command reads the cpanfile, installs
the modules into the local directory, and writes
cpanfile.snapshot. Test-only dependencies such as
Test::Fatal are not installed this time, because Carton does not
run the modules' test suites.
$ carton install Installing modules using /home/jano/report/cpanfile Successfully installed Ref-Util-XS-0.117 Successfully installed Ref-Util-0.204 Successfully installed Unicode-EastAsianWidth-12.0 Successfully installed String-TtyLength-0.03 Successfully installed Text-Table-Tiny-1.03 Successfully installed Path-Tiny-0.150 6 distributions installed Complete! Modules were installed into /home/jano/report/local
carton exec and carton list
To run the program with exactly the installed modules, we use carton
exec, which sets up @INC to see local and the
core modules only. The carton list command prints the installed
distributions.
$ carton exec perl report.pl +-------------+---------------+ | Module | Purpose | +-------------+---------------+ | Path::Tiny | file paths | | Text::CSV | CSV files | | Mojolicious | web framework | +-------------+---------------+ Text::Table::Tiny 1.03 $ carton list Path-Tiny-0.150 Ref-Util-0.204 Ref-Util-XS-0.117 String-TtyLength-0.03 Text-Table-Tiny-1.03 Unicode-EastAsianWidth-12.0
The snapshot file
The snapshot file pins each distribution to an exact release and records the modules it provides and requires. The listing shows its first lines.
$ head -12 cpanfile.snapshot
# carton snapshot format: version 1.0
DISTRIBUTIONS
Path-Tiny-0.150
pathname: D/DA/DAGOLDEN/Path-Tiny-0.150.tar.gz
provides:
Path::Tiny 0.150
Path::Tiny::Error 0.150
requirements:
Carp 0
Cwd 0
Digest 1.03
Digest::SHA 5.45
Deploying with Carton
Both cpanfile and cpanfile.snapshot belong in version
control; the local directory does not. On a server or in a CI job,
carton install --deployment installs the versions from the snapshot
and fails if the snapshot is out of date, instead of resolving newer ones. The
carton bundle command stores the distribution tarballs in the
project, for machines without access to CPAN.
Best practices
- Do not install into the system perl: use perlbrew or
plenv for a separate perl, or
local::libfor a private directory, and leave the system perl to the package manager. - Declare dependencies: keep a
cpanfilewith minimum versions in every project. - Pin versions for deployment: commit
cpanfile.snapshotand deploy withcarton install --deployment. - Locate a project's own modules with FindBin: use
use lib "$RealBin/lib"rather than relying on the current directory. - Check %INC when in doubt: the first match in
@INCwins, and%INCshows which file was loaded. - Require minimum versions: write
use Module VERSIONwhen the code depends on newer features.
In this article we have looked at how Perl searches @INC for
modules, how -I, PERL5LIB, and use lib
change the search path, how to require versions and load optional modules, and
how to install and pin dependencies with cpanm,
local::lib, cpanfile, and Carton.
Source
App::cpanminus · local::lib · cpanfile · Carton · perlvar: @INC
Author
List all Perl tutorials.