Join us live Sept. 27 – How Sanity and Vercel powered Morning Brew's transformation –>

Food Product Integrity

By Ronald Aveling

A schema to bring greater transparency to food sourcing.

schema.js

// First, we must import the schema creator
import createSchema from 'part:@sanity/base/schema-creator'

// Then import schema types from any plugins that might expose them
import schemaTypes from 'all:part:@sanity/base/schema-type'

// Documents
import product from './documents/product'
import ingredient from './documents/ingredient'
import vendor from './documents/vendor'
import certification from './documents/certification'
import classification from './documents/classification'
import nutrient from './documents/nutrient'
import award from './documents/award'

// Objects

import nutritionItem from './objects/nutritionItem'

// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
  // We name our schema
  name: 'Food Sourcing',
  // Then proceed to concatenate our document type
  // to the ones provided by any plugins that are installed
  types: schemaTypes.concat([
    // Objects
    //When added to this list, object types can be used as
    // { type: 'typename' } in other document schemas
    nutritionItem,
    // Documents
    product,
    ingredient,
    vendor,
    certification,
    classification,
    nutrient,
    award
  ])
})

documents/award.js

export default {
  title: 'Award',
  name: 'award',
  type: 'document',
  fields: [
    {
      title: 'Title',
      name: 'title',
      type: 'string',
    },
    {
      title: 'Description',
      name: 'description',
      type: 'text',
    },
    {
      title: 'Image',
      name: 'image',
      type: 'image',
    }
  ]
}

documents/certification.js


export default {
  title: 'Certification',
  name: 'certification',
  type: 'document',
  fields: [
    {
      title: 'Name',
      name: 'name',
      type: 'string',
    },
    {
      title: 'Description',
      name: 'description',
      type: 'text',
    },
    {
      title: 'Logo',
      name: 'image',
      type: 'image',
    },
    {
      title: 'URL',
      name: 'url',
      type: 'url'
    }
  ]
}

documents/classification.js

export default {
  title: 'Classification',
  name: 'classification',
  type: 'document',
  fields: [
    {
      title: 'Title',
      name: 'title',
      type: 'string',
    },
    {
      title: 'Description',
      name: 'description',
      type: 'text',
    }
  ]
}

documents/ingredient.js

export default {
  title: 'Ingredient',
  name: 'ingredient',
  type: 'document',
  fields: [
    {
      title: 'Name',
      name: 'name',
      type: 'string',
    },
    {
      title: 'Description',
      name: 'description',
      type: 'text',
    },
    {
      title: 'Image',
      name: 'image',
      type: 'image',
    },
    {
      title: 'Vendor',
      name: 'vendor',
      type: 'reference',
      to: [{type: 'vendor'}],
    },
    {
      title: 'Nutritional Data',
      name: 'nutritionData',
      type: 'array',
      of: [{
        type: 'nutritionItem'
      }]
    },
    {
      title: 'Certifications',
      name: 'certifications',
      type: 'array',
      of: [{
        type: 'reference',
        to: [{type: 'certification'}]
      }]
    }
  ]
}

documents/nutrient.js

export default {
  title: 'Nutrient',
  name: 'nutrient',
  type: 'document',
  fields: [
    {
      title: 'Name',
      name: 'name',
      type: 'string',
    },
    {
      title: 'Description',
      name: 'description',
      type: 'text',
    },
    {
      title: 'Type',
      name: 'type',
      type: 'string',
      options: {
        list: [
          {title: 'Protein', value: 'protein'},
          {title: 'Fat', value: 'fat'},
          {title: 'Sugar', value: 'sugar'},
          {title: 'Vitamin', value: 'vitamin'},
          {title: 'Mineral', value: 'mineral'}
        ]
      }
    },
    {
      title: 'Description Source',
      name: 'descriptionSource',
      type: 'url',
    },
    {
      title: 'Unit of Measure',
      name: 'unitOfMeasure',
      type: 'string',
      options: {
        list: [
          {title: 'Gram (g)', value: 'g'},
          {title: 'Milligram (mg)', value: 'mg'},
          {title: 'Microgram (µg)', value: 'µg'},
          {title: 'Kilojoule (kj)', value: 'kj'}
        ]
      }
    }
  ]
}

