Modules and organising a Julia project

Split code into modules, use and import them deliberately, and understand what Project.toml and Manifest.toml each record.

Modules

module Geometry

export area, perimeter          # names users get with "using"

struct Circle
    r::Float64
end

area(c::Circle) = pi * c.r^2
perimeter(c::Circle) = 2 * pi * c.r

function _helper(x)             # not exported: internal
    x * 2
end

end    # module
FormEffect
using GeometryBrings exported names into scope; the module itself is also accessible
import GeometryOnly the module name; call Geometry.area(...)
using Geometry: areaOnly the named bindings
import Geometry: areaNamed binding, and lets you add methods to it
exportMarks names for using; does not make them public API
include("x.jl")Evaluates a file inside the current module

To add a method to a function owned by another module you must import it, not using it. That rule prevents a stray definition from silently extending a function you did not intend to touch.

Projects and environments

mkdir MyPkg && cd MyPkg
julia --project=. -e 'using Pkg; Pkg.generate("MyPkg")'

# resulting layout
# MyPkg/
#   Project.toml        name, uuid, version, [deps], [compat]
#   Manifest.toml       exact resolved versions and hashes
#   src/MyPkg.jl        module MyPkg ... end
#   test/runtests.jl    @testset for the package
using Pkg
Pkg.activate(".")
Pkg.add(["DataFrames", "CSV"])       # writes into Project.toml and Manifest.toml
Pkg.status()
Pkg.update()
Pkg.instantiate()                     # install exactly what Manifest.toml records

Pkg.test()                            # runs test/runtests.jl in a clean environment
  • Project.toml declares direct dependencies and version compatibility; it is written by hand or edited by Pkg.add.
  • Manifest.toml records the full resolved dependency graph. Commit it for applications; it is optional for libraries.
  • Pkg.test() runs in a separate sandbox with the test dependencies added, so a passing test proves the package works outside your session.

Layout and testing

# src/MyPkg.jl
module MyPkg

include("types.jl")
include("operations.jl")

using .Types            # a submodule declared inside include files
using .Operations
export transform, Point

end
# test/runtests.jl
using Test
using MyPkg

@testset "MyPkg" begin
    @testset "transform" begin
        @test transform(Point(1.0, 2.0)) == Point(2.0, 4.0)
        @test_throws ArgumentError transform(Point(NaN, 0.0))
    end
end
💡
A package is only reusable if nothing depends on the current working directory. Use @__DIR__ or pkgdir(MyPkg) to build paths to data files, never a bare relative string, which breaks the moment someone calls your code from another directory.

FAQ

What is the difference between using and import?
using makes exported names available unqualified; import requires the module prefix and is needed when you want to add methods to another module's function.
Should I commit Manifest.toml for a library?
For an application or a script that must reproduce exactly, commit it. For a library that others will install, publishing the manifest can conflict with the resolver in the consumer's environment, so most libraries commit only the project file.

Installing Julia and the REPL workflow Debugging, testing and benchmarking Julia code

Last refreshed 2026-09-18.