diff --git a/src/ExpressionLanguage/ExpressionExtension.php b/src/ExpressionLanguage/ExpressionExtension.php
new file mode 100644
index 0000000000000000000000000000000000000000..4eb5585605975b32d82282fa1933907fc101f219
--- /dev/null
+++ b/src/ExpressionLanguage/ExpressionExtension.php
@@ -0,0 +1,142 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dbp\Relay\CoreBundle\ExpressionLanguage;
+
+/**
+ * This Type gets injected into our expression language variant with the name
+ * 'relay'. This allows us to add functions/methods with some kind of namespacing,
+ * instead of polluting the global namespace.
+ */
+class ExpressionExtension
+{
+    /**
+     * @var ExpressionLanguage
+     */
+    private $lang;
+
+    public function __construct(ExpressionLanguage $lang)
+    {
+        $this->lang = $lang;
+    }
+
+    public static function str_starts_with()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('str_starts_with', $args);
+    }
+
+    public static function str_ends_with()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('str_ends_with', $args);
+    }
+
+    public static function substr()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('substr', $args);
+    }
+
+    public static function strpos()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('strpos', $args);
+    }
+
+    public static function strlen()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('strlen', $args);
+    }
+
+    public static function ceil()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('ceil', $args);
+    }
+
+    public static function floor()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('floor', $args);
+    }
+
+    public static function round()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('round', $args);
+    }
+
+    public static function max()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('max', $args);
+    }
+
+    public static function min()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('min', $args);
+    }
+
+    public static function count()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('count', $args);
+    }
+
+    public static function implode()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('implode', $args);
+    }
+
+    public static function explode()
+    {
+        $args = func_get_args();
+
+        return call_user_func_array('explode', $args);
+    }
+
+    public static function empty($value): bool
+    {
+        // empty is not a real function, so call_user_func_array doesn't work
+        return empty($value);
+    }
+
+    public function map(iterable $iterable, string $expression): array
+    {
+        $transformedResult = [];
+        foreach ($iterable as $key => $value) {
+            $transformedResult[$key] = $this->lang->evaluate($expression, ['key' => $key, 'value' => $value, 'relay' => $this]);
+        }
+
+        return $transformedResult;
+    }
+
+    public function filter(iterable $iterable, string $expression): array
+    {
+        $filteredResult = [];
+        foreach ($iterable as $key => $value) {
+            if ($this->lang->evaluate($expression, ['key' => $key, 'value' => $value])) {
+                $filteredResult[] = $value;
+            }
+        }
+
+        return $filteredResult;
+    }
+}
diff --git a/src/ExpressionLanguage/ExpressionLanguage.php b/src/ExpressionLanguage/ExpressionLanguage.php
index fe726473f7c9104557d2f6d81704d64342d79657..bc29f3184e42f5f177e34a7a09d5ad7f52a59115 100644
--- a/src/ExpressionLanguage/ExpressionLanguage.php
+++ b/src/ExpressionLanguage/ExpressionLanguage.php
@@ -30,4 +30,15 @@ class ExpressionLanguage extends SymfonyExpressionLanguage
 
         parent::__construct($cache, $providers);
     }
+
+    /**
+     * @return mixed
+     */
+    public function evaluate($expression, array $values = [])
+    {
+        $ext = new ExpressionExtension($this);
+        $values['relay'] = $ext;
+
+        return parent::evaluate($expression, $values);
+    }
 }
diff --git a/tests/ExpressionLanguageTest.php b/tests/ExpressionLanguageTest.php
index 4b31dfa74e06ac919f768c78e0dd3bcd4c7c48d4..ef09df286b6dd20ba198d3a83ce1e4714a74af2a 100644
--- a/tests/ExpressionLanguageTest.php
+++ b/tests/ExpressionLanguageTest.php
@@ -25,6 +25,15 @@ class ExpressionLanguageTest extends TestCase
         $this->assertSame([1, 5, 2, 4], $lang->evaluate('filter([1, 5, 2, 4], "42")'));
         $this->assertSame([5, 4], $lang->evaluate('filter([1, 5, 2, 4], "value > 2")'));
         $this->assertSame([2, 4], $lang->evaluate('filter({1: 2, 3: 4}, "true")'));
+        $this->assertSame([5, 2, 4], $lang->evaluate('filter([0.5, 5, 2, 4], "floor(value)")'));
+
+        $this->assertSame([], $lang->evaluate('relay.filter([], "true")'));
+        $this->assertSame([], $lang->evaluate('relay.filter([1, 5, 2, 4], "false")'));
+        $this->assertSame([1, 5, 2, 4], $lang->evaluate('relay.filter([1, 5, 2, 4], "true")'));
+        $this->assertSame([1, 5, 2, 4], $lang->evaluate('relay.filter([1, 5, 2, 4], "42")'));
+        $this->assertSame([5, 4], $lang->evaluate('relay.filter([1, 5, 2, 4], "value > 2")'));
+        $this->assertSame([2, 4], $lang->evaluate('relay.filter({1: 2, 3: 4}, "true")'));
+        $this->assertSame([5, 2, 4], $lang->evaluate('relay.filter([0.5, 5, 2, 4], "relay.floor(value)")'));
     }
 
     public function testMap()
