diff --git a/news/44.bugfix b/news/44.bugfix
new file mode 100644
index 00000000..4d4b5bdb
--- /dev/null
+++ b/news/44.bugfix
@@ -0,0 +1,2 @@
+Added string interpolators to create volto URLs for context and portal based on an existing setting
+[erral]
diff --git a/src/plone/volto/configure.zcml b/src/plone/volto/configure.zcml
index 6916c533..fe0354fd 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..2e06329f
--- /dev/null
+++ b/src/plone/volto/interpolators/configure.zcml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
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..eff5567a
--- /dev/null
+++ b/src/plone/volto/interpolators/volto_absolute_url.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+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 = _("All Content")
+ description = _("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..76e8bc64
--- /dev/null
+++ b/src/plone/volto/interpolators/volto_portal_url.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+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 = _("All Content")
+ description = _("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..0f4db699
--- /dev/null
+++ b/src/plone/volto/tests/test_interpolators.py
@@ -0,0 +1,75 @@
+# -*- 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))