diff --git a/CHANGES.rst b/CHANGES.rst
index 63d2c60b..1454af4d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,8 @@ Changelog
4.0.0a4 (unreleased)
--------------------
-- Nothing changed yet.
+- Add string interpolators for volto portal url and volto absolute url. Fixes #44
+ [erral]
4.0.0a3 (2022-02-04)
diff --git a/src/plone/volto/configure.zcml b/src/plone/volto/configure.zcml
index b2d065fd..86c9499a 100644
--- a/src/plone/volto/configure.zcml
+++ b/src/plone/volto/configure.zcml
@@ -15,6 +15,7 @@
+
diff --git a/src/plone/volto/interpolators/__init__.py b/src/plone/volto/interpolators/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/plone/volto/interpolators/configure.zcml b/src/plone/volto/interpolators/configure.zcml
new file mode 100644
index 00000000..25ce8909
--- /dev/null
+++ b/src/plone/volto/interpolators/configure.zcml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/plone/volto/interpolators/volto_absolute_url.py b/src/plone/volto/interpolators/volto_absolute_url.py
new file mode 100644
index 00000000..48c91f5e
--- /dev/null
+++ b/src/plone/volto/interpolators/volto_absolute_url.py
@@ -0,0 +1,27 @@
+"""
+Content rule string interpolator to return volto url of a given content item
+"""
+
+from plone import api
+from plone.stringinterp.adapters import BaseSubstitution
+from plone.volto import _
+from Products.CMFCore.interfaces import IContentish
+from zope.component import adapter
+
+
+@adapter(IContentish)
+class VoltoAbsoluteURLSubstitution(BaseSubstitution):
+ """ URL Substitution adapter """
+
+ category = _(u"All Content")
+ description = _(u"Volto URL")
+
+ def safe_call(self):
+ """ get the url """
+ context_url = self.context.absolute_url()
+ plone_domain = api.portal.get().absolute_url()
+ frontend_domain = api.portal.get_registry_record("volto.frontend_domain")
+ if frontend_domain.endswith("/"):
+ frontend_domain = frontend_domain[:-1]
+
+ return context_url.replace(plone_domain, frontend_domain)
diff --git a/src/plone/volto/interpolators/volto_portal_url.py b/src/plone/volto/interpolators/volto_portal_url.py
new file mode 100644
index 00000000..c2257432
--- /dev/null
+++ b/src/plone/volto/interpolators/volto_portal_url.py
@@ -0,0 +1,22 @@
+"""
+Content rule string interpolator to return volto url of a given content item
+"""
+
+from plone import api
+from plone.stringinterp.adapters import BaseSubstitution
+from plone.volto import _
+from Products.CMFCore.interfaces import IContentish
+from zope.component import adapter
+
+
+@adapter(IContentish)
+class VoltoPortalURLSubstitution(BaseSubstitution):
+ """ URL Substitution adapter """
+
+ category = _(u"All Content")
+ description = _(u"Volto Portal URL")
+
+ def safe_call(self):
+ """ get the url """
+ frontend_domain = api.portal.get_registry_record("volto.frontend_domain")
+ return frontend_domain
diff --git a/src/plone/volto/tests/test_interpolators.py b/src/plone/volto/tests/test_interpolators.py
new file mode 100644
index 00000000..fb783de5
--- /dev/null
+++ b/src/plone/volto/tests/test_interpolators.py
@@ -0,0 +1,76 @@
+""" test string interpolation substitutions for content rules """
+# -*- coding: utf-8 -*-
+import unittest
+
+from plone import api
+from plone.app.testing import TEST_USER_ID, setRoles
+from plone.stringinterp.interfaces import IStringInterpolator
+
+from plone.volto.interpolators.volto_portal_url import (
+ VoltoPortalURLSubstitution,
+)
+from plone.volto.interpolators.volto_absolute_url import (
+ VoltoAbsoluteURLSubstitution,
+)
+from plone.volto.testing import (
+ PLONE_VOLTO_CORE_INTEGRATION_TESTING,
+ PLONE_VOLTO_CORE_FUNCTIONAL_TESTING,
+)
+
+
+class TestSubstitutions(unittest.TestCase):
+ """ Test case for substitutions"""
+
+ layer = PLONE_VOLTO_CORE_INTEGRATION_TESTING
+
+ def setUp(self):
+ self.portal = self.layer["portal"]
+ self.request = self.layer["request"]
+
+ def test_volto_portal_url(self):
+ """ test for volto_portal_url"""
+ volto_url = api.portal.get_registry_record("volto.frontend_domain")
+ substitution = VoltoPortalURLSubstitution(self.portal)()
+ self.assertEqual(substitution, volto_url)
+
+ def test_string_interpolation_volto_portal_url(self):
+ """ test interpolating as string """
+ volto_url = api.portal.get_registry_record("volto.frontend_domain")
+ string = "${volto_portal_url}"
+ value = IStringInterpolator(self.portal)(string)
+ self.assertEqual(value, volto_url)
+
+
+class TestSubstitutionsFunctional(unittest.TestCase):
+ """ Test case for substitutions"""
+
+ layer = PLONE_VOLTO_CORE_FUNCTIONAL_TESTING
+
+ def setUp(self):
+ """ test setup """
+ self.portal = self.layer["portal"]
+ self.request = self.layer["request"]
+ setRoles(self.portal, TEST_USER_ID, ["Manager"])
+
+ self.portal.invokeFactory("Document", id="doc", title="My Document")
+ self.document = self.portal.get("doc")
+
+ def test_volto_absolute_url(self):
+ """ test for volto_absolute_url"""
+
+ volto_url = api.portal.get_registry_record("volto.frontend_domain")
+ portal_url = self.portal.absolute_url()
+ context_url = self.document.absolute_url()
+ substitution = VoltoAbsoluteURLSubstitution(self.document)()
+ self.assertEqual(substitution, context_url.replace(portal_url, volto_url))
+
+ def test_string_interpolation_volto_absolute_url(self):
+ """ test as string interpolator"""
+
+ volto_url = api.portal.get_registry_record("volto.frontend_domain")
+ portal_url = self.portal.absolute_url()
+ context_url = self.document.absolute_url()
+
+ string = "${volto_absolute_url}"
+ value = IStringInterpolator(self.document)(string)
+ self.assertEqual(value, context_url.replace(portal_url, volto_url))