Skip to content
  • There are no suggestions because the search field is empty.

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

  1. 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.
  2. 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.
  3. Escape </.An FAQ response may contain HTML. Without escaping, an internal </script> would close the tag prematurely.
  4. 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 HTMLcurl -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.