# Quick tutorial on package extensions

**URL:** <https://discourse.julialang.org/t/quick-tutorial-on-package-extensions/130923>\
**Category:** Package Management\
**Tags:** tutorials, package-extensions\
**Created:** [July 21, 2025, 9:38pm UTC](https://discourse.julialang.org/t/quick-tutorial-on-package-extensions/130923 "2025-07-21T21:38:24Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![erikedin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/erikedin/32/9281_2.png) [@erikedin](https://discourse.julialang.org/u/erikedin)\
**Post date:** [July 21, 2025, 9:38pm UTC](https://discourse.julialang.org/t/quick-tutorial-on-package-extensions/130923/1 "2025-07-21T21:38:24Z")

</div>

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.

```julia-auto
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:

```julia
# 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:

```julia
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.

```julia-auto
(MyPackage) pkg> add Example

```

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

```toml
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

```toml
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

```julia
(MyPackage) pkg> status

```

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

```julia
(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`.

```julia
(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`.

```toml
[extensions]
MyExt = "Example"

```

The complete file should look like

```toml
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.

```bash
$ 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.

```julia
# 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
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.

---

<div class="post-metadata">

**Author:** ![benc](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benc/32/46974_2.png) [@benc](https://discourse.julialang.org/u/benc)\
**Post date:** [July 22, 2025, 8:21am UTC](https://discourse.julialang.org/t/quick-tutorial-on-package-extensions/130923/2 "2025-07-22T08:21:06Z")

</div>

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.
