Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Url Template Laravel Package

innmind/url-template

A PHP implementation of RFC 6570 URI Templates for expanding URL templates with variables. Build type-safe URLs using simple and complex expressions, with a clean API that fits the Innmind ecosystem and simplifies dynamic link generation.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require innmind/url-template

The package is a small, dependency-light RFC 6570 (URI Template) implementation that plugs into the rest of the Innmind ecosystem (innmind/url for the result object, innmind/immutable for the variable bag). It ships no Laravel service provider, config, or facade — you wire it yourself, which is usually five lines.

Read in this order:

  1. README.md — the supported operators table ({var}, {+var}, {#var}, {?var}, {&var}, {.var}, {/var}, {;var}) and the list/explode modifiers.
  2. src/Template.php — the only entry point you normally need.
  3. The expression/expansion classes under src/ — this is where {?a,b} vs {?a*,b} behaviour lives, and it is the file you open when an expansion surprises you.

First use case: building third-party API URLs where path segments and query parameters are optional, exactly like an OpenAPI servers/paths block.

use Innmind\UrlTemplate\Template;
use Innmind\Immutable\Map;

$template = Template::of('https://api.example.com/repos{/owner,repo}{?per_page,page}');

$url = $template->expand(Map::of(
    'owner', 'Innmind',
    'repo', 'UrlTemplate',
    'per_page', '100',
));

echo $url->toString();
// https://api.example.com/repos/Innmind/UrlTemplate?per_page=100

Depending on the version you pin, Template::of() and/or expand() are Maybe-returning (older releases use Template::load(Str $template)). Check the signature once, then hide it behind your own factory so the rest of the codebase never sees Maybe.

Implementation Patterns

Parse once, expand many. Parsing is the only non-trivial cost; expansion is cheap. Put templates in config and resolve them to Template instances in a container binding or a small registry, not inside the request path on every call.

// config/services.php
'github' => [
    'repo' => 'https://api.github.com/repos{/owner,repo}{?per_page}',
],
final class Endpoint
{
    /** @param Map<string, string> $variables */
    public function __construct(
        private Template $template,
        private Map $variables,
    ) {}

    public function url(): string
    {
        return $this->template->expand($this->variables)->toString();
    }
}

Keep variable bags immutable and complete. Innmind's Map is the natural value type here; build it from a validated DTO or a form request rather than passing request()->all() straight in. Because Map::of() takes flat key/value pairs, a typed wrapper with a Map getter keeps call sites readable.

Use it as a URL builder, not a string builder. The winning integration is: template → expanded Url → your HTTP client (innmind/http, Guzzle, or Laravel's Http via (string) $url). Doing it this way means the same object is used for signing, logging, and retries, and you never hand-concatenate ? and &.

Partial expansion for pagination and re-templating. Expanding a subset of variables leaves the rest as literal {...} expressions in most implementations — useful for "render this template with the knowns, keep {?cursor} for the client". Verify this against your pinned version before relying on it; if it isn't supported, expand with the cursor omitted and treat the base URL as the reusable piece.

Data-driven tests. Port a handful of rows from the official uritemplate-test suite into a PHPUnit data provider. It is the fastest way to lock in operator behaviour and it catches regressions when you bump a major version — Innmind does release majors.

Cache-aware expansion. Expansion order follows the template, so identical inputs produce identical strings. That makes the output safe to use as a cache key component or to sign, as long as you never reorder variables in the template itself.

Gotchas and Tips

  • {var} vs {+var} is the classic footgun. Simple expansion percent-encodes reserved characters, so a value containing / or : becomes %2F. Use {+var} when the variable is itself a path or a full URL; use {var} for opaque identifiers. Getting this backwards produces 404s that look like routing bugs.
  • Undefined variables expand to the empty string, silently. There is no "missing required variable" error by default. If a variable is mandatory in your domain, validate it before expanding — don't let a missing tenant ID become a request to /.
  • {?a} assumes no query string exists yet; {&a} appends. If your base template already carries a ?, starting a new expression with ? gives you a second ?. Reserve ? for the first expression and & for subsequent ones.
  • Modifiers multiply complexity. * (explode) and :3 (prefix) change the shape of the value you must supply — lists and maps need Sequence/Map values, not strings. Start with scalars, and only reach for modifiers when a real API demands it.
  • Type strictness. Values are expected to be strings or Innmind value objects; passing an int from request()->integer() can blow up. Cast at the boundary ('per_page' => (string) $perPage).
  • Map collapses duplicate keys and preserves insertion order; Sequence does not dedupe. When you build the variable bag from user input, duplicates are already gone by the time the template sees them — that's usually what you want, but don't rely on it for ordered list expansion.
  • Don't ->unwrap() in request paths. Maybe/Attempt returns are there because a malformed template or bad expansion is a programming error, not a user error. Convert once at boot (fail fast) and pass the concrete Template/Url around.
  • No extension points worth using. The classes are small and largely final; wrap rather than extend. Your own UrlBuilder/Endpoint type is the seam to hang logging, metrics, or required-variable validation on.
  • Pin the version and read the source. With a tiny user base there is no Stack Overflow coverage, but the code is a few hundred lines and readable in an afternoon — that is genuinely faster than guessing. Given the recent release cadence, use a tight constraint and skim the changelog on every bump.
  • Double-encoding. If a template literal already contains percent-encoded characters, expansion will encode them again. Keep the static part of the template plain and let the expander do the encoding, or you'll chase %2520 style bugs.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib