Dynamic date in the text block

I occasionally have texts with dynamic years, e.g:

  • “Our company has been in existence for 15 years.”
  • “Max Müller has been working for our company for 6 years.”

The year should adjust automatically every year. And also by month. Because if Max Müller started at the company in August, he must not have been with the company for 6 years since January. A date with month & year must therefore be stored.

But how do I realize this in a text block? Or what other solution would there be for this?

I have currently split such text blocks into 3 parts and (rather awkwardly) glued them together again in the template:

<?= $page->text1()->kt() ?>
$firstDate  = new DateTime($page->gruendungsjahr()); 
$secondDate = new DateTime(date("Y-m-d")); 
$intvl = $firstDate->diff($secondDate); 
echo $intvl->y ?>
<?= $page->text2()->kt() ?>

Many thanks for your tips. :ok_hand:

You could use a placeholder with for example the start date inside your text, e.g. {{ 04/1999 }}, then replace such string templates with relative dates when rendering.

See also: The power of placeholders | Kirby CMS

You could also use strings that refer to fields, for example

Our company has been in existence for {{ foundationDate }} years.

Which would refer to the foundation field in your page. Then do some date juggling to replace.

1 Like

I was just about to optimize my question when I got your answer :wink: Great!

Thanks for your tip. That sounds great and very logical.
But I’m going to put my :brain: brain cells into :hourglass: standby mode for today.

My temporary solution worked, but it was extremely cumbersome and inflexible. And incomplete text appeared at the latest when the search results were displayed.

Sounds like a plan, going to do the same. Good night.

1 Like

Additionally, I would then create a field method for easier replacement, for example toDateDiff(), so you can then do:

<?= Str::template($page->text(), [
    'foundationDate' => $page->foundationDate()->toDateDiff(),
]) ?>

Now I’ve invested (too) much time in getting the cookbook to work in my project and unfortunately have to capitulate. :white_flag:

:arrow_down:Replacing placeholders in templates
That worked. But does not correspond to the desired solution after a system-wide replacement.

:arrow_down:Defining the replacements in config.php
Does not work, the placeholder is not replaced.

:arrow_down:The final plugin
Also does not work, the placeholder is not replaced.

I copied the code examples 1:1 from the cookbook. However, I was confused by the various options and I don’t know what the output in the template for the “final plugin” looks like. Is

I created a quick example Starterkit: GitHub - texnixe/k-placeholders


  • placeholder plugin,
  • site.yml for the replacement structure field,
  • page “notes/exploring-the-universe” for the replacement example
  • site/snippets/block/text.php for the snippet to render the example

1 Like

If you prefer a solution that works on every page, without having to call replace() explicitely, you can use a hook instead, so keep the plugin with the toOptions method and the structure field from the example, but add a page.render:after hook:

