Skip to content

Examples ​

Practical templates for the formats Tsuku is used for most: CSV and XML feeds, JSON responses, HTML fragments and emails, configuration files and e-commerce documents. Every example on these pages is real: the template, the data and the output are files, and the output is produced by running the template with Tsuku 1.4.

How the examples work ​

Each example has four tabs:

TabWhat it shows
TemplateThe template file, template.<format>.tsuku, exactly as Tsuku reads it
Datadata.php, which returns the $data array
OutputWhat the template renders with that data - generated, never edited by hand
PHPThe call: echo $tsuku->process($template, $data);, plus any function the example registers

Run one yourself with a script like this:

php
<?php

use Qoliber\Tsuku\Tsuku;

require 'vendor/autoload.php';

$tsuku = new Tsuku();
$template = file_get_contents('template.csv.tsuku'); // the Template tab
$data = require 'data.php';                          // the Data tab

echo $tsuku->process($template, $data);              // prints the Output tab

Dates are formatted in UTC. The sources of all examples are in snippets/examples of the documentation repository; scripts/render-examples.php renders them, and the site is not published when an output no longer matches its template.

Example pages ​

CSV Exports → ​

A product catalog with escaping and line continuation, a Magento import file, orders with line items, a stock report, optional columns, a semicolon-separated file and an export that is safe to open in Excel.

XML Generation → ​

A Google Shopping feed, a sitemap with language alternates, an RSS feed with CDATA content, a SOAP request, an SVG chart and a feed streamed row by row.

JSON APIs → ​

A paginated product list, an order webhook, JSON-LD product data, a validation error response and a nested category tree.

HTML Generation → ​

A product grid, customer reviews, an order history table, an order confirmation email and a checkout form - each with a rendered preview.

Configuration Files → ​

Application settings in YAML, Docker Compose, a Kubernetes deployment, a .env file, an nginx server block and a Cargo.toml.

E-commerce → ​

A fixed-width invoice, a shipping label, a marketplace feed, an abandoned-cart email and a price list per customer group.

Quick examples ​

CSV export ​

@csv() quotes a value only when it needs it: Wholesale, EU contains a comma. @?{...} turns a boolean into yes / no.

tsuku
email,firstname,lastname,group,newsletter
@for(customers as customer)
@csv(customer.email),@csv(customer.firstname),@csv(customer.lastname),@csv(customer.group),@?{customer.subscribed "yes" : "no"}
@end
php
<?php

return [
    'customers' => [
        ['email' => '[email protected]', 'firstname' => 'Anna', 'lastname' => 'de Vries', 'group' => 'Retail', 'subscribed' => true],
        ['email' => '[email protected]', 'firstname' => 'Lena', 'lastname' => 'Lindner', 'group' => 'Wholesale, EU', 'subscribed' => false],
    ],
];
csv
email,firstname,lastname,group,newsletter
[email protected],Anna,de Vries,Retail,yes
[email protected],Lena,Lindner,"Wholesale, EU",no
php
echo $tsuku->process($template, $data);

XML product feed ​

@xml() escapes the & in the product name. The @match starts after <stock> and its @end is followed by </stock>, so it is an inline block: the line breaks and indentation around each case are dropped and the value lands between the tags.

tsuku-xml
<?xml version="1.0" encoding="UTF-8"?>
<products>
  @for(products as product)
  <product sku="@xml(product.sku)">
    <name>@xml(product.name)</name>
    <price currency="EUR">@number(product.price, 2, ".", "")</price>
    <stock>@match(product.qty)
      @case(0)
        out_of_stock
      @default
        in_stock
    @end</stock>
  </product>
  @end
</products>
php
<?php

return [
    'products' => [
        ['sku' => 'TEA-KYU-350', 'name' => 'Kyusu Teapot 350 ml', 'price' => 49.00, 'qty' => 12],
        ['sku' => 'TEA-MAT-SET', 'name' => 'Matcha Whisk & Bowl Set', 'price' => 29.90, 'qty' => 0],
    ],
];
xml
<?xml version="1.0" encoding="UTF-8"?>
<products>
  <product sku="TEA-KYU-350">
    <name>Kyusu Teapot 350 ml</name>
    <price currency="EUR">49.00</price>
    <stock>in_stock</stock>
  </product>
  <product sku="TEA-MAT-SET">
    <name>Matcha Whisk &amp; Bowl Set</name>
    <price currency="EUR">29.90</price>
    <stock>out_of_stock</stock>
  </product>
</products>
php
echo $tsuku->process($template, $data);

JSON response ​

@json() prints a complete JSON string, quotes included, so it is not wrapped in "...". The array uses a separator in front of every item except the first (@if(index > 0), @end), because JSON allows no trailing comma.

