Dynamic language loading in Sanity schemas not currently supported

34 replies
Last updated: Apr 27, 2020
Hi there ☆I would like to change this `localeString.js`:

const supportedLanguages = [
  {
    name: 'fr',
    title: 'Français',
    isDefault: true, // important
  },
  {
    name: 'ja',
    title: '日本語',
  },
  {
    name: 'en',
    title: 'English',
  },
]

export default {
  name: 'localeString',
  title: 'LocaleString',
  type: 'object',
  fieldsets: [
    {
      title: 'Translations',
      name: 'translations',
    },
  ],
  fields: supportedLanguages.map(langx => (
    {
      name: langx.name,
      title: langx.title,
      type: 'text',
      fieldset: langx.isDefault ? null : 'translations',
    }
  )),
}
to make it point out instead of
const supportedLanguages
the data that are in a document called
lang.js
.this is `lang.js`:

export default {
    name: 'lang',
    title: 'Langue',
    type: 'document',
    fields: [
        {
            name: 'name',
            title: 'Name',
            type: 'string',
        },
        {
            name: 'title',
            title: 'Title',
            type: 'string',
        },
        {
            name: 'isDefault',
            title: 'IsDefault',
            type: 'boolean',
        },
    ],
}
In VISION I have this query:

*[_type == 'lang' ]{name, title, isDefault}
giving this result:

