SEO - Generate structured data based on window.records values

One more thing to mention about the structure of the JavaScript code above.

Right now what it’s doing is waiting until the DOMContentLoaded event, then running a block of code every 100ms to see if jQuery has been loaded, then when jQuery is loaded, it runs the main body of its code.

This happens to work, but it’s got a couple of problems that could surface in the future in really mysterious ways.

The main problem is that jQuery being loaded is not actually the right thing to wait for. First of all, I don’t believe the body of the code actually depends on jQuery being loaded at all - it doesn’t use it. Second, the thing it actually should be waiting for is the data in window.records to be available. So I think you could remove the test typeof $ != 'undefined' and move the test of window.records up to that same spot where the jQuery test was.

That would also allow you to fix the other problem, which is that today when this code runs, if the main body has an error, the clearInterval() function never gets called, and that means this little block of code will run again every 100ms as long as the page is open. This could impact responsiveness from the user’s point of view. So the solution here is to call clearInterval() at the top of the body instead of the bottom.

1 Like

Hi @dcoletta , great analysis and feedback - I modified the original script from @artur, which wasn’t necessarily intended to load JSON-LD structured data.

Appreciate the suggestions - going to experiment a bit further.

1 Like

@irestmycase @dcoletta @artur
Would anyone be able to share that updated script? I’m managing to put together the JSON in a formula field in Airtable for my purposes (job post and organization structures), but to create the script is where I struggle.

Would much appreciate any code that could be shared.

@Suzie feels like a feature, or something to create a little tutorial for users? Since getting the pages listed on Google is so crucial for everyone :slight_smile:

I would strongly agree that, if this metadata is important for SEO, Softr should generate it automatically, and there should be a way in the studio to specify which fields of a List Details block should be included. The approach in this discussion topic is a proof of concept but it’s way outside what I think of as “no-code”.

Ironically, this is all totally obsolete already, as it would work way better for the whole internet for Google to use machine learning to generate the relevant metadata automatically.

1 Like

@Tim_ClimatEU this should actually work SEO - Generate structured data based on window.records values - #10 by irestmycase

1 Like

Confirming that it does! :rocket:

Hi all, thank you for the discussion, it is SUPER valuable. I somehow fail to make it work (we’re a bit off the “no-code” zone here).
This is what I did:

  1. in the Airtable table I added a column named “SEO:JSON-LD” with a formula that dynamically creates my JSON. I tested the JSON on the Google kit to test markup, and the JSON passed the test (

  2. in the Softr app, on the Page Custom Header Code, I inserted the exact script found above (reported below)

    document.addEventListener("DOMContentLoaded", function() {    
        var waitForData = setInterval(function () {
            if (typeof $ != 'undefined') {
                const recordId = getUrlParam('recordId');
                if (window.records && window.records[recordId] && window.records[recordId].record.fields['SEO:JSON-LD']) {
                    const jsonldScript = document.createElement('script');
                    jsonldScript.setAttribute('type', 'application/ld+json');

                    const structuredData = window.records[recordId].record.fields['SEO:JSON-LD'];
                    jsonldScript.textContent = structuredData;
        }, 100);

        function getUrlParam(name) {
            const url = new URL(window.location.href);
            let param;
            for(var key of url.searchParams.keys()) {
                if(key.toLowerCase() === name.toLowerCase()) {
                param = url.searchParams.get(name);
            if(!param && name.toLowerCase() === 'recordid') {
                param = getRecordIdFromPath();
            return param;
        function getRecordIdFromPath() {
            let pathName = window.location.pathname;
            if (pathName.indexOf('/r/rec') !== -1) {
                pathName = pathName.substr(pathName.indexOf('/r/rec') + 3);
                if (pathName.indexOf("/") !== -1) {
                pathName = pathName(0, pathName.indexOf('/'))
                return pathName;
            return undefined;
  1. I don’t think I am getting the result desired, as in “Inspect” from my Chrome browser, I get the following

I am pretty sure I am missing something. Can anyone help me find out what?


1 Like

I added that exact code in the page’s header’s custom code, but I have the same issue as @squatrito .

This is the error the console is giving me:

Does anyone have an idea what I might be doing wrong?

Just wanted to bump this up in case someone can helo @squatrito and me with the same problem. @artur @irestmycase

I’m also getting this error while trying to get an ID value from a record on a List Details page and then use that ID in a widget embedding script. I’m doing this so I can render a different widget for each item in the list.

<div class="calconic-calculator" data-calculatorid=""></div>
document.addEventListener("DOMContentLoaded", function() {
  var waitForData = setInterval(function () {
    if (typeof window.records !== 'undefined' && Object.keys(window.records).length > 0) {
      const calconicId = window.records[Object.keys(window.records)[0]].record.fields.CalconicId;
      var calculatorElement = document.querySelector('.calconic-calculator');
      calculatorElement.dataset.calculatorid = calconicId;
  }, 100);
  (function() {
    var qs, j, q, s, d = document, gi = d.getElementById, ce = d.createElement, gt = d.getElementsByTagName,
    id = "calconic_", b = "";
    if (!, id)) {
      j =, "script"); = id;
      j.type = "text/javascript";
      j.async = true;
      j.dataset.calconic = true;
      j.src = b + "calconic.min.js";
      q =, "script")[0];
      q.parentNode.insertBefore(j, q);