Schema.org server-side
Q-Share's Schema.org markup can be rendered server-side (SSR) and inlined directly into the HTML. A guide for hotel-website webmasters.
Why move to server-side
By default, the Q-Share script script.min.js generates the JSON-LD in the browser, after the page has loaded. Search engines and AI crawlers that do not execute JavaScript — or that read the raw HTML — never see this markup.
With SSR, your server calls the Q-Share API, retrieves exactly the same JSON-LD, and inlines it into the initial HTML. The markup becomes visible to every crawler, with no dependency on JavaScript.
Client-side (before)
<script data-schemas="Hotel"> filled in by script.min.js in the browser. Invisible in the raw HTML.
→Server-side (after)
The server calls the API, caches the response, and inlines the <script type="application/ld+json">. Visible everywhere.
Worth remembering
Same data, same Q-Data dialogue. The only thing that changes is the delivery method: from browser JavaScript to server-rendered HTML.
01 - The APIEndpoint and parameters
A single POST request with the x-source header. The response is the JSON-LD, ready to inline.
POST https://q-share.quinta.im/schemas/generate-from-license?license=…&lang=…&schema=…
| Parameter | Role | Example |
|---|---|---|
| license | The hotel's Q-Share licence (supplied by Quinta). | 8b0ae-OVqI |
| lang | Content language. Make this dynamic, per page. | fr · en · es · pt |
| schema | Comma-separated list of the schemas to generate (see dictionary). | Hotel,FAQPage |
Required header
Add x-source: qshare-ssr-{php|py|node} to the request. Allow for a 10-second timeout.
Language "br" → "pt"
If your site uses the br code for Portuguese, map it to pt for the API. All other codes are identical.
02 - Schema parameterSchema dictionary
Combine types freely, page by page. A typical hotel site uses Hotel,FAQPage,OfferCatalog,Product,Image,Restaurant.
| Schema | Describes |
|---|---|
| Business | The property's identity, contact details and official information. |
| Hotel | Hotel information: amenities, location, star rating. |
| FAQPage | Frequently asked questions, to enrich search results and AI answers. |
| OfferCatalog | Rates, availability and booking conditions. |
| Image | Visual content: metadata, captions, graphic elements. |
| BreadcrumbList | Breadcrumb trail: site structure and navigation hierarchy. |
| Product | Products and services with prices, offers and availability. |
| Restaurant | Restaurant: cuisine, capacity, reservations, opening hours. |
Deep-select
You can request only certain sub-parts of a schema: Hotel[Address,Geo,Amenities].
03 - Integration The code, in your language
Fetch the response, cache it, then inline it in the <head>. All three versions are available in the Q-Channel console.
PHP
<?php $params = http_build_query([ 'license' => '8b0ae-OVqI', 'lang' => 'fr', // Any schema from the dictionary (e.g. OfferCatalog, BreadcrumbList, // deep-select: Hotel[Address,Geo,Amenities]): 'schema' => 'Hotel,FAQPage', ]); $endpoint = 'https://q-share.quinta.im/schemas/generate-from-license?' . $params; // cURL first (works without allow_url_fopen), file_get_contents as fallback. function qshare_fetch_schema($url) { if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 10, CURLOPT_HTTPHEADER => ['x-source: qshare-ssr-php'], ]); $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return ($body !== false && $code >= 200 && $code < 300) ? $body : null; } $ctx = stream_context_create(['http' => [ 'method' => 'POST', 'header' => "x-source: qshare-ssr-php\r\n", 'timeout' => 10, ]]); return @file_get_contents($url, false, $ctx) ?: null; } // Cache the result on the server (1 h or more) — do not call this on every page view. $schema = qshare_fetch_schema($endpoint); // Never break the page if Q-Share goes down: simply omit the tag. // Escape "</" so that a "</script>" inside the JSON // (FAQ answers may contain HTML) does not close the tag early. if ($schema) { echo '<script type="application/ld+json">' . str_replace('</', '<\/', $schema) . '</script>'; }
Python
# qshare_ssr.py import requests ENDPOINT = 'https://q-share.quinta.im/schemas/generate-from-license' PARAMS = { 'license': '8b0ae-OVqI', # Any schema from the dictionary (e.g. OfferCatalog, BreadcrumbList, # deep-select: Hotel[Address,Geo,Amenities]): 'schema': 'Hotel,FAQPage', } def qshare_fetch_schema(lang): try: res = requests.post( ENDPOINT, params={**PARAMS, 'lang': lang}, # pass the current page's language headers={'x-source': 'qshare-ssr-py'}, timeout=10, ) res.raise_for_status() return res.text except requests.RequestException: # A Q-Share outage must never break the page. return None def get_qshare_schema_tag(lang): # Cache the result on the server (1 h or more) — not on every page view. schema = qshare_fetch_schema(lang) if not schema: return '' # no markup rather than a broken page # Escape "</" so an internal "</script>" cannot close the tag early. return ('<script type="application/ld+json">' + schema.replace('</', '<\\/') + '</script>')
Node.js
// qshare-ssr.js const ENDPOINT = 'https://q-share.quinta.im/schemas/generate-from-license' async function qshareFetchSchema(lang) { const params = new URLSearchParams({ license: '8b0ae-OVqI', lang, // pass the current page's language // Any schema from the dictionary (e.g. OfferCatalog, BreadcrumbList, // deep-select: Hotel[Address,Geo,Amenities]): schema: 'Hotel,FAQPage', }) const ctrl = new AbortController() const timer = setTimeout(() => ctrl.abort(), 10000) // 10 s timeout try { const res = await fetch(`${ENDPOINT}?${params}`, { method: 'POST', headers: { 'x-source': 'qshare-ssr-node' }, signal: ctrl.signal, }) return res.ok ? await res.text() : null } catch { return null // A Q-Share outage must never break the page. } finally { clearTimeout(timer) } } async function getQShareSchemaTag(lang) { // Cache the result on the server (1 h or more) — not on every page view. const schema = await qshareFetchSchema(lang) if (!schema) return '' // no markup rather than a broken page // Escape "</" so an internal "</script>" cannot close the tag early. const safe = schema.replace(/<\//g, '<\\/') return `<script type="application/ld+json">${safe}</script>` }
04 - Non-negotiable
The four rules
- Server-side cache (1 hour or more).Never call the API on every page view. Cache the response (file, Redis, memory) per language + schema-list pair.
- Silent fallback.If the API does not respond, emit no tag at all — or serve the last known cache. A Q-Share outage must never break the page.
- Escape
</.An FAQ response may contain HTML. Without escaping, an internal</script>would close the tag prematurely. - Dynamic language.Pass the language of the current page, not a fixed value. A hard-coded value renders all the markup in the wrong language.
Classic pitfall
The old client-side script often had data-lang="en" hard-coded. With SSR, make sure /fr/ pages really do request lang=fr.
05 - Final step · any language, any CMSInline the markup in the <head>
Whatever your site runs on — PHP, Python, Node.js or anything else — the principle is identical. The function above returns a complete <script type="application/ld+json">…</script> tag. All you have to do is print it in the <head> of every page, on every server-side render.
Server-side
The tag is there from the first HTML load — not added afterwards by the browser.
In the <head>
Place it just before </head>. Google also accepts the <body>, but the <head> is the convention.
Page language
Pass the language the current page actually displays, never a fixed value.
PHP
<head> <title>…</title> <!-- prints the <script type="application/ld+json"> returned by the function --> <?php echo qshare_schema($lang); ?> </head>
Python — Flask / Django (Jinja template)
# In the view: pass the ready-built tag to the template render_template('page.html', schema_tag=get_qshare_schema_tag(lang)) <!-- In the template, inside the <head> --> <head> <title>…</title> <!-- |safe: do not re-escape the HTML --> </head>
Node.js — Express (EJS template) or template literal
// Build the tag, then inject it into the <head> const schemaTag = await getQShareSchemaTag(lang) <!-- EJS template, inside the <head> --> <head> <title><%= title %></title> <%- schemaTag %> <!-- <%- : raw, unescaped output --> </head>
What they share
In all three cases you print the tag without re-escaping it (echo in PHP, |safe in Jinja, <%- in EJS) — it is ready-made HTML. Just adapt it to your template engine's syntax.
Leaving client-side behind?
If your site previously loaded script.min.js together with <script data-schemas="…"> tags, remove them or comment them out: SSR replaces them, and keeping both would produce duplicate markup.
06- Quality control
Check the result
- Read the raw HTML
curl -s https://your-site/ | grep ld+json— the block must appear without any JavaScript being executed. - Rich Results Testsearch.google.com/test/rich-results — paste the URL; Google renders the page and detects the schemas.
- Schema.org validatorvalidator.schema.org — checks that the syntax is valid.
- Language checkConfirm that a
/fr/page really does return French content inside the JSON-LD.
✓ Ready
Once these four checks are green, the markup is visible to every crawler and AI bot, continuously and without depending on the browser.