Before writing a real validator, it helps to get the whole toolchain working
end to end with the simplest possible Plinth program: a function that adds two
numbers. In this tutorial you will install plinthc, create a tiny project
from scratch, compile it, run it, and read the Plutus Core it produced.
This tutorial targets Linux and macOS; the steps are the same on both. There is a Windows version.
Before you start
You need on PATH:
- ghcup 0.2.1.0 or newer (check with
ghcup --version); - a recent
cabal(3.8 or newer); - ghcup’s
bindirectory,~/.ghcup/binby default, where the compiler installed in step 1 lands. The standard ghcup setup adds it toPATHalready.
The rest of the tutorial assumes the compiler is available as plain
plinthc, which is what step 1 gives you. If you
built it from source instead, replace plinthc
with the path of the binary the build produced.
Step 1: Install Plinth
plinthc is distributed via ghcup as a custom tool named plinthc (see
Install plinthc for the
details). Add the plinthc release channel, then install and activate the tool:
$ ghcup config add-release-channel https://raw.githubusercontent.com/ican-group/plinthc/ghcup-channel/ghcup-plinthc.yaml
$ ghcup install plinthc 9.12.170.1
$ ghcup set plinthc 9.12.170.1
Check that the compiler is on PATH:
$ plinthc --version
If you have already installed 9.12.170.1 this way, skip to step 2.
Step 2: Create the project
Make a new directory and add the three files below.
cabal.project wires up the build. It points cabal at plinthc
9.12.170.1 and its ghc-pkg (installed by ghcup as plinthc-pkg-9.12.170.1,
so that it does not collide with your regular GHC); pulls the
Plinth libraries (plutus-tx, plutus-core) from the
ghc-plinth-plutus fork the compiler was built against; uses the Cardano
package repository (CHaP) for the remaining dependencies; and builds the Cardano
crypto C libraries (libsodium, secp256k1, blst) from source via the
*-clib blocks, so you do not need them installed on your system:
-- The plinthc release. The ghc-plinth-plutus tag below must be the commit
-- that this release is built from.
with-compiler: plinthc-9.12.170.1
with-hc-pkg: plinthc-pkg-9.12.170.1
packages: .
repository cardano-haskell-packages
url: https://chap.intersectmbo.org/
secure: True
root-keys:
3e0cce471cf09815f930210f7827266fd09045445d65923e6d0238a6cd15126f
443abb7fb497a134c343faf52f0b659bd7999bc06b7f63fa76dc99d631f9bea1
a86a1f6ce86c449c46666bda44268677abf29b5b2d2eb5ec7af903ec2f117a82
bcec67e8e99cabfa7764d75ad9b158d72bfacf70ca1d0ec8bc6b4406d1bf8413
c00aae8461a256275598500ea0e187588c35a5d5d7454fb57eac18d9edb86a56
d4a35cd3121aa00d18544bb0ac01c3e1691d618f462c46129271bccf39f7e8ee
index-state:
, hackage.haskell.org 2026-08-06T04:37:23Z
, cardano-haskell-packages 2026-08-05T05:00:53Z
-- The Plinth libraries, from the fork plinthc was built against. The
-- compiler itself is built into plinthc, so plutus-tx-plugin is not needed.
source-repository-package
type: git
location: https://github.com/ican-group/ghc-plinth-plutus
tag: 01c6883c4c9aa503793db8041a8d74252bf951e5
subdir: plutus-tx
plutus-core
source-repository-package
type: git
location: https://github.com/ican-group/cardano-base
tag: 055ebbcc73e1cb234f1fd3fa237a4fb087130183
subdir: cardano-crypto-class
cardano-crypto-praos
source-repository-package
type: git
location: https://github.com/haskell-cryptography/blst-clib
tag: 0fd1d38d5ceed5529ac646efae3095b493a97927
source-repository-package
type: git
location: https://github.com/haskell-cryptography/libsodium-clib
tag: 985c18f75a71ff721370940666d71fda53edbb14
source-repository-package
type: git
location: https://github.com/haskell-cryptography/secp256k1-clib
tag: 211b95baad422966c9e719ed70cbc189c58eaae5
package secp256k1-clib
flags: +schnorrsig +recovery +ecdh +extrakeys
package cardano-crypto-class
flags: +use-haskell-clibs
package cardano-crypto-praos
flags: +use-haskell-clibs
package sodium-clib
-- disable -fPIE: the static boot libraries are not built with it, so the
-- link phase fails otherwise.
configure-options: --enable-pie=no
Use the ghc-plinth-plutus commit that matches your plinthc: the compiler and
these libraries are released together as a matched set, built from the same
commit. The commit above matches release 9.12.170.1, the release in
with-compiler. To use a different release, change with-compiler,
with-hc-pkg and this tag together. The commit of a release is the plutus
submodule of its tag (v<version>) in
plinthc.
Not for production. The vendored crypto C libraries pulled in by the
*-clib source-repository-packages above have not been audited. Use this
setup for learning and experimentation only — never for contracts that
handle real funds.
plinth-add.cabal describes the single executable. The ghc-options are
the flags Plinth needs to compile predictably (turning off optimisations that
would reshape the code before the plugin sees it) and to select the Plutus Core
version to target:
cabal-version: 3.0
name: plinth-add
version: 0.1.0.0
build-type: Simple
executable plinth-add
main-is: Main.hs
default-language: Haskell2010
default-extensions: DataKinds
build-depends:
, base
, text
, prettyprinter
, plutus-tx
, plutus-core
ghc-options:
-fexternal-interpreter
-fobject-code -fno-full-laziness -fno-spec-constr
-fno-specialise -fno-strictness -fno-unbox-small-strict-fields
-fno-unbox-strict-fields
-fplugin-opt Plinth.Plugin:target-version=1.1.0
Main.hs is the whole program. It defines the on-chain function, compiles
it to Plutus Core with a Template Haskell splice, and writes the result out as
readable UPLC:
{-# LANGUAGE TemplateHaskell #-}
module Main where
import PlutusTx
import qualified PlutusTx.Prelude as Tx
import qualified PlutusTx.Code as Code
import PlutusCore.Pretty (prettyPlcReadableSimple)
import Prettyprinter (defaultLayoutOptions, layoutPretty)
import Prettyprinter.Render.Text (renderStrict)
import Data.Text (unpack)
-- The on-chain function: add two integers.
addTyped :: Integer -> Integer -> Integer
addTyped x y = x Tx.+ y
-- Compile it to Plutus Core at compile time.
addScript :: CompiledCode (Integer -> Integer -> Integer)
addScript = $$(compile [|| addTyped ||])
-- Render compiled code as readable UPLC text.
renderUPLC :: CompiledCode a -> String
renderUPLC =
unpack
. renderStrict
. layoutPretty defaultLayoutOptions
. prettyPlcReadableSimple
. Code.getPlcNoAnn
main :: IO ()
main = writeFile "add.uplc" (renderUPLC addScript)
The typed quote [|| addTyped ||] and the compile splice are the only
Plinth-specific pieces; PlutusTx.Prelude (imported as Tx) supplies the
on-chain +, and everything else is ordinary Haskell.
Step 3: Build it
$ cabal update
$ cabal build
The first run clones and builds the Plinth libraries and the crypto C libraries
from source, so expect it to take a while. Because the Plinth plugin is built
into plinthc, no extra plugin configuration is required: the compile splice
is translated to Plutus Core as part of the normal build.
Step 4: Run it
$ cabal run plinth-add
main runs and writes the compiled program to add.uplc in the current
directory.
Step 5: Look at the Plutus Core
$ cat add.uplc
You should see something like:
program 1.1.0 (\x y -> addInteger x y)
That is the UPLC your two-line Haskell function
compiled to — the + became the addInteger builtin, wrapped in a
program with its language version. This is
exactly the kind of code the Cardano ledger executes, produced from Haskell by
plinthc.
Where to go next
- Lock and unlock funds on a local chain
— the next tutorial: write a real validator, deploy it on a local
Cardano network, and spend from it with
cardano-cli. - Use plinthc in a project — the same compiler setup, described on its own.
- The Plinth contract language — how a real validator is written, beyond this toy function.
- Generate a blueprint — package a compiled script for off-chain tooling. (A tutorial walking through it on a small example is coming.)
- Test a Plinth contract locally — evaluate a compiled validator and measure its execution budget, with no node.