'page.render:after' => function (string $contentType, string $html) {
    if ($contentType !== 'html') {
        return $html;

    return Str::template($html, site()->placeholders()->toOptions());

1 Like

Dear Sonja,

a huge thank you for your great work! I am overwhelmed.

And your supplement (hook solution) came at exactly the right moment because it worked straight away. The input option with the structure field is also an excellent solution.

However, I didn’t just copy and paste everything, but looked at all the functions in detail to understand the process.

I can use the plug-in in all my projects. This is because telephone numbers, company names, company foundation date or similar are used throughout all sections of a website.

You should definitely include this great solution in the :books: cookbook.


I have integrated ‘hooks’ directly into the plug-in to minimize the number of files.
Are there any problems if ‘hooks’ is already present in confiq.php?

It would be great to integrate the tab ‘placeholders’ for site.yml into the plug-in to make it a copy-and-paste solution. But that would be a luxury feature, the programming of which is beyond my knowledge.

I have now integrated your plug-in in 2 projects. It works perfectly!

Only in a third project I get an error message on all pages in the frontend, caused by the following line from the plug-in:

return Str::template($html, site()->placeholders()->toOptions());

The config.php code is identical for all three projects.
The Kirby 4.0.3 version is also the same.
There are even fewer plugins installed in the third project. I have temporarily removed all the others, that’s not the problem.

To check, here is the code of the entire plugin:

use Kirby\Cms\App as Kirby;

Kirby::plugin('gbd/gbd-placeholders', [

	'fieldMethods' => [
		'toOptions' => function($field) {
		$result = [];
		foreach ($field->toStructure() as $option) {
		if ($option->key()->isNotEmpty()) {
		$result[$option->key()->value()] = match ($option->datatype()->value()) {
		'date' => $option->date()->toDateDiff(),
		'text' => $option->text()->value(),
		return $result;
	'replace' => function ($field, array $placeholders = []) {
		$field->value = Str::template($field->value, $placeholders);

		return $field;
	'toDateDiff' => function($field) {
		$date = new DateTime($field->toDate('Y-m-d'));
		$current = new DateTime('now');

		$date->setTimezone(new DateTimeZone('Europe/Berlin'));
		$current->setTimezone(new DateTimeZone('Europe/Berlin'));

		$diff = $current->diff($date);

		return $diff->format('%y');

	'hooks' => [
		'page.render:after' => function (string $contentType, string $html) {
		if ($contentType !== 'html') {
		return $html;
		return Str::template($html, site()->placeholders()->toOptions());


Just create it as tab blueprint, like you would a seo tab or so, that you can then include in your site.yml. Like in this example, only register and put the tab blueprint in the plugin.

Regarding the error message, not sure what’s happening. The callback should return a string but seems to return an array for some reason. Are you using empty placeholders {{ }} somewhere by mistake?

1 Like

I learned a lot this weekend (:ok_hand: thanks to your great help).

I was able to register the blueprint in the plugin.
However, the following page was helpful:

Thanks to the example, it worked straight away.

Back to the error message: As my projects are only minimally different in terms of structure, I suspect the error is in an unspecific place. The full text search for {{ }} did not return any hits.
I cleaned up my project with your cleanup.php script. This was also unsuccessful. I will continue to :mag_right: look for the cause. I’m sure it’s just a :man_facepalming: trivial thing.

At least, when I put double empty curly braces into a text file, I get the same error message as you do. However, if you get this error message on every single page, it’s very likely something else. Would need proper debugging.

While I continue to look for the cause, I can show you the error report.
Is there anything useful for you?

Whoops\Exception\ErrorException thrown with message "Array to string conversion"

#10 Whoops\Exception\ErrorException in /Users/gerrit/Sites/localhost/website.com/kirby/src/Toolkit/Str.php:1290
#9 preg_replace_callback in /Users/gerrit/Sites/localhost/website.com/kirby/src/Toolkit/Str.php:1290
#8 Kirby\Toolkit\Str:template in /Users/gerrit/Sites/localhost/website.com/site/plugins/gbd-placeholders/index.php:44
#7 Kirby\Cms\App:{closure} in [internal]:0
#6 Closure:call in /Users/gerrit/Sites/localhost/website.com/kirby/src/Toolkit/Controller.php:60
#5 Kirby\Toolkit\Controller:call in /Users/gerrit/Sites/localhost/website.com/kirby/src/Cms/Event.php:139
#4 Kirby\Cms\Event:call in /Users/gerrit/Sites/localhost/website.com/kirby/src/Cms/App.php:225
#3 Kirby\Cms\App:apply in /Users/gerrit/Sites/localhost/website.com/kirby/src/Cms/Page.php:1020
#2 Kirby\Cms\Page:render in /Users/gerrit/Sites/localhost/website.com/kirby/src/Cms/App.php:775
#1 Kirby\Cms\App:io in /Users/gerrit/Sites/localhost/website.com/kirby/src/Cms/App.php:1189
#0 Kirby\Cms\App:render in /Users/gerrit/Sites/localhost/website.com/index.php:5

:pencil2: Edit:
I have only used 3 placeholders on the contact page so far. Everything was entered correctly in the .txt. Even if I delete the three placeholders again, the error message is still displayed. And on all pages.

If I remove the placeholder plugin, the page works.
Allegedly the following line from the plugin is the trigger:
return Str::template($html, site()->placeholders()->toOptions());

Of course, I also deleted the media and cache folders.

:partying_face: I have found the cause: It was due to a small JavaScript in footer.php that plays an audio file on mouse-over:

const clickSound = new Audio("<?= url('click') ?>.mp3");
const audios = {};
document.querySelectorAll(".clickSound").forEach((element, index) => {
  audios[index] = clickSound.cloneNode();
  element.addEventListener("mouseenter", () => audios[index].play());

As this is just a gimmick, I have :facepunch::wastebasket: removed the JS.

Finally it’s weekend. Let’s see what else I can do with this great Sunday. Go for a walk in the sun?
:angry: Oh crap, it’s already evening…

For a moment I thought you had moved to another time zone.

Well, I went for a bike ride in the sun in the afternoon :biking_woman:, think that was a good choice.

1 Like

:biking_woman: That sounds like a perfect decision!

I wish I were in a different time zone where it was still daytime to do something (without a computer). Kirby had me more mesmerized today than the great weather. Luckily the positive results had taken over.

Thank you so much for your great help.
That’s not a given on a Sunday.
Have a great rest of the Sunday.

Here is the (visual) result of your great help and tips.

As a freely available plugin for Plugins | Kirby CMS the range of functions is still too minimalistic. But there will certainly be a version 2.0 :wink:

@texnixe Version 1.1 of the plug-in is in the oven and I’m trying to optimize the logic :wink:
I can choose in the select (or toggle) field whether I want to enter a date or text. This means that there is a risk that there is a value in both fields. As the other field is hidden with “when”, this is not even noticeable. However, there is then a problem with the output.
Is it possible to prevent this? Or to display a message when saving that both fields have been filled in?

  keyheadline: demo-demo
  key: demo-demo
  datatype: text
  date: 2024-01-30
  text: >
    Hey date, you're in the wrong place. This is my place!

This is the relevant information, it doesn’t matter if both are filled in.

1 Like