Docs docs docs

This commit is contained in:
binwiederhier 2025-07-19 15:37:05 +02:00
parent 4603802f62
commit ae62e0d955
35 changed files with 764 additions and 2208 deletions

View file

@ -77,6 +77,12 @@ func WithMarkdown() PublishOption {
return WithHeader("X-Markdown", "yes")
}
// WithTemplate instructs the server to use a specific template for the message. If templateName is is "yes" or "1",
// the server will interpret the message and title as a template.
func WithTemplate(templateName string) PublishOption {
return WithHeader("X-Template", templateName)
}
// WithFilename sets a filename for the attachment, and/or forces the HTTP body to interpreted as an attachment
func WithFilename(filename string) PublishOption {
return WithHeader("X-Filename", filename)

View file

@ -32,6 +32,7 @@ var flagsPublish = append(
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
&cli.BoolFlag{Name: "markdown", Aliases: []string{"md"}, EnvVars: []string{"NTFY_MARKDOWN"}, Usage: "Message is formatted as Markdown"},
&cli.StringFlag{Name: "template", Aliases: []string{"tpl"}, EnvVars: []string{"NTFY_TEMPLATE"}, Usage: "use templates to transform JSON message body"},
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
@ -98,6 +99,7 @@ func execPublish(c *cli.Context) error {
actions := c.String("actions")
attach := c.String("attach")
markdown := c.Bool("markdown")
template := c.String("template")
filename := c.String("filename")
file := c.String("file")
email := c.String("email")
@ -146,6 +148,9 @@ func execPublish(c *cli.Context) error {
if markdown {
options = append(options, client.WithMarkdown())
}
if template != "" {
options = append(options, client.WithTemplate(template))
}
if filename != "" {
options = append(options, client.WithFilename(filename))
}

View file

@ -29,13 +29,9 @@ func init() {
commands = append(commands, cmdServe)
}
const (
defaultServerConfigFile = "/etc/ntfy/server.yml"
)
var flagsServe = append(
append([]cli.Flag{}, flagsDefault...),
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: defaultServerConfigFile, Usage: "config file"},
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: server.DefaultConfigFile, Usage: "config file"},
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used as HTTP listen address"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-https", Aliases: []string{"listen_https", "L"}, EnvVars: []string{"NTFY_LISTEN_HTTPS"}, Usage: "ip:port used as HTTPS listen address"}),
@ -56,7 +52,7 @@ var flagsServe = append(
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentTotalSizeLimit), Usage: "limit of the on-disk attachment cache"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentFileSizeLimit), Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-expiry-duration", Aliases: []string{"attachment_expiry_duration", "X"}, EnvVars: []string{"NTFY_ATTACHMENT_EXPIRY_DURATION"}, Value: util.FormatDuration(server.DefaultAttachmentExpiryDuration), Usage: "duration after which uploaded attachments will be deleted (e.g. 3h, 20h)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "template-dir", Aliases: []string{"template_dir"}, EnvVars: []string{"NTFY_TEMPLATE_DIR"}, Usage: "directory to load named message templates from"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "template-dir", Aliases: []string{"template_dir"}, EnvVars: []string{"NTFY_TEMPLATE_DIR"}, Value: server.DefaultTemplateDir, Usage: "directory to load named message templates from"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: util.FormatDuration(server.DefaultKeepaliveInterval), Usage: "interval of keepalive messages"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: util.FormatDuration(server.DefaultManagerInterval), Usage: "interval of for message pruning and stats printing"}),
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{Name: "disallowed-topics", Aliases: []string{"disallowed_topics"}, EnvVars: []string{"NTFY_DISALLOWED_TOPICS"}, Usage: "topics that are not allowed to be used"}),

View file

@ -6,6 +6,7 @@ import (
"crypto/subtle"
"errors"
"fmt"
"heckel.io/ntfy/v2/server"
"heckel.io/ntfy/v2/user"
"os"
"strings"
@ -25,7 +26,7 @@ func init() {
var flagsUser = append(
append([]cli.Flag{}, flagsDefault...),
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: defaultServerConfigFile, DefaultText: defaultServerConfigFile, Usage: "config file"},
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: server.DefaultConfigFile, DefaultText: server.DefaultConfigFile, Usage: "config file"},
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
)

View file