documents/product.js

export default {
  title: 'Product',
  name: 'product',
  type: 'document',
  fields: [
    {
      title: 'Name',
      name: 'name',
      type: 'string',
    },
    {
      title: 'Description',
      name: 'description',
      type: 'text',
    },
    {
      title: 'Price',
      name: 'price',
      type: 'number',
    },
    {
      title: 'SKU',
      name: 'sku',
      type: 'string',
    },
    {
      title: 'Product Image',
      name: 'image',
      type: 'image',
    },
    {
      title: 'Ingredients',
      name: 'ingredients',
      type: 'array',
      of: [{
        type: 'reference',
        to: [{type: 'ingredient'}]
      }]
    },
    {
      title: 'Certifications',
      name: 'certifications',
      type: 'array',
      of: [{
        type: 'reference',
        to: [{type: 'certification'}]
      }]
    },
    {
      title: 'Barcode',
      name: 'barcode',
      type: 'number',
    },
    {
      title: 'Classifications',
      name: 'classifications',
      type: 'array',
      of: [{
        type: 'reference',
        to: [{type: 'classification'}]
      }],
      description: 'Presence of allergens, meat, dairy, etc.',
    },
    {
      title: 'Awards',
      name: 'awards',
      type: 'array',
      of: [{
        type: 'reference',
        to: [{type: 'award'}]
      }],
      description: 'Gold medal, blue ribbons, press article, honorable mentions, etc',
    },
  ]
}

documents/vendor.js

export default {
  title: 'Vendor',
  name: 'vendor',
  type: 'document',
  fields: [
    {
      title: 'Name',
      name: 'name',
      type: 'string',
    },
    {
      title: 'Description',
      name: 'summary',
      type: 'text',
    },
    {
      title: 'Vendor Image',
      name: 'image',
      type: 'image',
    },
    {
      title: 'Certifications',
      name: 'certifications',
      type: 'array',
      of: [{
        type: 'reference',
        to: [{type: 'certification'}]
      }]
    },
    {
      title: 'Location',
      name: 'location',
      type: 'object',
      fields: [
        {title: 'Country', name: 'country', type: 'string'},
        {title: 'State', name: 'state', type: 'string'},
        {title: 'City/Region', name: 'city', type: 'string'},
      ]
    }
  ]
}

objects/nutritionitem.js

export default {
  name: 'nutritionItem',
  title: 'Nutrition Item',
  type: 'object',
  fields: [
    {
      title: 'Value',
      name: 'value',
      type: 'number',
    },
    {
      title: 'Type',
      name: 'type',
      type: 'reference',
      to: [{type: 'nutrient'}],
    }
  ]
}

This premise of the model is to bring greater transparency to food sourcing.

It includes a minimal set of common provisions for products such as sku and price.

It differentiates from regular product schemas with in the inclusion of product data related to ingredient sourcing. A Vendor document type relates to every product ingredient so that discerning consumers can learn about the supply-chain realities (where the vendor is situated, what certifications they have) that are linked to the food they consume. This transparency could be leveraged by ethical food producers to tell stories related to their sourcing choices. Fair Trade, B-Corp, Low Food Miles (relative to business owner), etc.

It also includes basic provision for Nutrition Data, Product Certification (Organic, Kosher, etc), Ingredient Certification, Awards, and Dietary Restrictions.

Conceptual applications

  • Promote critical thinking related to content modelling in Sanity,
  • or developing content modelling skills in general (see the image graph below)

Potential applications

  • Product labels and other printed marketing collateral
  • eCommerce Website
  • Native App

Notes

  • This is not a production-ready model. It lacks essential detail that can only be informed by real-world business needs.
  • It doesn't contain provision for rich text or block content. That could be added.
  • No customisations have been applied to:
    • Structure Builder
    • Previews
    • Field validation

Contributor