tsuku-json
{
  "id": {customer.id},
  "name": @json(customer.name),
  "email": @json(customer.email),
  "newsletter": @?{customer.subscribed "true" : "false"},
  "groups": [@for(customer.groups as group, index)@if(index > 0), @end@json(group)@end]
}
php
<?php

return [
    'customer' => [
        'id' => 4711,
        'name' => 'Anna "Annie" de Vries',
        'email' => '[email protected]',
        'subscribed' => true,
        'groups' => ['Retail', 'Tea Club'],
    ],
];
json
{
  "id": 4711,
  "name": "Anna \u0022Annie\u0022 de Vries",
  "email": "[email protected]",
  "newsletter": true,
  "groups": ["Retail", "Tea Club"]
}
php
echo $tsuku->process($template, $data);

Indented conditions ​

Lines that hold only directives print nothing - not even their indentation - so directives can be indented like the text they control. Each content line keeps its own indentation. See Whitespace Control.

tsuku
@for(orders as order)
Order #{order.increment_id}
  @if(order.status == "complete")
  Delivered on @date("j M", order.delivered_at)
  @elseif(order.tracking)
  On its way, tracking number {order.tracking}
  @else
  Being packed
  @end
@end
php
<?php

return [
    'orders' => [
        ['increment_id' => '000002154', 'status' => 'complete', 'delivered_at' => strtotime('2026-10-02'), 'tracking' => 'JVGL0611112222'],
        ['increment_id' => '000002203', 'status' => 'processing', 'tracking' => 'JVGL0612345678'],
        ['increment_id' => '000002207', 'status' => 'processing', 'tracking' => null],
    ],
];
txt
Order #000002154
  Delivered on 2 Oct
Order #000002203
  On its way, tracking number JVGL0612345678
Order #000002207
  Being packed
php
echo $tsuku->process($template, $data);

A registered function ​

Formatting that the built-in functions do not cover goes into a PHP function registered under a name; the template calls it like any other function. See Custom Functions.

tsuku
@for(orders as order)
#{order.increment_id}  @money(order.grand_total, order.currency)
@end
php
<?php

return [
    'orders' => [
        ['increment_id' => '000002203', 'grand_total' => 1108.70, 'currency' => 'EUR'],
        ['increment_id' => '000002204', 'grand_total' => 29.90, 'currency' => 'GBP'],
        ['increment_id' => '000002205', 'grand_total' => 64.00, 'currency' => 'CHF'],
    ],
];
txt
#000002203  1.108,70 €
#000002204  £29.90
#000002205  CHF 64.00
php
<?php

$tsuku->registerFunction('money', static fn (mixed $amount, string $currency = 'EUR'): string => match ($currency) {
    'EUR' => number_format((float) $amount, 2, ',', '.') . ' €',
    'GBP' => '£' . number_format((float) $amount, 2),
    default => $currency . ' ' . number_format((float) $amount, 2),
});

echo $tsuku->process($template, $data);

Using Tsuku in an application ​

Keep templates in files next to the code that fills them, and build plain arrays in PHP: the template only places values, it does not query anything.

Magento 2 ​

php
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Qoliber\Tsuku\Tsuku;

class ProductCsvExport
{
    public function __construct(
        private readonly Tsuku $tsuku,
        private readonly CollectionFactory $productCollectionFactory,
    ) {
    }

    public function export(): string
    {
        $collection = $this->productCollectionFactory->create()
            ->addAttributeToSelect(['name', 'description', 'price'])
            ->addAttributeToFilter('status', 1);

        $products = [];
        foreach ($collection as $product) {
            $products[] = [
                'sku' => $product->getSku(),
                'name' => $product->getName(),
                'description' => $product->getDescription(),
                'price' => (float) $product->getPrice(),
                'qty' => (int) $product->getExtensionAttributes()->getStockItem()?->getQty(),
            ];
        }

        // the product catalog template from the CSV Exports page
        $template = file_get_contents(__DIR__ . '/templates/products.csv.tsuku');

        return $this->tsuku->process($template, ['products' => $products]);
    }
}

The template is the product catalog from the CSV page. For catalogs with many thousands of products, render with processToStream() instead.

Laravel ​

php
use Illuminate\Http\Response;
use Qoliber\Tsuku\Tsuku;

class SitemapController
{
    public function __invoke(Tsuku $tsuku): Response
    {
        $pages = Page::query()->where('published', true)->get()
            ->map(fn (Page $page) => [
                'loc' => url($page->path),
                'updated_at' => $page->updated_at->getTimestamp(),
                'alternates' => $page->translations->pluck('url', 'locale')->all(),
            ])
            ->all();

        $xml = $tsuku->process(file_get_contents(resource_path('feeds/sitemap.xml.tsuku')), ['pages' => $pages]);

        return response($xml, 200, ['Content-Type' => 'application/xml; charset=UTF-8']);
    }
}

The template is the sitemap from the XML page.

Next Steps ​

Released under the MIT License.