@ -944,27 +944,165 @@ Templating lets you **format a JSON message body into human-friendly message and
[Go templates](https://pkg.go.dev/text/template) (see tutorials [here](https://blog.gopheracademy.com/advent-2017/using-go-templates/),
[here](https://www.digitalocean.com/community/tutorials/how-to-use-templates-in-go), and
[here](https://developer.hashicorp.com/nomad/tutorials/templates/go-template-syntax)). This is specifically useful when
**combined with webhooks** from services such as GitHub, Grafana, or other services that emit JSON webhooks.
**combined with webhooks** from services such as [GitHub](https://docs.github.com/en/webhooks/about-webhooks),
[Grafana](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/),
[Alertmanager](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config), or other services that emit JSON webhooks.
Instead of using a separate bridge program to parse the webhook body into the format ntfy expects, you can include a templated
message and/or a templated title which will be populated based on the fields of the webhook body (so long as the webhook body
is valid JSON).
You can enable templating by setting the `X-Template` header (or its aliases `Template` or `tpl`):
You can enable templating by setting the `X-Template` header (or its aliases `Template` or `tpl`, or the query parameter `?template=...`):
* **Pre-defined template files**: Setting the `X-Template` header or query parameter to a template name (e.g. `?template=github`)
to a pre-defined template name (e.g. `github`, `grafana`, or `alertmanager`) will use the template with that name.
See [pre-defined templates](#pre-defined-templates) for more details.
* **Custom template files**: Setting the `X-Template` header or query parameter to a custom template name (e.g. `?template=myapp`)
will use a custom template file from the template directory (defaults to `/etc/ntfy/templates`, can be overridden with `template-dir`).
See [custom templates](#custom-templates) for more details.
* **Inline templating**: Setting the `X-Template` header or query parameter to `yes` or `1` (e.g. `?template=yes`)
will enable inline templating, which means that the `message` and/or `title` **will be parsed as a Go template**.
See [Inline templating](#inline-templating) and [Template syntax](#template-syntax) for details on how to use Go
templates in your messages and titles.
* **Pre-defined template files**: You can also set `X-Template` header or query parameter to a template name (e.g. `?template=github`).
ntfy will then read the template from either the built-in pre-defined template files, or from the template files defined in
the `template-dir`. See [Template files](#pre-defined-templates) for more details.
will enable inline templating, which means that the `message` and/or `title` will be parsed as a Go template.
See [inline templating](#inline-templating) for more details.
To learn the basics of Go's templating language, please see [template syntax](#template-syntax).
### Pre-defined templates
When `X-Template: <name>` (aliases: `Template: <name>`, `Tpl: <name>`) or `?template=<name>` is set, ntfy will transform the
message and/or title based on one of the built-in pre-defined templates
The following **pre-defined templates** are available:
* `github`: Formats a subset of [GitHub webhook](https://docs.github.com/en/webhooks/about-webhooks) payloads (PRs, issues, new star, new watcher, new comment)
* `grafana`: Formats [Grafana webhook](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/) payloads (firing/resolved alerts)
* `alertmanager`: Formats [Alertmanager webhook](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config) payloads (firing/resolved alerts)
Here's an example of how to use the pre-defined `github` template: First, configure the webhook in GitHub to send a webhook to your ntfy topic, e.g. `https://ntfy.sh/mytopic?template=github`.
<figure markdown>
![GitHub webhook config](static/img/screenshot-github-webhook-config.png){ width=600 }
<figcaption>GitHub webhook configuration</figcaption>
</figure>
After that, when GitHub publishes a JSON webhook to the topic, ntfy will transform it according to the template rules
and you'll receive notifications in the ntfy app. Here's an example for when somebody stars your repository:
<figure markdown>
![pre-defined template](static/img/android-screenshot-template-predefined.png){ width=500 }
<figcaption>Receiving a webhook, formatted using the pre-defined "github" template</figcaption>
</figure>
### Custom templates
To define **your own custom templates**, place a template file in the template directory (defaults to `/etc/ntfy/templates`, can be overridden with `template-dir`)
and set the `X-Template` header or query parameter to the name of the template file (without the `.yml` extension).
For example, if you have a template file `/etc/ntfy/templates/myapp.yml`, you can set the header `X-Template: myapp` or
the query parameter `?template=myapp` to use it.
Template files must have the `.yml` (not: `.yaml`!) extension and must be formatted as YAML. They may contain `title` and `message` keys,
which are interpreted as Go templates.
Here's an **example custom template**:
=== "Custom template (/etc/ntfy/templates/myapp.yml)"
```yaml
title: |
{{- if eq .status "firing" }}
{{- if gt .percent 90.0 }}🚨 Critical alert
{{- else }}⚠️ Alert{{- end }}
{{- else if eq .status "resolved" }}
✅ Alert resolved
{{- end }}
message: |
Status: {{ .status }}
Type: {{ .type | upper }} ({{ .percent }}%)
Server: {{ .server }}
```
Once you have the template file in place, you can send the payload to your topic using the `X-Template`
header or query parameter:
=== "Command line (curl)"
```
echo '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}' | \
curl -sT- "https://ntfy.example.com/mytopic?template=myapp"
```
=== "ntfy CLI"
```
echo '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}' | \
ntfy publish --template=myapp https://ntfy.example.com/mytopic
```
=== "HTTP"
``` http
POST /mytopic?template=myapp HTTP/1.1
Host: ntfy.example.com
{
"status": "firing",
"type": "cpu",
"server": "ntfy.sh",
"percent": 99
}
```
=== "JavaScript"
``` javascript
fetch('https://ntfy.example.com/mytopic?template=myapp', {
method: 'POST',
body: '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}'
})
```
=== "Go"
``` go
payload := `{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}`
req, _ := http.NewRequest("POST", "https://ntfy.example.com/mytopic?template=myapp", strings.NewReader(payload))
http.DefaultClient.Do(req)
```
=== "PowerShell"
``` powershell
$Request = @{
Method = "POST"
Uri = "https://ntfy.example.com/mytopic?template=myapp"
Body = '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}'
}
Invoke-RestMethod @Request
```
=== "Python"
``` python
requests.post("https://ntfy.example.com/mytopic?template=myapp",
json={"status":"firing","type":"cpu","server":"ntfy.sh","percent":99})
```
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.example.com/mytopic?template=myapp', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json",
'content' => '{"status":"firing","type":"cpu","server":"ntfy.sh","percent":99}'
]
]));
```
Which will result in a notification that looks like this:
<figure markdown>
![notification from custom JSON webhook template](static/img/android-screenshot-template-custom.png){ width=500 }
<figcaption>JSON webhook, transformed using a custom template</figcaption>
</figure>
### Inline templating
When `X-Template: yes` or `?template=yes` is set, you can use Go templates in the `message` and `title` fields of your
webhook payload. This is most useful if no [pre-defined template](#pre-defined-templates) exists, for templated one-off messages,
of if you do not control the ntfy server (e.g., if you're using ntfy.sh). Please consider using [template files](#pre-defined-templates)
When `X-Template: yes` (aliases: `Template: yes`, `Tpl: yes`) or `?template=yes` is set, you can use Go templates in the `message` and `title` fields of your
webhook payload.
Inline templates are most useful for templated one-off messages, of if you do not control the ntfy server (e.g., if you're using ntfy.sh).
Consider using [pre-defined templates](#pre-defined-templates) or [custom templates](#custom-templates) instead,
if you control the ntfy server, as templates are much easier to maintain.
Here's an **example for a Grafana alert**:
@ -1078,10 +1216,6 @@ This example uses the `message`/`m` and `title`/`t` query parameters, but obviou
`Message`/`Title` headers. It will send a notification with a title `phil-pc: A severe error has occurred` and a message
`Error message: Disk has run out of space`.
### Pre-defined templates
XXXXXXXXXXXXxx
### Template syntax
ntfy uses [Go templates](https://pkg.go.dev/text/template) for its templates, which is arguably one of the most powerful,
yet also one of the worst templating languages out there.
@ -1101,23 +1235,23 @@ message templating and for transforming the data provided through the JSON paylo
Below are the functions that are available to use inside your message/title templates.
* [String Functions](./sprig/strings.md): `trim`, `trunc`, `substr`, `plural`, etc.
* [String List Functions](./sprig/string_slice.md): `splitList`, `sortAlpha`, etc.
* [Integer Math Functions](./sprig/math.md): `add`, `max`, `mul`, etc.
* [Integer List Functions](./sprig/integer_slice.md): `until`, `untilStep`
* [Date Functions](./sprig/date.md): `now`, `date`, etc.
* [Defaults Functions](./sprig/defaults.md): `default`, `empty`, `coalesce`, `fromJSON`, `toJSON`, `toPrettyJSON`, `toRawJSON`, `ternary`
* [Encoding Functions](./sprig/encoding.md): `b64enc`, `b64dec`, etc.
* [Lists and List Functions](./sprig/lists.md): `list`, `first`, `uniq`, etc.
* [Dictionaries and Dict Functions](./sprig/dicts.md): `get`, `set`, `dict`, `hasKey`, `pluck`, `dig`, etc.
* [Type Conversion Functions](./sprig/conversion.md): `atoi`, `int64`, `toString`, etc.
* [Path and Filepath Functions](./sprig/paths.md): `base`, `dir`, `ext`, `clean`, `isAbs`, `osBase`, `osDir`, `osExt`, `osClean`, `osIsAbs`
* [Flow Control Functions]( ./sprig/flow_control.md): `fail`
* [String Functions](publish/template-functions.md#string-functions): `trim`, `trunc`, `substr`, `plural`, etc.
* [String List Functions](publish/template-functions.md#string-list-functions): `splitList`, `sortAlpha`, etc.
* [Integer Math Functions](publish/template-functions.md#integer-math-functions): `add`, `max`, `mul`, etc.
* [Integer List Functions](publish/template-functions.md#integer-list-functions): `until`, `untilStep`
* [Date Functions](publish/template-functions.md#date-functions): `now`, `date`, etc.
* [Defaults Functions](publish/template-functions.md#default-functions): `default`, `empty`, `coalesce`, `fromJSON`, `toJSON`, `toPrettyJSON`, `toRawJSON`, `ternary`
* [Encoding Functions](publish/template-functions.md#encoding-functions): `b64enc`, `b64dec`, etc.
* [Lists and List Functions](publish/template-functions.md#lists-and-list-functions): `list`, `first`, `uniq`, etc.
* [Dictionaries and Dict Functions](publish/template-functions.md#dictionaries-and-dict-functions): `get`, `set`, `dict`, `hasKey`, `pluck`, `dig`, etc.
* [Type Conversion Functions](publish/template-functions.md#type-conversion-functions): `atoi`, `int64`, `toString`, etc.
* [Path and Filepath Functions](publish/template-functions.md#path-and-filepath-functions): `base`, `dir`, `ext`, `clean`, `isAbs`, `osBase`, `osDir`, `osExt`, `osClean`, `osIsAbs`
* [Flow Control Functions](publish/template-functions.md#flow-control-functions): `fail`
* Advanced Functions
* [UUID Functions](./sprig/uuid.md): `uuidv4`
* [Reflection](./sprig/reflection.md): `typeOf`, `kindIs`, `typeIsLike`, etc.
* [Cryptographic and Security Functions](./sprig/crypto.md): `sha256sum`, etc.
* [URL](./sprig/url.md): `urlParse`, `urlJoin`
* [UUID Functions](publish/template-functions.md#uuid-functions): `uuidv4`
* [Reflection](publish/template-functions.md#reflection-functions): `typeOf`, `kindIs`, `typeIsLike`, etc.
* [Cryptographic and Security Functions](publish/template-functions.md#cryptographic-and-security-functions): `sha256sum`, etc.
* [URL](publish/template-functions.md#url-functions): `urlParse`, `urlJoin`
## Publish as JSON

View file

@ -1,24 +0,0 @@
# Template Functions
ntfy includes a (reduced) version of [Sprig](https://github.com/Masterminds/sprig) to add functions that can be used
when you are using the [message template](publish.md#message-templating) feature.
Below are the functions that are available to use inside your message/title templates.
* [String Functions](./sprig/strings.md): `trim`, `trunc`, `substr`, `plural`, etc.
* [String List Functions](./sprig/string_slice.md): `splitList`, `sortAlpha`, etc.
* [Integer Math Functions](./sprig/math.md): `add`, `max`, `mul`, etc.
* [Integer List Functions](./sprig/integer_slice.md): `until`, `untilStep`
* [Date Functions](./sprig/date.md): `now`, `date`, etc.
* [Defaults Functions](./sprig/defaults.md): `default`, `empty`, `coalesce`, `fromJSON`, `toJSON`, `toPrettyJSON`, `toRawJSON`, `ternary`
* [Encoding Functions](./sprig/encoding.md): `b64enc`, `b64dec`, etc.
* [Lists and List Functions](./sprig/lists.md): `list`, `first`, `uniq`, etc.
* [Dictionaries and Dict Functions](./sprig/dicts.md): `get`, `set`, `dict`, `hasKey`, `pluck`, `dig`, etc.
* [Type Conversion Functions](./sprig/conversion.md): `atoi`, `int64`, `toString`, etc.
* [Path and Filepath Functions](./sprig/paths.md): `base`, `dir`, `ext`, `clean`, `isAbs`, `osBase`, `osDir`, `osExt`, `osClean`, `osIsAbs`
* [Flow Control Functions](./sprig/flow_control.md): `fail`
* Advanced Functions
* [UUID Functions](./sprig/uuid.md): `uuidv4`
* [Reflection](./sprig/reflection.md): `typeOf`, `kindIs`, `typeIsLike`, etc.
* [Cryptographic and Security Functions](./sprig/crypto.md): `sha256sum`, etc.
* [URL](./sprig/url.md): `urlParse`, `urlJoin`

View file

@ -1,36 +0,0 @@
# Type Conversion Functions
The following type conversion functions are provided by Sprig:
- `atoi`: Convert a string to an integer.
- `float64`: Convert to a `float64`.
- `int`: Convert to an `int` at the system's width.
- `int64`: Convert to an `int64`.
- `toDecimal`: Convert a unix octal to a `int64`.
- `toString`: Convert to a string.
- `toStrings`: Convert a list, slice, or array to a list of strings.
Only `atoi` requires that the input be a specific type. The others will attempt
to convert from any type to the destination type. For example, `int64` can convert
floats to ints, and it can also convert strings to ints.
## toStrings
Given a list-like collection, produce a slice of strings.
```
list 1 2 3 | toStrings
```
The above converts `1` to `"1"`, `2` to `"2"`, and so on, and then returns
them as a list.
## toDecimal
Given a unix octal permission, produce a decimal.
```
"0777" | toDecimal
```
The above converts `0777` to `511` and returns the value as an int64.

View file

@ -1,41 +0,0 @@
# Cryptographic and Security Functions
Sprig provides a couple of advanced cryptographic functions.
## sha1sum
The `sha1sum` function receives a string, and computes it's SHA1 digest.
```
sha1sum "Hello world!"
```
## sha256sum
The `sha256sum` function receives a string, and computes it's SHA256 digest.
```
sha256sum "Hello world!"
```
The above will compute the SHA 256 sum in an "ASCII armored" format that is
safe to print.
## sha512sum
The `sha512sum` function receives a string, and computes it's SHA512 digest.
```
sha512sum "Hello world!"
```
The above will compute the SHA 512 sum in an "ASCII armored" format that is
safe to print.
## adler32sum
The `adler32sum` function receives a string, and computes its Adler-32 checksum.
```
adler32sum "Hello world!"
```

View file

@ -1,126 +0,0 @@
# Date Functions
## now
The current date/time. Use this in conjunction with other date functions.
## ago
The `ago` function returns duration from time.Now in seconds resolution.
```
ago .CreatedAt
```
returns in `time.Duration` String() format
```
2h34m7s
```
## date
The `date` function formats a date.
Format the date to YEAR-MONTH-DAY:
```
now | date "2006-01-02"
```
Date formatting in Go is a [little bit different](https://pauladamsmith.com/blog/2011/05/go_time.html).
In short, take this as the base date:
```
Mon Jan 2 15:04:05 MST 2006
```
Write it in the format you want. Above, `2006-01-02` is the same date, but
in the format we want.
## dateInZone
Same as `date`, but with a timezone.
```
dateInZone "2006-01-02" (now) "UTC"
```
## duration
Formats a given amount of seconds as a `time.Duration`.
This returns 1m35s
```
duration "95"
```
## durationRound
Rounds a given duration to the most significant unit. Strings and `time.Duration`
gets parsed as a duration, while a `time.Time` is calculated as the duration since.
This return 2h
```
durationRound "2h10m5s"
```
This returns 3mo
```
durationRound "2400h10m5s"
```
## unixEpoch
Returns the seconds since the unix epoch for a `time.Time`.
```
now | unixEpoch
```
## dateModify, mustDateModify
The `dateModify` takes a modification and a date and returns the timestamp.
Subtract an hour and thirty minutes from the current time:
```
now | date_modify "-1.5h"
```
If the modification format is wrong `dateModify` will return the date unmodified. `mustDateModify` will return an error otherwise.
## htmlDate
The `htmlDate` function formats a date for inserting into an HTML date picker
input field.
```
now | htmlDate
```
## htmlDateInZone
Same as htmlDate, but with a timezone.
```
htmlDateInZone (now) "UTC"
```
## toDate, mustToDate
`toDate` converts a string to a date. The first argument is the date layout and
the second the date string. If the string can't be convert it returns the zero
value.
`mustToDate` will return an error in case the string cannot be converted.
This is useful when you want to convert a string date to another format
(using pipe). The example below converts "2017-12-31" to "31/12/2017".
```
toDate "2006-01-02" "2017-12-31" | date "02/01/2006"
```

View file

@ -1,169 +0,0 @@
# Default Functions
Sprig provides tools for setting default values for templates.
## default
To set a simple default value, use `default`:
```
default "foo" .Bar
```
In the above, if `.Bar` evaluates to a non-empty value, it will be used. But if
it is empty, `foo` will be returned instead.
The definition of "empty" depends on type:
- Numeric: 0
- String: ""
- Lists: `[]`
- Dicts: `{}`
- Boolean: `false`
- And always `nil` (aka null)
For structs, there is no definition of empty, so a struct will never return the
default.
## empty
The `empty` function returns `true` if the given value is considered empty, and
`false` otherwise. The empty values are listed in the `default` section.
```
empty .Foo
```
Note that in Go template conditionals, emptiness is calculated for you. Thus,
you rarely need `if empty .Foo`. Instead, just use `if .Foo`.
## coalesce
The `coalesce` function takes a list of values and returns the first non-empty
one.
```
coalesce 0 1 2
```
The above returns `1`.
This function is useful for scanning through multiple variables or values:
```
coalesce .name .parent.name "Matt"
```
The above will first check to see if `.name` is empty. If it is not, it will return
that value. If it _is_ empty, `coalesce` will evaluate `.parent.name` for emptiness.
Finally, if both `.name` and `.parent.name` are empty, it will return `Matt`.
## all
The `all` function takes a list of values and returns true if all values are non-empty.
```
all 0 1 2
```
The above returns `false`.
This function is useful for evaluating multiple conditions of variables or values:
```
all (eq .Request.TLS.Version 0x0304) (.Request.ProtoAtLeast 2 0) (eq .Request.Method "POST")
```
The above will check http.Request is POST with tls 1.3 and http/2.
## any
The `any` function takes a list of values and returns true if any value is non-empty.
```
any 0 1 2
```
The above returns `true`.
This function is useful for evaluating multiple conditions of variables or values:
```
any (eq .Request.Method "GET") (eq .Request.Method "POST") (eq .Request.Method "OPTIONS")
```
The above will check http.Request method is one of GET/POST/OPTIONS.
## fromJSON, mustFromJSON
`fromJSON` decodes a JSON document into a structure. If the input cannot be decoded as JSON the function will return an empty string.
`mustFromJSON` will return an error in case the JSON is invalid.
```
fromJSON "{\"foo\": 55}"
```
## toJSON, mustToJSON
The `toJSON` function encodes an item into a JSON string. If the item cannot be converted to JSON the function will return an empty string.
`mustToJSON` will return an error in case the item cannot be encoded in JSON.
```
toJSON .Item
```
The above returns JSON string representation of `.Item`.
## toPrettyJSON, mustToPrettyJSON
The `toPrettyJSON` function encodes an item into a pretty (indented) JSON string.
```
toPrettyJSON .Item
```
The above returns indented JSON string representation of `.Item`.
## toRawJSON, mustToRawJSON
The `toRawJSON` function encodes an item into JSON string with HTML characters unescaped.
```
toRawJSON .Item
```
The above returns unescaped JSON string representation of `.Item`.
## ternary
The `ternary` function takes two values, and a test value. If the test value is
true, the first value will be returned. If the test value is empty, the second
value will be returned. This is similar to the c ternary operator.
### true test value
```
ternary "foo" "bar" true
```
or
```
true | ternary "foo" "bar"
```
The above returns `"foo"`.
### false test value
```
ternary "foo" "bar" false
```
or
```
false | ternary "foo" "bar"
```
The above returns `"bar"`.

View file

@ -1,172 +0,0 @@
# Dictionaries and Dict Functions
Sprig provides a key/value storage type called a `dict` (short for "dictionary",
as in Python). A `dict` is an _unorder_ type.
The key to a dictionary **must be a string**. However, the value can be any
type, even another `dict` or `list`.
Unlike `list`s, `dict`s are not immutable. The `set` and `unset` functions will
modify the contents of a dictionary.
## dict
Creating dictionaries is done by calling the `dict` function and passing it a
list of pairs.
The following creates a dictionary with three items:
```
$myDict := dict "name1" "value1" "name2" "value2" "name3" "value 3"
```
## get
Given a map and a key, get the value from the map.
```
get $myDict "name1"
```
The above returns `"value1"`
Note that if the key is not found, this operation will simply return `""`. No error
will be generated.
## set
Use `set` to add a new key/value pair to a dictionary.
```
$_ := set $myDict "name4" "value4"
```
Note that `set` _returns the dictionary_ (a requirement of Go template functions),
so you may need to trap the value as done above with the `$_` assignment.
## unset
Given a map and a key, delete the key from the map.
```
$_ := unset $myDict "name4"
```
As with `set`, this returns the dictionary.
Note that if the key is not found, this operation will simply return. No error
will be generated.
## hasKey
The `hasKey` function returns `true` if the given dict contains the given key.
```
hasKey $myDict "name1"
```
If the key is not found, this returns `false`.
## pluck
The `pluck` function makes it possible to give one key and multiple maps, and
get a list of all of the matches:
```
pluck "name1" $myDict $myOtherDict
```
The above will return a `list` containing every found value (`[value1 otherValue1]`).
If the give key is _not found_ in a map, that map will not have an item in the
list (and the length of the returned list will be less than the number of dicts
in the call to `pluck`.
If the key is _found_ but the value is an empty value, that value will be
inserted.
A common idiom in Sprig templates is to uses `pluck... | first` to get the first
matching key out of a collection of dictionaries.
## dig
The `dig` function traverses a nested set of dicts, selecting keys from a list
of values. It returns a default value if any of the keys are not found at the
associated dict.
```
dig "user" "role" "humanName" "guest" $dict
```
Given a dict structured like
```
{
user: {
role: {
humanName: "curator"
}
}
}
```
the above would return `"curator"`. If the dict lacked even a `user` field,
the result would be `"guest"`.
Dig can be very useful in cases where you'd like to avoid guard clauses,
especially since Go's template package's `and` doesn't shortcut. For instance
`and a.maybeNil a.maybeNil.iNeedThis` will always evaluate
`a.maybeNil.iNeedThis`, and panic if `a` lacks a `maybeNil` field.)
`dig` accepts its dict argument last in order to support pipelining.
## keys
The `keys` function will return a `list` of all of the keys in one or more `dict`
types. Since a dictionary is _unordered_, the keys will not be in a predictable order.
They can be sorted with `sortAlpha`.
```
keys $myDict | sortAlpha
```
When supplying multiple dictionaries, the keys will be concatenated. Use the `uniq`
function along with `sortAlpha` to get a unqiue, sorted list of keys.
```
keys $myDict $myOtherDict | uniq | sortAlpha
```
## pick
The `pick` function selects just the given keys out of a dictionary, creating a
new `dict`.
```
$new := pick $myDict "name1" "name2"
```
The above returns `{name1: value1, name2: value2}`
## omit
The `omit` function is similar to `pick`, except it returns a new `dict` with all
the keys that _do not_ match the given keys.
```
$new := omit $myDict "name1" "name3"
```
The above returns `{name2: value2}`
## values
The `values` function is similar to `keys`, except it returns a new `list` with
all the values of the source `dict` (only one dictionary is supported).
```
$vals := values $myDict
```
The above returns `list["value1", "value2", "value 3"]`. Note that the `values`
function gives no guarantees about the result ordering- if you care about this,
then use `sortAlpha`.

View file

@ -1,6 +0,0 @@
# Encoding Functions
Sprig has the following encoding and decoding functions:
- `b64enc`/`b64dec`: Encode or decode with Base64
- `b32enc`/`b32dec`: Encode or decode with Base32

View file

@ -1,11 +0,0 @@
# Flow Control Functions
## fail
Unconditionally returns an empty `string` and an `error` with the specified
text. This is useful in scenarios where other conditionals have determined that
template rendering should fail.
```
fail "Please accept the end user license agreement"
```

View file

@ -1,41 +0,0 @@
# Integer List Functions
## until
The `until` function builds a range of integers.
```
until 5
```
The above generates the list `[0, 1, 2, 3, 4]`.
This is useful for looping with `range $i, $e := until 5`.
## untilStep
Like `until`, `untilStep` generates a list of counting integers. But it allows
you to define a start, stop, and step:
```
untilStep 3 6 2
```
The above will produce `[3 5]` by starting with 3, and adding 2 until it is equal
or greater than 6. This is similar to Python's `range` function.
## seq
Works like the bash `seq` command.
* 1 parameter (end) - will generate all counting integers between 1 and `end` inclusive.
* 2 parameters (start, end) - will generate all counting integers between `start` and `end` inclusive incrementing or decrementing by 1.
* 3 parameters (start, step, end) - will generate all counting integers between `start` and `end` inclusive incrementing or decrementing by `step`.
```
seq 5 => 1 2 3 4 5
seq -3 => 1 0 -1 -2 -3
seq 0 2 => 0 1 2
seq 2 -2 => 2 1 0 -1 -2
seq 0 2 10 => 0 2 4 6 8 10
seq 0 -2 -5 => 0 -2 -4
```

View file

@ -1,188 +0,0 @@
# Lists and List Functions
Sprig provides a simple `list` type that can contain arbitrary sequential lists
of data. This is similar to arrays or slices, but lists are designed to be used
as immutable data types.
Create a list of integers:
```
$myList := list 1 2 3 4 5
```
The above creates a list of `[1 2 3 4 5]`.
## first, mustFirst
To get the head item on a list, use `first`.
`first $myList` returns `1`
`first` panics if there is a problem while `mustFirst` returns an error to the
template engine if there is a problem.
## rest, mustRest
To get the tail of the list (everything but the first item), use `rest`.
`rest $myList` returns `[2 3 4 5]`
`rest` panics if there is a problem while `mustRest` returns an error to the
template engine if there is a problem.
## last, mustLast
To get the last item on a list, use `last`:
`last $myList` returns `5`. This is roughly analogous to reversing a list and
then calling `first`.
`last` panics if there is a problem while `mustLast` returns an error to the
template engine if there is a problem.
## initial, mustInitial
This compliments `last` by returning all _but_ the last element.
`initial $myList` returns `[1 2 3 4]`.
`initial` panics if there is a problem while `mustInitial` returns an error to the
template engine if there is a problem.
## append, mustAppend
Append a new item to an existing list, creating a new list.
```
$new = append $myList 6
```
The above would set `$new` to `[1 2 3 4 5 6]`. `$myList` would remain unaltered.
`append` panics if there is a problem while `mustAppend` returns an error to the
template engine if there is a problem.
## prepend, mustPrepend
Push an element onto the front of a list, creating a new list.
```
prepend $myList 0
```
The above would produce `[0 1 2 3 4 5]`. `$myList` would remain unaltered.
`prepend` panics if there is a problem while `mustPrepend` returns an error to the
template engine if there is a problem.
## concat
Concatenate arbitrary number of lists into one.
```
concat $myList ( list 6 7 ) ( list 8 )
```
The above would produce `[1 2 3 4 5 6 7 8]`. `$myList` would remain unaltered.
## reverse, mustReverse
Produce a new list with the reversed elements of the given list.
```
reverse $myList
```
The above would generate the list `[5 4 3 2 1]`.
`reverse` panics if there is a problem while `mustReverse` returns an error to the
template engine if there is a problem.
## uniq, mustUniq
Generate a list with all of the duplicates removed.
```
list 1 1 1 2 | uniq
```
The above would produce `[1 2]`
`uniq` panics if there is a problem while `mustUniq` returns an error to the
template engine if there is a problem.
## without, mustWithout
The `without` function filters items out of a list.
```
without $myList 3
```
The above would produce `[1 2 4 5]`
Without can take more than one filter:
```
without $myList 1 3 5
```
That would produce `[2 4]`
`without` panics if there is a problem while `mustWithout` returns an error to the
template engine if there is a problem.
## has, mustHas
Test to see if a list has a particular element.
```
has 4 $myList
```
The above would return `true`, while `has "hello" $myList` would return false.
`has` panics if there is a problem while `mustHas` returns an error to the
template engine if there is a problem.
## compact, mustCompact
Accepts a list and removes entries with empty values.
```
$list := list 1 "a" "foo" ""
$copy := compact $list
```
`compact` will return a new list with the empty (i.e., "") item removed.
`compact` panics if there is a problem and `mustCompact` returns an error to the
template engine if there is a problem.
## slice, mustSlice
To get partial elements of a list, use `slice list [n] [m]`. It is
equivalent of `list[n:m]`.
- `slice $myList` returns `[1 2 3 4 5]`. It is same as `myList[:]`.
- `slice $myList 3` returns `[4 5]`. It is same as `myList[3:]`.
- `slice $myList 1 3` returns `[2 3]`. It is same as `myList[1:3]`.
- `slice $myList 0 3` returns `[1 2 3]`. It is same as `myList[:3]`.
`slice` panics if there is a problem while `mustSlice` returns an error to the
template engine if there is a problem.
## chunk
To split a list into chunks of given size, use `chunk size list`. This is useful for pagination.
```
chunk 3 (list 1 2 3 4 5 6 7 8)
```
This produces list of lists `[ [ 1 2 3 ] [ 4 5 6 ] [ 7 8 ] ]`.
## A Note on List Internals
A list is implemented in Go as a `[]interface{}`. For Go developers embedding
Sprig, you may pass `[]interface{}` items into your template context and be
able to use all of the `list` functions on those items.

View file

@ -1,78 +0,0 @@
# Integer Math Functions
The following math functions operate on `int64` values.
## add
Sum numbers with `add`. Accepts two or more inputs.
```
add 1 2 3
```
## add1
To increment by 1, use `add1`
## sub
To subtract, use `sub`
## div
Perform integer division with `div`
## mod
Modulo with `mod`
## mul
Multiply with `mul`. Accepts two or more inputs.
```
mul 1 2 3
```
## max
Return the largest of a series of integers:
This will return `3`:
```
max 1 2 3
```
## min
Return the smallest of a series of integers.
`min 1 2 3` will return `1`
## floor
Returns the greatest float value less than or equal to input value
`floor 123.9999` will return `123.0`
## ceil
Returns the greatest float value greater than or equal to input value
`ceil 123.001` will return `124.0`
## round
Returns a float value with the remainder rounded to the given number to digits after the decimal point.
`round 123.555555 3` will return `123.556`
## randInt
Returns a random integer value from min (inclusive) to max (exclusive).
```
randInt 12 30
```
The above will produce a random number in the range [12,30].

View file

@ -1,114 +0,0 @@
# Path and Filepath Functions
While Sprig does not grant access to the filesystem, it does provide functions
for working with strings that follow file path conventions.
## Paths
Paths separated by the slash character (`/`), processed by the `path` package.
Examples:
* The [Linux](https://en.wikipedia.org/wiki/Linux) and
[MacOS](https://en.wikipedia.org/wiki/MacOS)
[filesystems](https://en.wikipedia.org/wiki/File_system):
`/home/user/file`, `/etc/config`;
* The path component of
[URIs](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier):
`https://example.com/some/content/`, `ftp://example.com/file/`.
### base
Return the last element of a path.
```
base "foo/bar/baz"
```
The above prints "baz".
### dir
Return the directory, stripping the last part of the path. So `dir "foo/bar/baz"`
returns `foo/bar`.
### clean
Clean up a path.
```
clean "foo/bar/../baz"
```
The above resolves the `..` and returns `foo/baz`.
### ext
Return the file extension.
```
ext "foo.bar"
```
The above returns `.bar`.
### isAbs
To check whether a path is absolute, use `isAbs`.
## Filepaths
Paths separated by the `os.PathSeparator` variable, processed by the `path/filepath` package.
These are the recommended functions to use when parsing paths of local filesystems, usually when dealing with local files, directories, etc.
Examples:
* Running on Linux or MacOS the filesystem path is separated by the slash character (`/`):
`/home/user/file`, `/etc/config`;
* Running on [Windows](https://en.wikipedia.org/wiki/Microsoft_Windows)
the filesystem path is separated by the backslash character (`\`):
`C:\Users\Username\`, `C:\Program Files\Application\`;
### osBase
Return the last element of a filepath.
```
osBase "/foo/bar/baz"
osBase "C:\\foo\\bar\\baz"
```
The above prints "baz" on Linux and Windows, respectively.
### osDir
Return the directory, stripping the last part of the path. So `osDir "/foo/bar/baz"`
returns `/foo/bar` on Linux, and `osDir "C:\\foo\\bar\\baz"`
returns `C:\\foo\\bar` on Windows.
### osClean
Clean up a path.
```
osClean "/foo/bar/../baz"
osClean "C:\\foo\\bar\\..\\baz"
```
The above resolves the `..` and returns `foo/baz` on Linux and `C:\\foo\\baz` on Windows.
### osExt
Return the file extension.
```
osExt "/foo.bar"
osExt "C:\\foo.bar"
```
The above returns `.bar` on Linux and Windows, respectively.
### osIsAbs
To check whether a file path is absolute, use `osIsAbs`.

View file

@ -1,50 +0,0 @@
# Reflection Functions
Sprig provides rudimentary reflection tools. These help advanced template
developers understand the underlying Go type information for a particular value.
Go has several primitive _kinds_, like `string`, `slice`, `int64`, and `bool`.
Go has an open _type_ system that allows developers to create their own types.
Sprig provides a set of functions for each.
## Kind Functions
There are two Kind functions: `kindOf` returns the kind of an object.
```
kindOf "hello"
```
The above would return `string`. For simple tests (like in `if` blocks), the
`kindIs` function will let you verify that a value is a particular kind:
```
kindIs "int" 123
```
The above will return `true`
## Type Functions
Types are slightly harder to work with, so there are three different functions:
- `typeOf` returns the underlying type of a value: `typeOf $foo`
- `typeIs` is like `kindIs`, but for types: `typeIs "*io.Buffer" $myVal`
- `typeIsLike` works as `typeIs`, except that it also dereferences pointers.
**Note:** None of these can test whether or not something implements a given
interface, since doing so would require compiling the interface in ahead of time.
## deepEqual
`deepEqual` returns true if two values are ["deeply equal"](https://golang.org/pkg/reflect/#DeepEqual)
Works for non-primitive types as well (compared to the built-in `eq`).
```
deepEqual (list 1 2 3) (list 1 2 3)
```
The above will return `true`

View file

@ -1,72 +0,0 @@
# String List Functions
These function operate on or generate slices of strings. In Go, a slice is a
growable array. In Sprig, it's a special case of a `list`.
## join
Join a list of strings into a single string, with the given separator.
```
list "hello" "world" | join "_"
```
The above will produce `hello_world`
`join` will try to convert non-strings to a string value:
```
list 1 2 3 | join "+"
```
The above will produce `1+2+3`
## splitList and split
Split a string into a list of strings:
```
splitList "$" "foo$bar$baz"
```
The above will return `[foo bar baz]`
The older `split` function splits a string into a `dict`. It is designed to make
it easy to use template dot notation for accessing members:
```
$a := split "$" "foo$bar$baz"
```
The above produces a map with index keys. `{_0: foo, _1: bar, _2: baz}`
```
$a._0
```
The above produces `foo`
## splitn
`splitn` function splits a string into a `dict` with `n` keys. It is designed to make
it easy to use template dot notation for accessing members:
```
$a := splitn "$" 2 "foo$bar$baz"
```
The above produces a map with index keys. `{_0: foo, _1: bar$baz}`
```
$a._0
```
The above produces `foo`
## sortAlpha
The `sortAlpha` function sorts a list of strings into alphabetical (lexicographical)
order.
It does _not_ sort in place, but returns a sorted copy of the list, in keeping
with the immutability of lists.

View file

@ -1,309 +0,0 @@
# String Functions
Sprig has a number of string manipulation functions.
## trim
The `trim` function removes space from either side of a string:
```
trim " hello "
```
The above produces `hello`
## trimAll
Remove given characters from the front or back of a string:
```
trimAll "$" "$5.00"
```
The above returns `5.00` (as a string).
## trimSuffix
Trim just the suffix from a string:
```
trimSuffix "-" "hello-"
```
The above returns `hello`
## trimPrefix
Trim just the prefix from a string:
```
trimPrefix "-" "-hello"
```
The above returns `hello`
## upper
Convert the entire string to uppercase:
```
upper "hello"
```
The above returns `HELLO`
## lower
Convert the entire string to lowercase:
```
lower "HELLO"
```
The above returns `hello`
## title
Convert to title case:
```
title "hello world"
```
The above returns `Hello World`
## repeat
Repeat a string multiple times:
```
repeat 3 "hello"
```
The above returns `hellohellohello`
## substr
Get a substring from a string. It takes three parameters:
- start (int)
- end (int)
- string (string)
```
substr 0 5 "hello world"
```
The above returns `hello`
## trunc
Truncate a string (and add no suffix)
```
trunc 5 "hello world"
```
The above produces `hello`.
```
trunc -5 "hello world"
```
The above produces `world`.
## contains
Test to see if one string is contained inside of another:
```
contains "cat" "catch"
```
The above returns `true` because `catch` contains `cat`.
## hasPrefix and hasSuffix
The `hasPrefix` and `hasSuffix` functions test whether a string has a given
prefix or suffix:
```
hasPrefix "cat" "catch"
```
The above returns `true` because `catch` has the prefix `cat`.
## quote and squote
These functions wrap a string in double quotes (`quote`) or single quotes
(`squote`).
## cat
The `cat` function concatenates multiple strings together into one, separating
them with spaces:
```
cat "hello" "beautiful" "world"
```
The above produces `hello beautiful world`
## indent
The `indent` function indents every line in a given string to the specified
indent width. This is useful when aligning multi-line strings:
```
indent 4 $lots_of_text
```
The above will indent every line of text by 4 space characters.
## nindent
The `nindent` function is the same as the indent function, but prepends a new
line to the beginning of the string.
```
nindent 4 $lots_of_text
```
The above will indent every line of text by 4 space characters and add a new
line to the beginning.
## replace
Perform simple string replacement.
It takes three arguments:
- string to replace
- string to replace with
- source string
```
"I Am Henry VIII" | replace " " "-"
```
The above will produce `I-Am-Henry-VIII`
## plural
Pluralize a string.
```
len $fish | plural "one anchovy" "many anchovies"
```
In the above, if the length of the string is 1, the first argument will be
printed (`one anchovy`). Otherwise, the second argument will be printed
(`many anchovies`).
The arguments are:
- singular string
- plural string
- length integer
NOTE: Sprig does not currently support languages with more complex pluralization
rules. And `0` is considered a plural because the English language treats it
as such (`zero anchovies`). The Sprig developers are working on a solution for
better internationalization.
## regexMatch, mustRegexMatch
Returns true if the input string contains any match of the regular expression.
```
regexMatch "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" "test@acme.com"
```
The above produces `true`
`regexMatch` panics if there is a problem and `mustRegexMatch` returns an error to the
template engine if there is a problem.
## regexFindAll, mustRegexFindAll
Returns a slice of all matches of the regular expression in the input string.
The last parameter n determines the number of substrings to return, where -1 means return all matches
```
regexFindAll "[2,4,6,8]" "123456789" -1
```
The above produces `[2 4 6 8]`
`regexFindAll` panics if there is a problem and `mustRegexFindAll` returns an error to the
template engine if there is a problem.
## regexFind, mustRegexFind
Return the first (left most) match of the regular expression in the input string
```
regexFind "[a-zA-Z][1-9]" "abcd1234"
```
The above produces `d1`
`regexFind` panics if there is a problem and `mustRegexFind` returns an error to the
template engine if there is a problem.
## regexReplaceAll, mustRegexReplaceAll
Returns a copy of the input string, replacing matches of the Regexp with the replacement string replacement.
Inside string replacement, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch
```
regexReplaceAll "a(x*)b" "-ab-axxb-" "${1}W"
```
The above produces `-W-xxW-`
`regexReplaceAll` panics if there is a problem and `mustRegexReplaceAll` returns an error to the
template engine if there is a problem.
## regexReplaceAllLiteral, mustRegexReplaceAllLiteral
Returns a copy of the input string, replacing matches of the Regexp with the replacement string replacement
The replacement string is substituted directly, without using Expand
```
regexReplaceAllLiteral "a(x*)b" "-ab-axxb-" "${1}"
```
The above produces `-${1}-${1}-`
`regexReplaceAllLiteral` panics if there is a problem and `mustRegexReplaceAllLiteral` returns an error to the
template engine if there is a problem.
## regexSplit, mustRegexSplit
Slices the input string into substrings separated by the expression and returns a slice of the substrings between those expression matches. The last parameter `n` determines the number of substrings to return, where `-1` means return all matches
```
regexSplit "z+" "pizza" -1
```
The above produces `[pi a]`
`regexSplit` panics if there is a problem and `mustRegexSplit` returns an error to the
template engine if there is a problem.
## regexQuoteMeta
Returns a string that escapes all regular expression metacharacters inside the argument text;
the returned string is a regular expression matching the literal text.
```
regexQuoteMeta "1.2.3"
```
The above produces `1\.2\.3`
## See Also...
The [Conversion Functions](conversion.md) contain functions for converting strings. The [String List Functions](string_slice.md) contains
functions for working with an array of strings.

View file

@ -1,33 +0,0 @@
# URL Functions
## urlParse
Parses string for URL and produces dict with URL parts
```
urlParse "http://admin:secret@server.com:8080/api?list=false#anchor"
```
The above returns a dict, containing URL object:
```yaml
scheme: 'http'
host: 'server.com:8080'
path: '/api'
query: 'list=false'
opaque: nil
fragment: 'anchor'
userinfo: 'admin:secret'
```
For more info, check https://golang.org/pkg/net/url/#URL
## urlJoin
Joins map (produced by `urlParse`) to produce URL string
```
urlJoin (dict "fragment" "fragment" "host" "host:80" "path" "/path" "query" "query" "scheme" "http")
```
The above returns the following string:
```
proto://host:80/path?query#fragment
```

View file

@ -1,9 +0,0 @@
# UUID Functions
Sprig can generate UUID v4 universally unique IDs.
```
uuidv4
```
The above returns a new UUID of the v4 (randomly generated) type.

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View file

@ -94,7 +94,6 @@ nav:
- "Integrations + projects": integrations.md
- "Release notes": releases.md
- "Emojis 🥳 🎉": emojis.md
- "Template Functions": sprig.md
- "Troubleshooting": troubleshooting.md
- "Known issues": known-issues.md
- "Deprecation notices": deprecations.md

View file

@ -11,6 +11,8 @@ import (
// Defines default config settings (excluding limits, see below)
const (
DefaultListenHTTP = ":80"
DefaultConfigFile = "/etc/ntfy/server.yml"
DefaultTemplateDir = "/etc/ntfy/templates"
DefaultCacheDuration = 12 * time.Hour
DefaultCacheBatchTimeout = time.Duration(0)
DefaultKeepaliveInterval = 45 * time.Second // Not too frequently to save battery (Android read timeout used to be 77s!)
@ -173,7 +175,7 @@ type Config struct {
// NewConfig instantiates a default new server config
func NewConfig() *Config {
return &Config{
File: "", // Only used for testing
File: DefaultConfigFile, // Only used for testing
BaseURL: "",
ListenHTTP: DefaultListenHTTP,
ListenHTTPS: "",
@ -196,6 +198,7 @@ func NewConfig() *Config {
AttachmentTotalSizeLimit: DefaultAttachmentTotalSizeLimit,
AttachmentFileSizeLimit: DefaultAttachmentFileSizeLimit,
AttachmentExpiryDuration: DefaultAttachmentExpiryDuration,
TemplateDir: DefaultTemplateDir,
KeepaliveInterval: DefaultKeepaliveInterval,
ManagerInterval: DefaultManagerInterval,
DisallowedTopics: DefaultDisallowedTopics,
@ -258,6 +261,5 @@ func NewConfig() *Config {
WebPushEmailAddress: "",
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
TemplateDir: "",
}
}

View file

@ -126,6 +126,26 @@
# attachment-file-size-limit: "15M"
# attachment-expiry-duration: "3h"
# Template directory for message templates.
#
# When "X-Template: <name>" (aliases: "Template: <name>", "Tpl: <name>") or "?template=<name>" is set, transform the message
# based on one of the built-in pre-defined templates, or on a template defined in the "template-dir" directory.
#
# Template files must have the ".yml" extension and must be formatted as YAML. They may contain "title" and "message" keys,
# which are interpreted as Go templates.
#
# Example template file (e.g. /etc/ntfy/templates/grafana.yml):
# title: |
# {{- if eq .status "firing" }}
# {{ .title | default "Alert firing" }}
# {{- else if eq .status "resolved" }}
# {{ .title | default "Alert resolved" }}
# {{- end }}
# message: |
# {{ .message | trunc 2000 }}
#
# template-dir: "/etc/ntfy/templates"
# If enabled, allow outgoing e-mail notifications via the 'X-Email' header. If this header is set,
# messages will additionally be sent out as e-mail using an external SMTP server.
#

View file

@ -2918,7 +2918,7 @@ func TestServer_MessageTemplate_Range(t *testing.T) {
require.Equal(t, 200, response.Code)
m := toMessage(t, response.Body.String())
require.Equal(t, "Severe URLs:\n- https://severe1.com\n- https://severe2.com\n", m.Message)
require.Equal(t, "Severe URLs:\n- https://severe1.com\n- https://severe2.com", m.Message)
}
func TestServer_MessageTemplate_ExceedMessageSize_TemplatedMessageOK(t *testing.T) {
@ -2971,8 +2971,7 @@ Labels:
Annotations:
- summary = 15m load average too high
Source: localhost:3000/alerting/grafana/NW9oDw-4z/view
Silence: localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter
`, m.Message)
Silence: localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter`, m.Message)
}
func TestServer_MessageTemplate_GitHub(t *testing.T) {
@ -3073,18 +3072,75 @@ func TestServer_MessageTemplate_UnsafeSprigFunctions(t *testing.T) {
var (
//go:embed testdata/webhook_github_comment_created.json
githubCommentCreatedJSON string
//go:embed testdata/webhook_github_issue_opened.json
githubIssueOpenedJSON string
)
func TestServer_MessageTemplate_FromNamedTemplate(t *testing.T) {
func TestServer_MessageTemplate_FromNamedTemplate_GitHubCommentCreated(t *testing.T) {
t.Parallel()
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "POST", "/mytopic", githubCommentCreatedJSON, map[string]string{
"Template": "github",
})
response := request(t, s, "POST", "/mytopic?template=github", githubCommentCreatedJSON, nil)
require.Equal(t, 200, response.Code)
m := toMessage(t, response.Body.String())
require.Equal(t, "💬 New comment on issue #1389 — instant alerts without Pull to refresh", m.Title)
require.Equal(t, "💬 New comment on issue #1389 — instant alerts without Pull to refresh", m.Message)
require.Equal(t, "💬 [ntfy] New comment on issue #1389 instant alerts without Pull to refresh", m.Title)
require.Equal(t, `Commenter: https://github.com/wunter8
Repository: https://github.com/binwiederhier/ntfy
Comment link: https://github.com/binwiederhier/ntfy/issues/1389#issuecomment-3078214289
Comment:
These are the things you need to do to get iOS push notifications to work:
1. open a browser to the web app of your ntfy instance and copy the URL (including "http://" or "https://", your domain or IP address, and any ports, and excluding any trailing slashes)
2. put the URL you copied in the ntfy `+"`"+`base-url`+"`"+` config in server.yml or NTFY_BASE_URL in env variables
3. put the URL you copied in the default server URL setting in the iOS ntfy app
4. set `+"`"+`upstream-base-url`+"`"+` in server.yml or NTFY_UPSTREAM_BASE_URL in env variables to "https://ntfy.sh" (without a trailing slash)`, m.Message)
}
func TestServer_MessageTemplate_FromNamedTemplate_GitHubIssueOpened(t *testing.T) {
t.Parallel()
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "POST", "/mytopic?template=github", githubIssueOpenedJSON, nil)
require.Equal(t, 200, response.Code)
m := toMessage(t, response.Body.String())
require.Equal(t, "🐛 [ntfy] Issue opened: #1391 http 500 error (ntfy error 50001)", m.Title)
require.Equal(t, `Opened by: https://github.com/TheUser-dev
Repository: https://github.com/binwiederhier/ntfy
Issue link: https://github.com/binwiederhier/ntfy/issues/1391
Labels: 🪲 bug
Description:
:lady_beetle: **Describe the bug**
When sending a notification (especially when it happens with multiple requests) this error occurs
:computer: **Components impacted**
ntfy server 2.13.0 in docker, debian 12 arm64
:bulb: **Screenshots and/or logs**
`+"```"+`
closed with HTTP 500 (ntfy error 50001) (error=database table is locked, http_method=POST, http_path=/_matrix/push/v1/notify, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=30, visitor_id=ip:<edited>, visitor_ip=<edited>, visitor_messages=448, visitor_messages_limit=17280, visitor_messages_remaining=16832, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=57.049697891799994, visitor_seen=2025-07-16T15:06:35.429Z)
`+"```"+`
:crystal_ball: **Additional context**
Looks like this has already been fixed by #498, regression?`, m.Message)
}
func TestServer_MessageTemplate_FromNamedTemplate_GitHubIssueOpened_OverrideConfigTemplate(t *testing.T) {
t.Parallel()
c := newTestConfig(t)
c.TemplateDir = t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(c.TemplateDir, "github.yml"), []byte(`
title: |
Custom title: action={{ .action }} trunctitle={{ .issue.title | trunc 10 }}
message: |
Custom message {{ .issue.number }}
`), 0644))
s := newTestServer(t, c)
response := request(t, s, "POST", "/mytopic?template=github", githubIssueOpenedJSON, nil)
fmt.Println(response.Body.String())
require.Equal(t, 200, response.Code)
m := toMessage(t, response.Body.String())
require.Equal(t, "Custom title: action=opened trunctitle=http 500 e", m.Title)
require.Equal(t, "Custom message 1391", m.Message)
}
func newTestConfig(t *testing.T) *Config {
@ -3093,6 +3149,7 @@ func newTestConfig(t *testing.T) *Config {
conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
conf.CacheStartupQueries = "pragma journal_mode = WAL; pragma synchronous = normal; pragma temp_store = memory;"
conf.AttachmentCacheDir = t.TempDir()
conf.TemplateDir = t.TempDir()
return conf
}

View file

@ -0,0 +1,29 @@
title: |
{{- if eq .status "firing" }}
🚨 Alert: {{ (first .alerts).labels.alertname }}
{{- else if eq .status "resolved" }}
✅ Resolved: {{ (first .alerts).labels.alertname }}
{{- else }}
{{ fail "Unsupported Alertmanager status." }}
{{- end }}
message: |
Status: {{ .status | title }}
Receiver: {{ .receiver }}
{{- range .alerts }}
Alert: {{ .labels.alertname }}
Instance: {{ .labels.instance }}
Severity: {{ .labels.severity }}
Starts at: {{ .startsAt }}
{{- if .endsAt }}Ends at: {{ .endsAt }}{{ end }}
{{- if .annotations.summary }}
Summary: {{ .annotations.summary }}
{{- end }}
{{- if .annotations.description }}
Description: {{ .annotations.description }}
{{- end }}
Source: {{ .generatorURL }}
{{ end }}

View file

@ -6,7 +6,7 @@ title: |
👀 {{ .sender.login }} started watching {{ .repository.name }}
{{- else if and .comment (eq .action "created") }}
💬 New comment on #{{ .issue.number }}: {{ .issue.title }}
💬 New comment on issue #{{ .issue.number }} {{ .issue.title }}
{{- else if .pull_request }}
🔀 Pull request {{ .action }}: #{{ .pull_request.number }} {{ .pull_request.title }}
@ -47,6 +47,7 @@ message: |
{{ .action | title }} by: {{ .issue.user.html_url }}
Repository: {{ .repository.html_url }}
Issue link: {{ .issue.html_url }}
{{ if .issue.labels }}Labels: {{ range .issue.labels }}{{ .name }} {{ end }}{{ end }}
{{ if .issue.body }}
Description:
{{ .issue.body | trunc 2000 }}{{ end }}

View file

@ -1,9 +1,11 @@
message: |
{{if .alerts}}
{{.alerts | len}} alert(s) triggered
{{else}}
No alerts triggered.
{{end}}
title: |
⚠️ Grafana alert: {{.title}}
{{- if eq .status "firing" }}
🚨 {{ .title | default "Alert firing" }}
{{- else if eq .status "resolved" }}
✅ {{ .title | default "Alert resolved" }}
{{- else }}
⚠️ Unknown alert: {{ .title | default "Alert" }}
{{- end }}
message: |
{{ .message | trunc 2000 }}

View file

@ -0,0 +1,33 @@
{
"version": "4",
"groupKey": "...",
"status": "firing",
"receiver": "webhook-receiver",
"groupLabels": {
"alertname": "HighCPUUsage"
},
"commonLabels": {
"alertname": "HighCPUUsage",
"instance": "server01",
"severity": "critical"
},
"commonAnnotations": {
"summary": "High CPU usage detected"
},
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "HighCPUUsage",
"instance": "server01",
"severity": "critical"
},
"annotations": {
"summary": "High CPU usage detected"
},
"startsAt": "2025-07-17T07:00:00Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://prometheus.local/graph?g0.expr=..."
}
]
}

View file

@ -0,0 +1,51 @@
{
"receiver": "ntfy\\.example\\.com/alerts",
"status": "resolved",
"alerts": [
{
"status": "resolved",
"labels": {
"alertname": "Load avg 15m too high",
"grafana_folder": "Node alerts",
"instance": "10.108.0.2:9100",
"job": "node-exporter"
},
"annotations": {
"summary": "15m load average too high"
},
"startsAt": "2024-03-15T02:28:00Z",
"endsAt": "2024-03-15T02:42:00Z",
"generatorURL": "localhost:3000/alerting/grafana/NW9oDw-4z/view",
"fingerprint": "becbfb94bd81ef48",
"silenceURL": "localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DLoad+avg+15m+too+high&matcher=grafana_folder%3DNode+alerts&matcher=instance%3D10.108.0.2%3A9100&matcher=job%3Dnode-exporter",
"dashboardURL": "",
"panelURL": "",
"values": {
"B": 18.98211314475876,
"C": 0
},
"valueString": "[ var='B' labels={__name__=node_load15, instance=10.108.0.2:9100, job=node-exporter} value=18.98211314475876 ], [ var='C' labels={__name__=node_load15, instance=10.108.0.2:9100, job=node-exporter} value=0 ]"
}
],
"groupLabels": {
"alertname": "Load avg 15m too high",
"grafana_folder": "Node alerts"
},
"commonLabels": {
"alertname": "Load avg 15m too high",
"grafana_folder": "Node alerts",
"instance": "10.108.0.2:9100",
"job": "node-exporter"
},
"commonAnnotations": {
"summary": "15m load average too high"
},
"externalURL": "localhost:3000/",
"version": "1",
"groupKey": "{}:{alertname=\"Load avg 15m too high\", grafana_folder=\"Node alerts\"}",
"truncatedAlerts": 0,
"orgId": 1,
"title": "[RESOLVED] Load avg 15m too high Node alerts (10.108.0.2:9100 node-exporter)",
"state": "ok",
"message": "**Resolved**\n\nValue: B=18.98211314475876, C=0\nLabels:\n - alertname = Load avg 15m too high\n - grafana_folder = Node alerts\n - instance = 10.108.0.2:9100\n - job = node-exporter\n"
}