Quick tutorial on package extensions

Hello! I was reading the documentation on package extensions and felt that I needed some more detailed instructions. I wrote this tutorial based on what I learned (only today, so this is surely not an exhaustive tutorial).

Quick tutorial on package extensions

A package can have modules that are conditionally loaded based on what other packages are loaded. These are called extensions.

Goal

In the below text, there will be a package MyPackage. It will have an extension module MyExt which should only be loaded if the Example package is loaded in the environment.

MyPackage will define a method greet(::This), which prints a message “Hello world, from MyPackage!”. MyExt will extend that method with greet(::That), which prints “Hello world, from MyExt!”.

A new environment will be created. If Example is not installed, then we can run greet(This()) and see “Hello world, from MyPackage!”, but greet(That()) will not work.

If Example is installed, then we can run greet(That()) and see “Hello world, from MyExt!”.

Source code

Here the package is MyPackage, with a main module also called MyPackage. The module MyExt is an extension.

Here is the source code layout, with only relevant files shown.

MyPackage/ ->
    src/ ->
        MyPackage.jl
    ext/ ->
        MyExt.jl
    Project.toml

The extension is expected to be in MyPackage/ext. Note that this it outside of the MyPackage/src directory.

Here is the main MyPackage/src/MyPackage.jl file:

# MyPackage/src/MyPackage.jl
module MyPackage

export greet, This, That

struct This end
struct That end

greet(::This) = print("Hello World, from MyPackage!")

end # module MyPackage

Here is the extension file MyPackage/ext/MyExt.jl file:

module MyExt

using MyPackage

MyPackage.greet(::That) = print("Hello world, from MyExt!")

end # module MyExt

Note that ext/MyExt.jl is not included from src/MyPackage.jl the way a normal sub-module file would be. Julia will know to find the MyExt package under ext.

Adding a weak dependency

Suppose you have a package MyPackage with an extension module MyExt. The module MyExt should only be loaded if the package Example is loaded in the same environment. Then MyPackage will need to have the Example package as a “weak dependency”. This allows a package to specify version limits for an optional package.

Adding a weak dependency requires (this is my understanding at least, at the time of writing in July 2025) manually modifying Project.toml.

First, in the MyPackage environment, add the Example package as a normal dependency.

(MyPackage) pkg> add Example

This updates the Project.toml file, so that it looks something like

name = "MyPackage"
uuid = "b6ab5532-f566-471e-8495-90ba1797e34a"
authors = ["Erik Edin <erikedin@users.noreply.github.com>"]
version = "0.1.0"

[deps]
Example = "7876af07-990d-54b4-ab0e-23690620f79a"

[compat]
Example = "0.5.5"

To make the Example package into a weak dependency, move it from the section [deps] to [weakdeps]. Then it should look like

name = "MyPackage"
uuid = "b6ab5532-f566-471e-8495-90ba1797e34a"
authors = ["Erik Edin <erikedin@users.noreply.github.com>"]
version = "0.1.0"

[weakdeps]
Example = "7876af07-990d-54b4-ab0e-23690620f79a"

[compat]
Example = "0.5.5"

In this simple case, there are no other packages in the [deps] section, so we just rename it to [weakdeps].

In Julia, in package mode, if you run

(MyPackage) pkg> status

then you will get a warning that Project.toml has changed.

(MyPackage) pkg> status
Project MyPackage v0.1.0
Status `~/.julia/dev/MyPackage/Project.toml` (empty project)
Warning The project dependencies or compat requirements have changed since the manifest was last resolved. It is recommended to `Pkg.resolve()` or consider `Pkg.update()` if necessary.

Following the recommendation, run resolve.

(MyPackage) pkg> resolve
  No Changes to `~/.julia/dev/MyPackage/Project.toml`
    Updating `~/.julia/dev/MyPackage/Manifest.toml`
  [7876af07] - Example v0.5.5

Note that Project.toml is unchanged and that Example is still listed under [weakdeps].

When MyPackage is added into an environment, then Example will not be automatically installed.

Making MyExt an extension

What we want here is for MyExt to be loaded if and only if Example is loaded in the environment.
This is done by adding MyExt to the [extensions] section of Project.toml.

[extensions]
MyExt = "Example"

The complete file should look like

name = "MyPackage"
uuid = "b6ab5532-f566-471e-8495-90ba1797e34a"
authors = ["Erik Edin <erikedin@users.noreply.github.com>"]
version = "0.1.0"

[weakdeps]
Example = "7876af07-990d-54b4-ab0e-23690620f79a"

[extensions]
MyExt = "Example"

[compat]
Example = "0.5.5"

Using an extension

Here a new environment is created. Note that this is a different environment that the MyPackage environment. Both the MyPackage and the conditional dependency Example are added.

$ mkdir newenv
$ cd newenv
$ julia --project=.
pkg> add Example
pkg> dev MyPackage

MyPackage is added with dev because it’s not a registered package.

First, load just MyPackage and see that greet(This()) works. Also ensure that greet(That()) does not work, until the Example package is loaded.

# Load package and ensure that greet(This()) works as expected.
julia> using MyPackage
julia> greet(This())
Hello World, from MyPackage!

# The Example package is not loaded, so the extension is also not
# loaded.
julia> greet(That())
ERROR: MethodError: no method matching greet(::That)
The function `greet` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  greet(::This)
   @ MyPackage ~/.julia/dev/MyPackage/src/MyPackage.jl:8

Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

Load the Example package, and try running greet(That()) again.

julia> using Example
Precompiling MyExt...
  1 dependency successfully precompiled in 1 seconds. 2 already precompiled.

julia> greet(That())
Hello world, from MyExt!

Now the extension to the greet method is available.

29 Likes

Thank you for this, it has been on my todo list forever to use this, but haven’t had time to look into the details, this is a perfect walkthrough.

2 Likes