t3s / vars
Global variables for TYPO3 v14: maintain company, address, phone, email etc. once as site settings and use them as {{placeholders}} in RTE, Fluid and TypoScript. CKEditor 5 dropdown, automatic tel/mailto/URL links and an "Address" content element with schema.org markup, opening hours, social media l
Requires
- php: ^8.2
- typo3/cms-core: ^14.3
- typo3/cms-frontend: ^14.3
- typo3/cms-rte-ckeditor: ^14.3
Requires (Dev)
None
Suggests
- quellenform/t3x-iconpack-fontawesome: Icons for phone, email, website etc. in the content element "Address"
Provides
None
Conflicts
None
Replaces
None
README
Version: 1.1.0 · Package: t3s/vars · Extension key: t3s_vars · Documentation: docs.typo3.org · Live demo: www.t3sbootstrap.de/extensions/globale-variablen
t3s_vars provides centrally maintained values such as company, address, phone or email as placeholders:
Call {{company}}: {{phone}}
- Values are maintained in the backend as site settings (different per site)
- Replacement in the RTE (bodytext) and wherever
lib.parseFunc/lib.parseFunc_RTEapplies - CKEditor 5 dropdown "Variables" – added automatically to every preset (core or custom)
- Automatic links: phone →
tel:, email →mailto:(with spam protection), website → URL - Fluid ViewHelpers
<vars:get>and<vars:replace>(e.g. for headers) - Content element "Address" (
t3s_address) – layouts Compact, Compact (address & map), Split (contact + Google Maps) and Split (contact only),<address>with schema.org markup - Icons for phone, email, website etc. if EXT:iconpack with Font Awesome is installed
- Google Maps: route button to your Google Business profile, optional map with two-click privacy
- Opening hours in schema.org format – shown localized ("Mon–Fri 08:00–17:00" / "Mo–Fr 08:00–17:00 Uhr")
- Social media profiles (Facebook, Instagram, LinkedIn, XING, YouTube, X, TikTok, Mastodon) as icon links
- vCard download – "Save contact" link in the content element (
.vcffor the address book) - JSON-LD (
LocalBusinessor any schema.org type) in the<head>of every page – optional - Multilingual sites: translations of single values per language
- Values also available as TypoScript constants and in PAGEVIEW
- No database tables or fields required (the layout of the content element is stored in
pi_flexform), no custom RTE preset required
Requirements
| TYPO3 | 14.3+ |
| PHP | 8.2+ |
| RTE | CKEditor 5 (rte_ckeditor) |
Installation
composer require t3s/vars
Then add the site set "T3S Vars - Global variables" (t3s/vars) to your site:
Sites → Setup → Edit site → Sets, or in config/sites/<site>/config.yaml:
dependencies: - t3s/vars
Flush caches:
vendor/bin/typo3 cache:flush
Maintaining values
Sites → Setup → Edit settings → category Global variables.
The values are stored in config/sites/<site>/settings.yaml:
vars: company: 'TYPO3 Association' street: 'Bahnhofstrasse 4' zip: '6340' city: 'Baar' country: 'Switzerland' email: 'info@typo3.org' website: 'https://typo3.org' openingHours: 'Mo-Fr 08:00-17:00; Sa 09:00-12:00' linkedin: 'https://www.linkedin.com/company/typo3'
Included variables
| Placeholder | Setting | Link |
|---|---|---|
{{company}} |
vars.company |
– |
{{person}} |
vars.person |
– |
{{profession}} |
vars.profession |
– |
{{street}} |
vars.street |
– |
{{addressAddition}} |
vars.addressAddition |
– |
{{zip}} |
vars.zip |
– |
{{city}} |
vars.city |
– |
{{country}} |
vars.country |
– |
{{phone}} |
vars.phone |
tel: |
{{fax}} |
vars.fax |
tel: |
{{email}} |
vars.email |
mailto: |
{{website}} |
vars.website |
URL |
{{vatId}} |
vars.vatId |
– |
{{maps}} |
vars.maps |
– (used by the content element) |
{{openingHours}} |
vars.openingHours |
– (localized, one period per line) |
{{facebook}}, {{instagram}}, {{linkedin}}, {{xing}}, {{youtube}}, {{x}}, {{tiktok}}, {{mastodon}} |
vars.facebook … |
URL |
Adding custom variables
In your own site set in settings.definitions.yaml – every key with the prefix vars. automatically becomes a placeholder and shows up in the RTE dropdown:
settings: vars.register: label: 'Commercial register' category: vars type: string default: '' vars.mobile: label: 'Mobile' category: vars type: string default: ''
Nested keys are possible: vars.office.phone → {{office.phone}}.
Usage
In the RTE
Type the placeholder or insert it via the Variables dropdown in the toolbar. The dropdown only lists variables that have a value, each with its label and current value.
| Notation | Output |
|---|---|
{{phone}} |
Link if configured as a link key, otherwise text |
{{phone:text}} |
always plain text |
{{unknown}} |
stays unchanged (typos are easy to spot) |
Empty values are replaced by an empty string.
Controlling links
Category Variables: options in the site settings:
| Setting | Default | Link |
|---|---|---|
t3sVars.telKeys |
phone,fax |
tel: – spaces, hyphens etc. are removed from the href |
t3sVars.mailKeys |
email |
mailto: – config.spamProtectEmailAddresses applies |
t3sVars.urlKeys |
website and the social media keys |
URL – opens in a new tab (target="_blank" rel="noopener") |
Simply add your own keys, e.g. phone,fax,mobile.
Website URLs: enter the full URL including https:// (e.g. https://typo3.org). If the scheme is missing, https:// is added automatically for the link; the link text is shown exactly as entered.
Every setting has a short help text with format and example in the backend (English and German).
Google Maps
| Setting | Category | Content |
|---|---|---|
vars.maps |
Global variables | Share link of your Google Business profile (Google Maps → your business → Share → https://maps.app.goo.gl/…) |
t3sVars.mapsEmbed |
Variables: options | Embed URL for the map: Google Maps → Share → Embed a map → paste the src URL (https://www.google.com/maps/embed?pb=…) or the whole <iframe> code – share links do not work here |
t3sVars.mapsConsent |
Variables: options | Load the map only after a click (default: on) |
Use the share link of your Business profile – not a plain address search – so the route button points to your verified listing. This links website and profile consistently (local SEO); together with identical NAP data (name, address, phone) on the website and in the profile it is a supporting signal, not a ranking guarantee.
Only http(s) URLs are accepted for both values.
Opening hours
vars.openingHours takes the periods in schema.org format, separated by ; (the settings editor shows a single-line field; line breaks in settings.yaml work as well):
vars: openingHours: 'Mo-Fr 08:00-17:00; Sa 09:00-12:00; By appointment'
Days: Mo Tu We Th Fr Sa Su, ranges with -, lists with , (Mo,We,Fr 09:00-12:00). In the frontend the periods are shown in the page language – "Mon–Fri 08:00–17:00" in English, "Mo–Fr 08:00–17:00 Uhr" in German. Entries in another format (e.g. "By appointment") are shown as text and left out of the structured data.
Social media
vars.facebook, vars.instagram, vars.linkedin, vars.xing, vars.youtube, vars.x, vars.tiktok, vars.mastodon (category Global variables: social media) take the full profile URL. The content element shows them as icon links (brand icons via EXT:iconpack, otherwise the name as text); JSON-LD lists them as sameAs. Only http(s) URLs are used.
Multilingual sites
Site settings apply to all languages of a site. Values that differ per language go into t3sVars.translations (category Variables: options), separated by ;:
en.country = Germany; en.profession = Web developer; fr.country = Allemagne
The language code is the one of the site language (en for en_US.UTF-8). Translations apply to placeholders, ViewHelpers, the content element and JSON-LD – not to TypoScript constants and {site.settings.vars.*}. For opening hours separate the periods with |: en.openingHours = Mo-Fr 08:00-17:00 | By appointment. Day names are translated automatically, so opening hours usually need no translation.
JSON-LD (schema.org)
With t3sVars.jsonLd enabled, every page gets structured data in the <head> – name, URL, phone, fax, email, VAT ID, address, opening hours, map link and social profiles:
<script type="application/ld+json">{ "@context": "https://schema.org", "@type": "LocalBusiness", "name": "TYPO3 Association", "url": "https://typo3.org", "address": { "@type": "PostalAddress", "streetAddress": "Bahnhofstrasse 4", "postalCode": "6340", "addressLocality": "Baar", "addressCountry": "Switzerland" }, "openingHours": ["Mo-Fr 08:00-17:00"], "sameAs": ["https://www.linkedin.com/company/typo3"] }</script>
| Setting | Default | |
|---|---|---|
t3sVars.jsonLd |
off | output JSON-LD (requires vars.company) |
t3sVars.jsonLdType |
LocalBusiness |
schema.org type, e.g. Organization, ProfessionalService, Store, Restaurant, Dentist – also used for the microdata of the content element |
The script is added before </head> via a PSR-14 event, so it works with PAGE and PAGEVIEW alike. Disable it if another extension already outputs LocalBusiness/Organization data. Check the result with Google's Rich Results Test.
In Fluid
The namespace vars is registered globally – no {namespace} declaration needed.
<!-- single value (linked if configured) --> <vars:get key="phone" /> <vars:get key="phone" link="0" /> <vars:get key="register" default="–" /> {vars:get(key: 'company')} <!-- replace placeholders in any text --> <vars:replace link="0">{data.header}</vars:replace> {data.header -> vars:replace(link: 0)}
<vars:replace> escapes the content before replacing. A ViewHelper output inside (e.g. f:sanitize.html()) is not escaped twice.
Example: placeholders in headers
Placeholders are replaced automatically in RTE text – but headers are output directly by the template, without parseFunc. An editor entering the header
Welcome to {{company}}
sees Welcome to {{company}} in the frontend. With vars:replace in the template that outputs the header, it becomes Welcome to TYPO3 Association.
Override the partial that outputs the header and wrap the output. With fluid_styled_content this is Partials/Header/Header.html – replace {header} with:
<vars:replace link="0">{header}</vars:replace>
If your theme outputs headers in another partial, wrap the output there in the same way. Output that is already HTML (e.g. {header -> f:format.raw()}) can be wrapped as is – it is not escaped twice.
link="0" prevents nested links – headers are often linked themselves.
More examples
<!-- page title in a PAGEVIEW template: "Contact – {{company}}" --> <title>{page.title -> vars:replace(link: 0)}</title> <!-- image caption or teaser text without RTE --> <vars:replace>{file.description}</vars:replace>
In PAGEVIEW templates the raw values are available directly:
{site.settings.vars.company}
In TypoScript
Site settings are automatically available as TypoScript constants. {$vars.…} works only in TypoScript (setup/constants of a site set) – not in Fluid and not in the RTE.
Useful wherever TypoScript itself outputs or sets a value:
# meta tag
page.meta.author = {$vars.company}
# copyright in footer via lib object
lib.copyright = TEXT
lib.copyright.value = © {$vars.company}
lib.copyright.htmlSpecialChars = 1
lib.copyright.wrap = <span class="copyright">|</span>
# extension config that expects TypoScript
plugin.tx_news.settings.detail.media.image.alt = {$vars.company}
Overview: which notation where
| Where | Notation |
|---|---|
| RTE / bodytext | {{company}} |
| Fluid template | <vars:get key="company" /> |
| Headers etc. (override the header partial) | <vars:replace link="0">…</vars:replace> – see example above |
| PAGEVIEW template | {site.settings.vars.company} |
| TypoScript | {$vars.company} |
If no TypoScript object needs the value, {$vars.…} is not required.
Content element "Address" (t3s_address)
In the new content element wizard under Default → Address (global variables). The data comes directly from the site settings, so changes there apply to all address elements at once (after flushing the cache).
| Field | Use |
|---|---|
| Layout | Compact (address only), Compact (address & map), Split (contact + map) or Split (contact only, no map) – layouts with map need t3sVars.mapsEmbed (hint shown at the field) |
| Header / Subheader | Split: heading and small label above it (e.g. "Contact") |
Opening hours and social media links appear automatically below the contact details as soon as they are maintained.
Save contact (vCard): the contact details end with a link Save contact that downloads a .vcf file (name, company, profession, address, phone, fax, email, website, social profiles) for the address book on phone and computer. The file is embedded in the link as data URL – no extra request, no server route, cached with the page. Disable it with t3sVars.vcard: false.
Layout "Compact"
Output (empty values are omitted):
- Header, frame, spacing etc. come from the
Defaultlayout of the content elements (e.g. fluid_styled_content). - Links follow the settings under Variables: options.
- Labels (Phone, Fax, Email, Web) are translated (en, de).
Layout "Compact (address & map)"
Same address block as Compact, the Google map below it (full width, min. 350px, rounded corners). Uses the same two-click privacy as Split. Without t3sVars.mapsEmbed it looks like Compact.
Layout "Split"
Two columns from 992px: left subheading, heading, NAP blocks (Headquarters, Direct contact) and the route button; right the Google map. Without t3sVars.mapsEmbed – or with the layout Split (contact only, no map) – the section is single-column.
- The header is rendered inside the left column (the layout's header section is left empty). Hidden header layout hides it.
- The consent script is only loaded when a map is shown.
- Colors follow the surrounding text color, so light and dark mode work without configuration. Links and buttons use the Bootstrap primary/link color if available. Override per site:
.t3s-contact { --t3s-contact-primary: currentColor; --t3s-contact-accent: #2563eb; --t3s-contact-muted: color-mix(in srgb, currentColor 72%, transparent); --t3s-contact-bg-light: color-mix(in srgb, currentColor 6%, transparent); --t3s-contact-btn-bg: #2563eb; --t3s-contact-btn-color: #fff; --t3s-contact-padding: 2rem; }
- Two-click privacy: with
t3sVars.mapsConsent(default) no request is sent to Google until the visitor clicks Load map. Turned off, the iframe is embedded directly (loading="lazy"). - Content Security Policy: if CSP is active in the frontend, allow
frame-src https://www.google.com.
Icons (EXT:iconpack + Font Awesome)
If EXT:iconpack with Font Awesome (fa5, fa6 or fa7) is installed, the content element shows icons instead of the labels "Phone:", "Fax:", "Email:", "Web:":
| Variable | Icon |
|---|---|
| phone | mobile |
| fax | fax |
envelope |
|
| website | globe |
| maps (route link) | map-marker-alt |
- Switch off with the site setting
t3sVars.icons. - Icons get the classes
t3s-icon fa-fwandaria-hidden="true". - Other icons via TypoScript, e.g. a landline icon:
tt_content.t3s_address.dataProcessing.1760.icons.phone = phone - Without EXT:iconpack the labels are shown – nothing to configure.
Spacing
The address is built from blocks: profession + owner, company + address, contact entries (each on its own line), VAT ID. contact.css is loaded in all layouts; adjust the spacing per site:
.t3s-address { --t3s-address-gap: 1.5rem; /* between the blocks */ --t3s-address-item-gap: 1rem; /* between phone, email, web … */ --t3s-address-vat-gap: 3rem; /* above the VAT ID */ }
Customizing the template
Templates: Address.fluid.html (Compact), AddressMap.fluid.html (Compact (address & map)), AddressSplit.fluid.html (Split). Shared partials: T3sVars/Address.fluid.html (address block), T3sVars/Parts.fluid.html (sections Identity, Postal, Contact, Vat – used by all layouts) and T3sVars/Map.fluid.html (map incl. two-click privacy).
Place your own templates/partials in your own extension and add the path with a higher index:
tt_content.t3s_address.partialRootPaths.1770 = EXT:my_extension/Resources/Private/Partials/
tt_content.t3s_address.templateRootPaths.20 = EXT:my_extension/Resources/Private/Templates/Content/
Available in the template:
| Variable | Content |
|---|---|
{vars.<key>} |
raw value (escaped by Fluid) |
{varsLinked.<key>} |
escaped and linked if configured – output with -> f:format.raw() |
{varsConfig.mapsUrl} |
validated Business profile link |
{varsConfig.mapsEmbed} |
validated embed URL |
{varsConfig.mapsConsent} |
two-click privacy on/off |
{varsConfig.vcard} |
{href, filename} of the vCard download or null |
{varsConfig.schemaType} |
schema.org type for the microdata (t3sVars.jsonLdType) |
{varsHours} |
opening hours: list of {spec, text} – spec is the schema.org value (empty for free text), text the localized display |
{varsSocial} |
social profiles: {name, url, icon} per key |
{varsIcons.<key>} |
icon markup (phone, fax, email, website, maps, openingHours) if EXT:iconpack + Font Awesome is available – output with -> f:format.raw() |
This also lets you add custom variables, e.g. {vars.register}.
The DataProcessor T3S\T3sVars\DataProcessing\VariablesProcessor can also be used in other content elements or in PAGEVIEW:
tt_content.my_element.dataProcessing.1760 = T3S\T3sVars\DataProcessing\VariablesProcessor
RTE integration
An event listener (AfterPrepareConfigurationForEditorEvent) adds the plugin and button to every CKEditor 5 preset – no custom preset required.
- Set the position: add
t3sVarsto the preset's toolbar yourself – the button is then not appended a second time:editor: config: toolbar: items: - bold - t3sVars
- Disable: Admin Tools → Settings → Extension Configuration → t3s_vars → rteButton
- No button if the site has no variables with a value.
Planned
- Placeholders in headers without overriding the partial – a site setting that enables
<vars:replace>for headers - Backend preview – the page module shows the real address and the chosen layout instead of the header only
Source and issues
Source code, bug reports and feature requests: github.com/t3solution/t3s_vars
License and author
GPL-2.0-or-later · Helmut Hackbarth · T3Solution