diff --git a/src/pins/template/README.md b/src/pins/template/README.md
new file mode 100644
index 00000000..a895725b
--- /dev/null
+++ b/src/pins/template/README.md
@@ -0,0 +1,86 @@
+
+# Writing your own pin
+
+The files here should give you an introduction into writing your own pin.
+
+## Overall workflow
+
+The `encrypt` script reads a plain text from stdin, encrypts it using
+`jose jwe enc` which writes the result to stdout, together with some
+information how to re-create the plain text later. The encryption key
+itself *must* *not* be included here.
+
+The encryption key is provided or created by the pin and stashed away
+in some way. That is the core logic of a pin.
+
+A configuration in the JSON format is provided as the first parameter,
+it controls the pin's operation.
+
+The `decrypt` script reads the encrypted information from stdin,
+decrypts it using `ose jwe dec` which again writes the result to
+stdout. The information provided by `encrypt` above is available, this
+must be sufficient to restore the encryption key.
+
+## How to use this template
+
+Copy all the files here (except for this one) into a new subdirectory
+of `src/pins/`, named as your pin.
+
+Replace @pin@ with the name of your pin everywhere, including file names.
+
+The `clevis-{en,de}crypt-@pin@` scripts require the most attention.
+
+Have a man page in `clevis-encrypt-@pin@.1.adoc`.
+
+Adjust `meson.build`.
+
+Provide a test in `pin-@pin@`.
+
+Adjust dracut configuration in `dracut.module-setup.sh.in`.
+
+Adjust initramfs configuration in `initramfs.in`.
+
+Optionally add something to `clevis-luks-list`.
+
+Finally, add your pin in `../meson.build`.
+
+## Comments
+
+An extra form of comments is used to explain concepts. They all should
+be removed before sending out patches/merge requests.
+
+ #%# some generic information
+ #!# things worth to know, gotchas
+ #?# some bits that require more understanding
+
+## Nameing your pin and configuration variables
+
+The pin name should be short and reflect the purpose. To avoid trouble
+or extra work, the name should start with a letter, followed by letters,
+digits, or underscore.
+
+Parameter names for the pin configuration should follow the same
+syntax. These templates assume they can be used as a shell variable.
+
+## Templates variables
+
+The templates use `@...@` to mark places that can semi-automatically
+be adjusted to your needs. Variables are
+
+* `@pin@`: The name of this pin, see above
+* `@PIN@`: The name of this pin, uppercase
+* `@year@`: Current year
+* `@name@`: Your name
+* `@email@`: Your e-mail address
+* `@mand1@`: The name of a mandatory parameter
+* `@mand2@`: The name of another mandatory parameter
+* `@opt1@`: The name of an optional parameter
+* `@param1@`: The name of a parameter needed for decryption
+* `@param2@`: Another name
+
+If you have more parameters, extend accordingly
+
+Any `@@` requires attention in wording.
+
+Make sure you've replaced *all* occurances of template variables.
+Else the build will probably fail.
diff --git a/src/pins/template/clevis-decrypt-@pin@ b/src/pins/template/clevis-decrypt-@pin@
new file mode 100755
index 00000000..621de719
--- /dev/null
+++ b/src/pins/template/clevis-decrypt-@pin@
@@ -0,0 +1,73 @@
+#%# Creating an decrypting pin
+#%#
+#%# Read README.md and clevis-encrypt-@pin@ first, this file aims to
+#%# to avoid information duplication.
+#%# Unfortunately, this one uses a bashism (read -d) that is not at
+#%# all easy to eliminate.
+#!/bin/bash
+
+set -eu
+
+# Copyright (c) @year@ @name@
+# Author: @name@ <@email@>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+#%# This program takes no options - everything needed to know will be
+#%# read from stdin.
+[ $# -eq 1 ] && [ "${1:-}" = "--summary" ] && exit 2
+
+if [ -t 0 ] ; then
+ echo >&2
+ echo 'Usage: clevis decrypt @pin@ < JWE > PLAINTEXT' >&2
+ echo >&2
+ exit 1
+fi
+
+#%# The input is concatenated using the dot. Read the first element
+#%# but leave everything else in the buffer. Only read -d can to that.
+read -d . hdr64
+#%# The header is base64-encoded. Decode now and also verify this is valid JSON
+if ! hdr="$(jose fmt --quote="$hdr64" --string --b64load --object --output=-)" ; then
+ echo 'JWE header corrupt' >&2
+ exit 1
+fi
+
+#%# Input validation: The pin must exist by name.
+if [ "$(jose fmt --json="$hdr" --get clevis --get pin --unquote=-)" != '@pin@' ] ; then
+ echo 'JWE pin mismatch!' >&2
+ exit 1
+fi
+
+#%# Load the parameters into shell variables.
+if ! @param1@="$(jose fmt --json="$hdr" --get clevis --get @pin@ --get @param1@ --unquote=-)" ; then
+ echo 'JWE missing 'clevis.@pin@.@param1@' header parameter!' >&2
+ exit 1
+fi
+if ! @param2@="$(jose fmt --json="$hdr" --get clevis --get @pin@ --get @param2@ --unquote=-)" ; then
+ echo 'JWE missing 'clevis.@pin@.@param2@' header parameter!' >&2
+ exit 1
+fi
+
+#%# Possibly some pre-checks on your parameters are needed.
+
+#%# Now everything is set up for your pin's business logic
+#%#
+#%# Your job: Somehow bring the key into `jwk`.
+jwk="$(load_jwk)"
+
+#%# Finally, forward everything to `jose jwe dec` which does the
+#%# decryption job.
+( printf '%s' "$jwk$hdr64." ; cat ) | exec jose jwe dec --key=- --input=-
diff --git a/src/pins/template/clevis-encrypt-@pin@ b/src/pins/template/clevis-encrypt-@pin@
new file mode 100755
index 00000000..ee01834e
--- /dev/null
+++ b/src/pins/template/clevis-encrypt-@pin@
@@ -0,0 +1,140 @@
+#%# Creating an encrypting pin
+#%#
+#%# Read README.md first.
+#%#
+#%# The shell interpreter. For portability, it should have as little
+#%# requirements as possibly. Especially the decrypt should be executable
+#%# in busybox' ash as well
+#!/bin/sh
+
+#%# Some hardening. Safeguard against coding errors.
+set -eu
+
+#%# Legal stuff. Put your name etc. here.
+#%# Of course you're not bound to GPL-3+ but it will certainly ease
+#%# inclusion in upstream clevis if you use that.
+# Copyright (c) @year@ @name@
+# Author: @name@ <@email@>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+#%# A one-line summary. Will be used in the help messages below.
+SUMMARY='Encrypts using a @pin@ @@ policy'
+
+#%# Some option parsing, very simple.
+#%# Don't touch, it's hardcoded in the `clevis` program.
+if [ "${1:-}" = '--summary' ] ; then
+ echo "$SUMMARY"
+ exit 0
+fi
+
+#%# Regular operation assumes output goes to a file. If not, print
+#%# some usage information and bail out.
+if [ -t 0 ] ; then
+#!# Since this script runs in a pipe, *all* operational messages
+#!# must go to stderr.
+ exec >&2
+ echo
+#%# Don't be confused: This script is called from `clevis`, so
+#%# the usage text has spaces, not dashes.
+#%# Also, the configuration is in $1, see below.
+ echo 'Usage: clevis encrypt @pin@ CONFIG < PLAINTEXT > JWE'
+ echo
+ echo "$SUMMARY"
+ echo
+ echo 'This command uses the following configuration properties:'
+ echo
+#%# For the sake of users: Give a good explanation of your pin's
+#%# parameters.
+#%# Mandatory parameters should contain the string "REQUIRED"
+ echo ' @mand1@: One parameter @@ (REQUIRED)'
+ echo
+ echo ' @mand2@: Another parameter @@ (REQUIRED)'
+ echo
+#%# Optional parameters should mention the default value.
+ echo ' @opt1@: An optional parameter @@ (default: @@)'
+ echo
+#%# Pure visual: Make sure the short descriptions are aligned to the
+#%# same column.
+ exit 2
+fi
+
+#%# The CONFIG parameter in $1 has to be valid JSON
+if ! cfg="$(jose fmt --json="${1:-}" --object --output=- 2>/dev/null)" ; then
+ echo 'Configuration is malformed!' >&2
+ exit 1
+fi
+
+#%# Load the values from the configuration into shell variables.
+#%# Re-using the name is certainly a good idea
+#%#
+#%# For mandatory parameters it's like that:
+if ! @mand1@="$(jose fmt --json="$cfg" --object --get @mand1@ --unquote=-)" ; then
+ echo 'Missing the required @mand1@ property!' >&2
+ exit 1
+fi
+if ! @mand2@="$(jose fmt --json="$cfg" --object --get @mand2@ --unquote=-)" ; then
+ echo 'Missing the required @mand2@ property!' >&2
+ exit 1
+fi
+
+#%# For optional parameters, use:
+@opt1@="$(jose fmt --json="$cfg" --object --get @opt1@ --unquote=-)" || @opt1@='@@'
+
+#%# Possibly validate parameters. If a check can be done at *en*crypt
+#%# time, it should be done now.
+
+#%# Now everything is set up for your pin's business logic.
+#%#
+#%# Your jobs, in no particular order:
+#%#
+#%# 1. Have the key in `jwk`:
+#%# If you want to create a new key:
+jwk="$(jose jwk gen --input='{"alg":"A256GCM"}')"
+#%# Feel free to use different algorithms for `"alg"`,
+#%# jose-jwk-gen(1) and jose-alg(1) have more on all this.
+#%#
+#%# Or, if you want to use an existing key, just load it into `jwk`
+#%# from wherever you got it from:
+jwk="$(somehow_get_the_key)"
+#%# Remember the result must be a valid jwk object.
+
+#%# 2. Store the key somewhere. That's your logic.
+store_the_jwk "$jwk"
+#%# It is a good idea to store the entire `$jwk`. If you want to
+#%# extract the actual key, use
+#%# jose fmt --json="$jwk" --object --get k --unquote=-
+#%# ... but you're probably wrong if you want to do that.
+
+#%# 3. Assemble all the information you will need to re-create
+#%# the key later in `jwe`:
+#%#
+#%# First create a skeleton that declares the pin, and creates a store.
+jwe='{"protected":{"clevis":{"pin":"@pin@","@pin@":{}}}}'
+#%# Then populate that store. Possibly you'll just have to pass
+#%# the parameters. Leave out those you will not need for decryption.
+#%# NB: The long form of the `-U` parameter of `jose fmt` is "--unwind"
+jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get @pin@ --quote "$@mand1@" --set @mand1@ -UUUU --output=-)"
+jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get @pin@ --quote "$@opt1@" --set @opt1@ -UUUU --output=-)"
+
+#%# Almost there!
+#%# Forward everything to `jose jwe enc` which does the encryption job -
+#%# including reading the plaintext from stdin which gets replicated
+#%# using `cat`.
+( printf '%s' "$jwe$jwk" ; cat ) | exec jose jwe enc --input=- --key=- --detached=- --compact
+#%# Anything that follows is executed only if jose failed.
+#%#
+#!# When using mktemp or the like, cleaning up has to be done
+#!# manually. See clevis-{en,de}crypt-tpm2 for an example.
diff --git a/src/pins/template/clevis-encrypt-@pin@.1.adoc b/src/pins/template/clevis-encrypt-@pin@.1.adoc
new file mode 100644
index 00000000..34539f84
--- /dev/null
+++ b/src/pins/template/clevis-encrypt-@pin@.1.adoc
@@ -0,0 +1,51 @@
+#%# Align the equal signs after substitution
+CLEVIS-ENCRYPT-@PIN@(1)
+=======================
+:doctype: manpage
+
+
+== NAME
+
+clevis-encrypt-@pin@ - Encrypts using a @@ policy
+
+== SYNOPSIS
+
+*clevis encrypt @pin@* CONFIG < PT > JWE
+
+== OVERVIEW
+
+The *clevis encrypt @pin@* command encrypts using a @@ policy.
+Its only argument is the JSON configuration object.
+
+#%# And so on ...
+
+Encrypting data using the @pin@ pin works like this:
+
+ $ clevis encrypt @pin@ '{"@mand1@":"@@","@mand2":"@@"}' < PT > JWE
+
+To decrypt the data, just pass it to the *clevis decrypt* command:
+
+ $ clevis decrypt < JWE > PT
+
+== CONFIG
+
+This command uses the following configuration properties:
+
+#%# Keep this in sync with the short help in clevis-encrypt-@pin@
+
+* *@mand1@* (string) :
+ @@ (REQUIRED)
+
+* *@mand2@* (string) :
+ @@ (REQUIRED)
+
+* *@opt1@* (string) :
+ @@ (default: @@)
+
+== BUGS
+
+#%# List any flaws and gotchas here.
+
+== SEE ALSO
+
+link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)]
diff --git a/src/pins/template/dracut.module-setup.sh.in b/src/pins/template/dracut.module-setup.sh.in
new file mode 100755
index 00000000..89b6d6ae
--- /dev/null
+++ b/src/pins/template/dracut.module-setup.sh.in
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) @year@ @name@
+# Author: @name@ <@email@>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+depends() {
+ echo clevis
+ return 0
+}
+
+install() {
+ inst clevis-decrypt-@pin@
+}
diff --git a/src/pins/template/initramfs.in b/src/pins/template/initramfs.in
new file mode 100755
index 00000000..2fb343f7
--- /dev/null
+++ b/src/pins/template/initramfs.in
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) @year@ @name@
+# Author: @name@ <@email@>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+case $1 in
+prereqs)
+ exit 0
+ ;;
+esac
+
+. @initramfstoolsdir@/hook-functions
+
+die() {
+ code="$1"
+ msg="$2"
+ echo " (ERROR): $msg" >&2
+ exit $1
+}
+
+copy_exec @bindir@/clevis-decrypt-@pin@ || die 1 "@bindir@/clevis-decrypt-@pin@ not found"
+
+#%# Possibly you to need to install additional binaries:
+#%#
+#%# find_binary() {
+#%# bin_name="$1"
+#%# resolved=$(which ${bin_name})
+#%# [ -z "$resolved" ] && die 1 "Unable to find ${bin_name}"
+#%# echo "$resolved"
+#%# }
+#%#
+#%# curl_bin=$(find_binary "curl")
+#%# copy_exec "${curl_bin}" || die 2 "Unable to copy ${curl_bin} to initrd image"
diff --git a/src/pins/template/meson.build b/src/pins/template/meson.build
new file mode 100644
index 00000000..49a31c69
--- /dev/null
+++ b/src/pins/template/meson.build
@@ -0,0 +1,66 @@
+#%# This controls build and test
+#%#
+#%# Feel free to peek into other tests to get an idea what can be done.
+#%#
+#%# Possibly you'll need some extra programs, probe for them.
+
+#%# curl is just an example
+curl = find_program('curl', required: false)
+#%# dracut and initramfs_tools are needed for early userland
+dracut = dependency('dracut', required: false)
+initramfs_tools = find_program('update-initramfs', required: false)
+
+if curl.found()
+#%# BUild the programs
+ bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-@pin@')
+ bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-@pin@')
+#%# and the manpage
+ mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-@pin@.1')
+
+#%# Set up a test environment
+ env = environment()
+ env.append('PATH',
+ join_paths(meson.source_root(), 'src'),
+ meson.current_source_dir(),
+ '/usr/libexec',
+ libexecdir,
+ separator: ':'
+ )
+
+#%# Run the test.
+ test('pin-@pin@', find_program('./pin-@pin@'), env: env)
+else
+ warning('Will not install @pin@ pin due to missing dependencies!')
+endif
+
+#%# dracut support
+if dracut.found()
+ dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + '-pin-@pin@'
+#?# In general, substituation is not needed but it's better to
+#?# stay flexible.
+ configure_file(
+ input: 'dracut.module-setup.sh.in',
+ output: 'module-setup.sh',
+ install_dir: dracutdir,
+ configuration: data,
+ )
+else
+ warning('Will not install dracut module clevis-pin-@pin@ due to missing dependencies!')
+endif
+
+#%# initramfs support
+if initramfs_tools.found()
+ initramfstools_dir = '/usr/share/initramfs-tools'
+ initramfs_hooks_dir = '/usr/share/initramfs-tools/hooks'
+ initramfs_data = configuration_data()
+ initramfs_data.merge_from(data)
+ initramfs_data.set('initramfstoolsdir', initramfstools_dir)
+ configure_file(
+ input: 'initramfs.in',
+ output: 'clevis-pin-@pin@',
+ install_dir: initramfs_hooks_dir,
+ configuration: initramfs_data,
+ )
+else
+ warning('Will not install initramfs module clevis-pin-@pin@ due to missing dependencies!')
+endif
diff --git a/src/pins/template/pin-@pin@ b/src/pins/template/pin-@pin@
new file mode 100755
index 00000000..c740e3e0
--- /dev/null
+++ b/src/pins/template/pin-@pin@
@@ -0,0 +1,40 @@
+#%# Of course you should provide some test
+#%#
+#!/bin/sh
+
+#%# Maximum verbosity is desired
+set -ex
+
+# Copyright (c) @year@ @name@
+# Author: @name@ <@email@>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+#%# A simple test will encrypt a text, decrypt, and compare.
+#%# Some work might be necessary to set up a test bed.
+#%#
+#%# Feel free to peek into other tests to get an idea what can be done.
+#%#
+#!# Your test might require root privileges but your script might
+#!# not have them. So probe, and exit mit the magical code 77 to skip
+#!# that test instead of failing.
+
+#%# Create your configuration
+cfg="$(printf '{"@mand1@":"%s","@mand2@":"%s"}' "@@" "@@")"
+#%# The input
+inp='hi'
+enc="$(printf '%s' "$inp" | clevis encrypt @pin@ "$cfg")"
+dec="$(printf '%s' "$enc" | clevis decrypt)"
+test "$dec" = "$inp"