Skip to content

Modules and CPAN

Code Organization and the Perl Ecosystem

Version: 1.1 Year: 2026


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:

require File::Basename;
my $base = File::Basename::basename($path);   # full package name required

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.

# Print your @INC to see the search path
perl -e 'print join("\n", @INC), "\n";'

Modifying @INC

The use lib pragma is the standard way to add paths inside scripts. It prepends paths to @INC at compile time:

use lib '/home/user/lib';       # inside your script
use lib './lib';                 # relative path

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

name    = My-Utils
author  = Your Name <you@example.com>
license = Perl_5
version = 0.001

[@Basic]

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


Previous: File I/O and System Interaction | Next: Object-Oriented Perl | Back to Index

Comments