Migration from Sprig
Coming from Sprig and looking to use Sprout? You're in the right place for a complete guide on making the transition. This guideline will help you navigate the differences.
Introduction
Sprout is a modern templating engine inspired by Sprig but with enhanced features and a more modular approach. Migrating from Sprig to Sprout involves understanding these differences and adjusting your templates and code accordingly.
Key Differences
1. Registry System
Sprig: Functions are globally available.
Sprout: Functions are grouped into registries for modularity. Each registry can be added to the handler as needed.
2. Handler and Function Management
Sprig: Functions are accessed directly.
Sprout: Functions are managed by a handler, allowing for better control over function availability, error handling, and logging.
Migration Tip
Nothing to do, using the new handler is enough.
3. Error Handling
Sprig: Limited and inconsistent error handling, with some functions causing panics (see Panicking Functions), and not fully adhering to Go template standards.
Sprout: Offers configurable error handling strategies, including returning default values, or return error to stop template generation (default), providing a more consistent and flexible approach.
Migration Tip
You can learn more about the safe strategy here: Safe Functions
4. Function Aliases
Sprig: Functions are accessed by a single name, deprecated functions are duplicated in code.
Sprout: Supports function aliases, allowing multiple names for the same function.
5. Following the Go Template Convention
Sprig: No error for naming convention are followed.
Sprout: Design to adhere closely to Go's native template conventions, ensuring compatibility and a consistent experience for developers familiar with Go templates. This adherence helps reduce surprises and ensures that templates behave as expected according to Go's standard practices.
How to Transition
Choose your migration path based on your situation:
Option A: New Projects or Breaking Changes Acceptable
If you're starting a new project or your users accept breaking changes, use Sprout directly:
This gives you all the improvements (bug fixes, proper piping support, better error handling) without any compatibility overhead.
Option B: Non-Breaking Migration for End-Users
If you have existing end-users and need a gradual migration path:
Phase 1: Replace Sprig with Sprigin
Sprigin provides full backward compatibility while logging deprecation warnings. Your end-users will see warnings in logs about deprecated functions and signature changes, giving them time to update their templates.
Customizing the Logger
By default, Sprigin logs warnings to the standard slog default handler. You can provide your own logger to integrate with your application's logging system, using the *With variant of any entrypoint:
This is useful when you want to:
Route deprecation warnings to a specific log destination
Use a structured logging format (JSON, etc.)
Filter or aggregate warnings in your logging infrastructure
Every entrypoint has such a variant:
FuncMap()
FuncMapWith(opts...)
TxtFuncMap()
TxtFuncMapWith(opts...)
HtmlFuncMap()
HtmlFuncMapWith(opts...)
GenericFuncMap()
GenericFuncMapWith(opts...)
HermeticTxtFuncMap()
HermeticTxtFuncMapWith(opts...)
HermeticHtmlFuncMap()
HermeticHtmlFuncMapWith(opts...)
The original functions keep their exact signature, so upgrading Sprigin is never a breaking change. If an option is invalid (a nil logger, for instance) it is reported through the handler logger and skipped, and the function map is still built.
Phase 2: Keep Sprigin for X Versions/Months
Maintain sprigin for your defined deprecation period (e.g., 3-6 months or 2-3 versions) to:
Respect your breaking change policy
Allow end-users to see and act on deprecation warnings
Ensure a smooth transition
Phase 3: Switch to Sprout
Once your deprecation period ends, replace sprigin with sprout:
How to Transition for your end-users
You use Sprig or Sprout for end-users and want to migrate? Here's a detailed plan to ensure confidence during the migration:
The Sprigin Compatibility Layer
The sprigin package is specifically designed for this use case. It:
Supports both signatures: Automatically detects whether you're using the old Sprig signature (
get $dict "key") or the new Sprout piping signature ($dict | get "key")Logs deprecation warnings: When old signatures or deprecated functions are used, warnings are logged to inform your end-users (see Customizing the Logger to integrate with your logging system)
Preserves Sprig behavior: Bug-for-bug compatible to avoid breaking existing templates
Migration Steps
Communicate the Purpose of the Migration Explain the reasons for switching to Sprout, emphasizing improvements such as better performance, modular function registries, enhanced error handling, and new features like function notices and safe functions.
Replace Sprig with Sprigin Simply replace your import and function call:
Monitor Deprecation Warnings Sprigin logs warnings for deprecated functions and signature changes. Share these logs with your end-users so they can update their templates. Use
sprigin.FuncMapWith(sprigin.WithLogger(...))to route warnings to your preferred logging destination.Keep Sprigin for X Versions/Months Maintain the compatibility layer according to your breaking change policy. We recommend 3-6 months or 2-3 versions.
Final Switch to Sprout Once your deprecation period ends and end-users have migrated their templates, switch to Sprout directly.
Our maintainers and collaborators can assist you if you have questions. Don't hesitate to open a discussion on GitHub!
Migrating Common Functions
Many functions in Sprig have direct equivalents in Sprout, but they might be organized differently or require registration in a handler.
Example: Simple Function Migration
Sprig:
Sprout:
Example: Using Aliases
Sprig:
Sprout:
You can continue to use the same function name inside your template
Panicking Functions
In Sprig, errors within certain functions cause a panic. Both Sprout and Sprigin fix all panics, returning errors properly instead.
Old Behavior (Sprig): Triggers a panic on error
New Behavior (Sprout & Sprigin): Returns nil or an empty value on error
Migration Tip
Whether you use sprout or sprigin, all panics are fixed. You can safely migrate without worrying about panics crashing your application.
Methods that previously caused a panic in Sprig:
DeepCopy
MustDeepCopy
ToRawJson
Append
Prepend
Concat
Chunk
Uniq
Compact
Slice
Without
Rest
Initial
Reverse
First
Last
Has
Dig
RandAlphaNumeric
RandAlpha
RandAscii
RandNumeric
RandBytes
Function-Specific Changes
Signature Changes (Argument Order)
Sprout reorders function arguments to support Go template piping conventions. The target (map/list) is now the last argument instead of the first.
Breaking in v1.1.0. Up to v1.0.x, calling these functions with the Sprig argument order still worked in Sprout itself: the old order was detected, silently reordered, and a deprecation warning was logged. That tolerance is removed in v1.1.0. The following functions now fail instead of warning:
get, set, unset, hasKey, pick, omit, append, prepend, slice, without
If you upgrade from v1.0.x and your templates still use the Sprig order, update them with the tables below before bumping. Nothing changes if you already use the pipe syntax.
Map Functions: get, set, unset, hasKey, pick, omit
get
{{ get $dict "key" }}
{{ $dict | get "key" }}
set
{{ set $dict "key" "value" }}
{{ $dict | set "key" "value" }}
unset
{{ unset $dict "key" }}
{{ $dict | unset "key" }}
hasKey
{{ hasKey $dict "key" }}
{{ $dict | hasKey "key" }}
pick
{{ pick $dict "k1" "k2" }}
{{ $dict | pick "k1" "k2" }}
omit
{{ omit $dict "k1" "k2" }}
{{ $dict | omit "k1" "k2" }}
List Functions: append, prepend, slice, without
append
{{ $list = append $list "value" }}
{{ $list = $list | append "value" }}
prepend
{{ $list = prepend $list "value" }}
{{ $list = $list | prepend "value" }}
slice
{{ slice $list 1 3 }}
{{ $list | slice 1 3 }}
without
{{ without $list "a" "b" }}
{{ $list | without "a" "b" }}
append and prepend return a new list, they never modify the one they receive. Accumulating in a range therefore requires assigning the result back, in Sprig as well as in Sprout. Only the position of the arguments changes:
Dropping the = leaves $stages empty and prints the intermediate lists into the output:
Dig Function
Sprig:
{{ dig "key1" "key2" "default" $dict }}- default value is the second-to-last argumentSprout:
{{ $dict | dig "key1" "key2" | default "default" }}- usedefaultfilter separately
Additional differences:
Sprig: Dots in keys are treated literally (
dig "a.b"looks for key"a.b")Sprout: Keys are split on dots (
dig "a.b"is equivalent todig "a" "b")
Behavior Changes
MustDeepCopy
Sprig: Accepts
nilinput, causing an internal panic.Sprout: Returns
nilif input isnil, avoiding panic.
Rand Functions
Sprig: Causes an internal panic if the length parameter is zero.
Sprout: Returns an empty string if the length is zero, ensuring stability.
DateAgo
Sprig: Does not support int32 and *time.Time; returns "0s".
Sprout: Supports int32 and *time.Time and returns the correct duration.
DateRound
Sprig: Returns a corrected duration in positive form, even for negative inputs.
Sprout: Accurately returns the duration, preserving the sign of the input.
Date
Sprig: Uses local timezone for formatting.
Sprout: Uses the timezone from the time value itself.
Base32Decode / Base64Decode
Sprig: Returns the error message string when decoding fails.
Sprout: Returns an empty string when decoding fails.
ToCamelCase / ToPascalCase
Sprig: The
camelcasefunction returns PascalCase (e.g.,"hello_world"→"HelloWorld"). No true camelCase function exists.Sprout:
toCamelCasereturns true camelCase (e.g.,"hello_world"→"helloWorld")toPascalCasereturns PascalCase (e.g.,"hello_world"→"HelloWorld")The alias
camelcasepoints totoPascalCasefor backward compatibility.
ToTitleCase / Title
Sprig: Uses the deprecated
strings.Titlefunction which:Doesn't lowercase letters before capitalizing (
"HELLO"stays"HELLO")Treats apostrophes as word separators (
"it's"becomes"It'S")
Sprout: Uses proper Unicode title casing (
"HELLO"→"Hello","it's"→"It's")
Substr
Sprig:
substr 0 0 "hello"returns""(empty string)Sprout:
substr 0 0 "hello"returns"hello"(full string, as end=0 means "to the end")
KindOf
Sprig:
kindOf nilreturns"invalid"Sprout:
kindOf nilreturns an error
EllipsisBoth / Abbrevboth
Sprig: Has a bug where offset <= 4 is ignored (truncates from right only)
Sprout: Respects the offset parameter correctly
Nospace / Ellipsis (Abbrev)
Sprig: Has bugs with Unicode characters (e.g.,
nospace "α β γ")Sprout: Correctly handles Unicode characters
Merge / MergeOverwrite
Sprig: Dereferences when the second value is the default Go value (e.g.,
0for int is treated as "not set").Sprout: Does not dereference; keeps the second value as-is (e.g.,
0for int is preserved).
Deprecated Features
Functions to be Removed
The following functions are marked with // ! DEPRECATED and will be removed in the next major version:
fail
Limited use case
Use Go error handling
urlParse
Moving to dedicated URL package
Will be replaced
urlJoin
Moving to dedicated URL package
Will be replaced
getHostByName
Non-deterministic, security concern
Handle DNS outside templates
Perform cryptographic operations (listed in crypto package) outside of templates. The crypto registry will be dropped in future versions.
Renamed Functions (Deprecated Aliases)
The following function names are deprecated. They still work but will log deprecation warnings. Use the new names instead:
String Functions
upper, toupper, uppercase
toUpper
lower, tolower, lowercase
toLower
title, titlecase
toTitleCase
camelcase
toPascalCase
snake, snakecase
toSnakeCase
kebab, kebabcase
toKebabCase
swapcase
swapCase
abbrev
ellipsis
abbrevboth
ellipsisBoth
trimall
trimAll
List Functions
push
append
mustPush
mustAppend
tuple
list
biggest
max
Encoding Functions
b64enc
base64Encode
b64dec
base64Decode
b32enc
base32Encode
b32dec
base32Decode
Path Functions
base
pathBase
dir
pathDir
ext
pathExt
clean
pathClean
isAbs
pathIsAbs
Date Functions
date_modify
dateModify
date_in_zone
dateInZone
must_date_modify
mustDateModify
ago
dateAgo
Type Conversion Functions
int, atoi
toInt
int64
toInt64
float64
toFloat64
toDecimal
toOctal
toStrings
strSlice
Math Functions
addf
add
add1f
add1
subf
sub
Other Functions
expandenv
expandEnv
Conclusion
Migrating from Sprig to Sprout offers significant benefits, including improved error handling, modular function management, and enhanced compatibility with modern Go practices. While the sprigin package provides a bridge for backward compatibility, fully embracing Sprout’s native capabilities will lead to a more stable and maintainable codebase. For further details on Sprout’s features and API, consult the official Sprout documentation.
Last updated