@@ -35,6 +44,14 @@ class ExpressionLanguageTest extends TestCase
         $this->assertSame([2, 6, 3, 5], $lang->evaluate('map([1, 5, 2, 4], "value + 1")'));
         $this->assertSame([1 => 3, 3 => 7], $lang->evaluate('map({1: 2, 3: 4}, "key + value")'));
         $this->assertSame([1 => 42, 3 => 42], $lang->evaluate('map({1: 2, 3: 4}, "42")'));
+        $this->assertSame([1.0], $lang->evaluate('map([0.5], "ceil(value)")'));
+
+        $this->assertSame([], $lang->evaluate('relay.map([], "true")'));
+        $this->assertSame([false], $lang->evaluate('relay.map([1], "false")'));
+        $this->assertSame([2, 6, 3, 5], $lang->evaluate('relay.map([1, 5, 2, 4], "value + 1")'));
+        $this->assertSame([1 => 3, 3 => 7], $lang->evaluate('relay.map({1: 2, 3: 4}, "key + value")'));
+        $this->assertSame([1 => 42, 3 => 42], $lang->evaluate('relay.map({1: 2, 3: 4}, "42")'));
+        $this->assertSame([1.0], $lang->evaluate('relay.map([0.5], "relay.ceil(value)")'));
     }
 
     public function testEmpty()
@@ -45,6 +62,12 @@ class ExpressionLanguageTest extends TestCase
         $this->assertTrue($lang->evaluate('empty("0")'));
         $this->assertFalse($lang->evaluate('empty(42)'));
         $this->assertFalse($lang->evaluate('empty([42])'));
+
+        $this->assertTrue($lang->evaluate('relay.empty([])'));
+        $this->assertTrue($lang->evaluate('relay.empty(0)'));
+        $this->assertTrue($lang->evaluate('relay.empty("0")'));
+        $this->assertFalse($lang->evaluate('relay.empty(42)'));
+        $this->assertFalse($lang->evaluate('relay.empty([42])'));
     }
 
     public function testPhp()
@@ -53,6 +76,10 @@ class ExpressionLanguageTest extends TestCase
         $this->assertSame(2, $lang->evaluate('count([1, 2])'));
         $this->assertSame('1-2', $lang->evaluate('implode("-", ["1", "2"])'));
         $this->assertSame(['1', '2'], $lang->evaluate('explode("-", "1-2")'));
+
+        $this->assertSame(2, $lang->evaluate('relay.count([1, 2])'));
+        $this->assertSame('1-2', $lang->evaluate('relay.implode("-", ["1", "2"])'));
+        $this->assertSame(['1', '2'], $lang->evaluate('relay.explode("-", "1-2")'));
     }
 
     public function testNumeric()
@@ -63,6 +90,12 @@ class ExpressionLanguageTest extends TestCase
         $this->assertSame(1.0, $lang->evaluate('round(0.5)'));
         $this->assertSame(42, $lang->evaluate('max([2, 42])'));
         $this->assertSame(2, $lang->evaluate('min([2, 42])'));
+
+        $this->assertSame(2.0, $lang->evaluate('relay.ceil(1.2)'));
+        $this->assertSame(1.0, $lang->evaluate('relay.floor(1.9)'));
+        $this->assertSame(1.0, $lang->evaluate('relay.round(0.5)'));
+        $this->assertSame(42, $lang->evaluate('relay.max([2, 42])'));
+        $this->assertSame(2, $lang->evaluate('relay.min([2, 42])'));
     }
 
     public function testString()
@@ -75,5 +108,13 @@ class ExpressionLanguageTest extends TestCase
         $this->assertSame('foo', $lang->evaluate('substr("foobar", 0, 3)'));
         $this->assertSame(1, $lang->evaluate('strpos("foobar", "oo")'));
         $this->assertSame(6, $lang->evaluate('strlen("foobar")'));
+
+        $this->assertTrue($lang->evaluate('relay.str_starts_with("foo", "fo")'));
+        $this->assertFalse($lang->evaluate('relay.str_starts_with("foo", "xo")'));
+        $this->assertTrue($lang->evaluate('relay.str_ends_with("foo", "oo")'));
+        $this->assertFalse($lang->evaluate('relay.str_ends_with("foo", "of")'));
+        $this->assertSame('foo', $lang->evaluate('relay.substr("foobar", 0, 3)'));
+        $this->assertSame(1, $lang->evaluate('relay.strpos("foobar", "oo")'));
+        $this->assertSame(6, $lang->evaluate('relay.strlen("foobar")'));
     }
 }