"result":[3 items
0:{3 items
"isDefault":false
"name":"ja"
"title":"日本語"
}
1:{3 items
"isDefault":true
"name":"fr"
"title":"Français"
}
2:{3 items
"isDefault":false
"name":"en"
"title":"English"
In the Docs the query example is:

const query = '*[_type == "bike" && seats >= $minSeats] {name, seats}'
const params = {minSeats: 2}

client.fetch(query, params).then(bikes => {
  console.log('Bikes with more than one seat:')
  bikes.forEach(bike => {
    console.log(`${bike.name} (${bike.seats} seats)`)
  })
})
So I modify
localeString.js
different ways but I am not able so far to find how to insert that into
fields
so it fits correctly in:
import client from 'part:@sanity/base/client'
import groq from "groq"

const query = groq `*[_type == 'lang']{
  name,
  title,
  isDefault
}`
const params = {}

export default {
  name: 'localeString',
  title: 'LocaleString',
  type: 'object',
  fieldsets: [
    {
      title: 'Translations',
      name: 'translations',
    },
  ],
  fields: [
    {
      client.fetch(query, params).then(langs => {
      langs.forEach(lang => ({
        name: lang.name,
        title: lang.title,
        type: 'text',
        fieldset: lang.isDefault ? null : 'translations',
      }))
      })
    }
  ]
}
I know that I miss the keys and everything with fields but I really don't know how I should do...
AI Update

The core issue you're facing is that Sanity schema definitions are synchronous and evaluated at build/startup time, but you're trying to use async GROQ queries to fetch data from documents to dynamically generate fields. Unfortunately, this pattern isn't supported in Sanity's schema system.

Here's why your current approach won't work:

fields: [
  {
    client.fetch(query, params).then(langs => {  // ❌ Can't use async/promises in schema
      langs.forEach(lang => ({
        // ...
      }))
    })
  }
]

The fields array expects synchronous field definitions, not promises. Even if you tried to make the entire schema export async, Sanity wouldn't process it correctly.

Based on the community discussion I found, here are your best options:

1. Define all possible language fields, use hidden to control visibility

Define all possible languages in your schema, then use the hidden property to conditionally show/hide them. The limitation is that hidden can only access the current document's data, not other documents via GROQ.

fields: allLanguages.map((lang) => ({
  name: lang.name,
  title: lang.title,
  type: 'text',
  fieldset: lang.isDefault ? null : 'translations',
  hidden: ({document}) => {
    // Can only check current document values
    // Can't do async GROQ queries here
    return !document?.selectedLanguages?.includes(lang.name)
  }
}))

This plugin is designed exactly for your use case - managing localized content with configurable languages. It provides a UI to select which languages to show and handles the field visibility for you.

3. Use a custom input component

Instead of trying to generate fields dynamically, create a custom input component that fetches the language configuration and renders the appropriate inputs:

import {useEffect, useState} from 'react'
import {useClient} from 'sanity'

export default {
  name: 'localeString',
  type: 'object',
  components: {
    input: (props) => {
      const client = useClient({apiVersion: '2023-01-01'})
      const [languages, setLanguages] = useState([])
      
      useEffect(() => {
        client.fetch('*[_type == "lang"]{name, title, isDefault}')
          .then(setLanguages)
      }, [])
      
      return (
        <div>
          {languages.map(lang => (
            <div key={lang.name}>
              <label>{lang.title}</label>
              <input
                value={props.value?.[lang.name] || ''}
                onChange={(e) => props.onChange({
                  ...props.value,
                  [lang.name]: e.target.value
                })}
              />
            </div>
          ))}
        </div>
      )
    }
  },
  fields: [] // Empty, handled by custom component
}

Check out the custom input widgets documentation for more details.

4. Store as a simple object with dynamic keys

Instead of predefined fields, store translations as a simple object:

{
  name: 'translations',
  type: 'object',
  // No predefined fields - just a flexible object
}

Then use a custom input component to manage the dynamic language keys based on your lang documents.

Bottom Line

The schema system is intentionally synchronous and static. For dynamic, content-driven field generation, you'll need to move that logic into custom input components or use existing solutions like the language-filter plugin. The custom component approach gives you full control to fetch your lang documents and render appropriate inputs at runtime.

Hi User, you’re pretty close already I believe 🙂 What’s the error you’re getting at the moment? Could you add
console.log(lang)
to the
forEach
loop to see if that output matches what you expect?
yes of course but I don't know how to look at console.log(lang).
What browser are you using?
firefox, chrome as you like. Right now firefox.
But before console.log() I have some errors displayed.
In Chrome, go to View &gt; Developer &gt; JavaScript console, or if you’re on a Mac, you can use CMD+ALT+J (not sure what the Windows shortcut is).
And feel free to paste those errors here 😉
Yes but where would you like the console.log(lang) to be placed?
From localeString, I query lang.js to get the references from lang.js for the languages
User, could you place the console.log(lang) in there please:
export default {
  name: 'localeString',
  title: 'LocaleString',
  type: 'object',
  fieldsets: [
    {
      title: 'Translations',
      name: 'translations',
    },
  ],
  fields: [
    {
      client.fetch(query, params).then(langs => {
      langs.forEach(lang => ({
        name: lang.name,
        title: lang.title,
        type: 'string',
        fieldset: lang.isDefault ? null : 'translations',
      }))
      })
    }
  ]
}
Please forgive me of being so clumsy.I did this:

import client from 'part:@sanity/base/client'
import groq from "groq"

const query = groq `*[_type == 'lang']{
  name,
  title,
  isDefault
}`
const params = {}

export default {
  name: 'localeString',
  title: 'LocaleString',
  type: 'object',
  fieldsets: [
    {
      title: 'Translations',
      name: 'translations',
    },
  ],
  fields: [
    {
      console.log(lang),
      client.fetch(query, params).then(langs => {
      langs.forEach(lang => ({
        name: lang.name,
        title: lang.title,
        type: 'string',
        fieldset: lang.isDefault ? null : 'translations',
      }))
      })
    }
  ]
}
and I got that:

ERROR in ./schemas/localeString.js
Module build failed: SyntaxError: C:\xampp\htdocs\<http://francois-vidit-2.com|francois-vidit-2.com>\studio\schemas\localeString.js: Unexpected token, expected "," (46:13)

  44 |   fields: [
  45 |     {
> 46 |       console.log(lang),
     |              ^
  47 |       client.fetch(query, params).then(langs => {
  48 |       langs.forEach(lang => ({
  49 |         name: lang.name,
Nothing fits.
If I understand correctly, you want to load your languages dynamically from your
lang
document type instead of using
const supportedLanguages
.
Have you tried something like this?

import client from 'part:@sanity/base/client'
import groq from "groq"
const query = groq `*[_type == 'lang']{
  name,
  title,
  isDefault
}`
const params = {}
export default {
  name: 'localeString',
  title: 'LocaleString',
  type: 'object',
  fieldsets: [
    {
      title: 'Translations',
      name: 'translations',
    },
  ],
  fields: 
    client.fetch(query, params).then(langs => {
    langs.map(lang => ({
      name: lang.name,
      title: lang.title,
      type: 'string',
      fieldset: lang.isDefault ? null : 'translations',
    }))
  })
}
yes completelythis is the error I get doing so:

The "fields" property must be an array of fields. Instead saw "object
you understand correctly.
Hi again. Actually, it’s not as straightforward as it seems. There’s nothing wrong with your approach per se, and it should actually return an array of language-specific fields, but it might be that Promises are not supported in type definitions. I’m checking this internally.
Thank you User.
Do you need more information?
I focusing on that point because my idea using Sanity is to avoid redondance of data: I would like all my localeSomething to point a single source of truth.
I feel better to realise that my question is been object of attention: I am a little bit more confident now.
Also I would like to link other field later on the same way.
We went on using the fetch method from that example.But on the Youtube video with User on initialValue, he is using async await.
I don't really understand the difference, I tried also that side.... miserabily!
It’s a perfectly valid approach - just not in type definitions perhaps if they don’t support Promises 🙂 I’ll let you know if there’s a workaround.
Yes 🌻 please!!!There must be one
👻
I’m afraid I have to confirm that async schemas are currently not supported, so you’ll have to go the traditional route with this for now. Sorry about the time you lost researching this issue but hopefully you still learned a few things to use elsewhere? 🙂
what do you mean by the traditional way?
Ah by
const supportedLanguages
?
I have already found a solution by having a central const
supportedLanguages
in a data folder and
import
it, but it is not really SANITY!!
I am glad to have an answer at least, it is now clear.
Indeed, that way 🙂 I’m glad you have a solution at least (and it’s a single source) 👍 Hopefully future versions will give you a way to make the languages load dynamically.
you mean that the team at Sanity consider this as sufficiently important to consider working on it?
In terms of principles, It cannot be denied... It would push the concept of Sanity to a accomplishment... I am surprised that you seem to be surprised!!
I would love to be able to do that in a near future... 🌜
If I may ask another question:in the Docs there is a piece of code:

function localize(value, languages) {
  if (Array.isArray(value)) {
    return value.map(v => localize(v, languages))
  } else if (typeof value == 'object') {
    if (/^locale[A-Z]/.test(value._type)) {
      const language = languages.find(lang => value[lang])
      return value[language]
    }
    
    return Object.keys(value).reduce((result, key) => {
      result[key] = localize(value[key], languages)
      return result
    }, {})
  }
  return value
}
Am I getting it right if I say that this function has to be placed not in Sanity but where the localized data arrives to be rendered (whatever the javascript system) (for instance, for me, Gatsby)?
It is not precise where this should be, what role it plays and it is not obvious to me...

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?