Skip Navigation
Image showing stacked interfaces over a UI over a green background.

Deep Dive: Update your designs using Linked Data

Speed up collaboration with copywriters, translators and more by generating Linked Data from your designs to share with your team

In our last Deep Dive, we showed how you could use Linked Data to quickly apply custom data sets to your designs. Now, we’re focusing on how to significantly speed up the collaboration with copywriters, translators and other members of your team who help you with content specific to your design. For those eager to try this technique in their workflow, we also provide a ready-to-use script.

Design first, update (data) later

Image showing a Travel app interface in Sketch with some custom data.

Whether you are designing marketing material for your next campaign or working on your next big project, chances are that other stakeholders of the project will be involved eventually. It could be a UX-writer reviewing interface copy and proposing changes, or the legal team that wants a closer look at the copy in your consent form.

Using Linked Data in your designs

Some organizations have spreadsheets full of realistic placeholder data because you can’t access real APIs for security reasons. However, the more common scenario is when teams design brand and marketing assets once and then create countless variations from the main templates. The only differences between these assets and the designer’s original is the text and imagery.

When the people providing images and text aren’t designers, it’s more important than ever to have a seamless workflow between them and the rest of the team. To address this, you can extract the data from your design in plain text, then share it easily with your team. Once they’ve finished with it, you can pull it back into Sketch to update your design.

Workflow steps

  1. Create a design
  2. Extract data from your design as JSON file
  3. Update the data outside Sketch
  4. Refresh the Linked Data in your Sketch document

For now, we’ll assume that you already have a design you want to work with — so we’ll be starting by extracting the data from a document. Linked Data files have to follow your design’s layer hierarchy and you’ll need to name your entries in the same way as the layers they are meant for. Getting the structure right is one hurdle. The other is that writing JSON syntax can be tedious and prone to mistakes.

While there are code editors and command-line tools that can flag such issues, as a designer you may not be familiar with them. You are almost guaranteed to miss a comma or quote somewhere. Instead, we can let Sketch do the heavy lifting and produce valid JSON data from a selection using its JavaScript plugin API.

Extract Data from a design as JSON

Just want to go ahead and extract JSON Data from your document? Skip straight to the complete script below.

Sketch Data supports text and images. You can assign values to or derive them from text layers, image fills as well as symbol overrides of either, including for nested symbols. To define an image, include the file path to an image file relative to the JSON file.

[
  {
    "name": "Anje Keizer",
    "avatar": "/Faces/109.jpg",
    "location": "Bangkok",
    "bio": "Dog lover 🐕, mahjong champion 🀄️, and traveler 🗺 ",
    "social": {
      "handle": "@akeizer01",
      "bio": "Loving life and living in Dallas, go Mavs!"
    }
  }
]

Sketch stores any images within a document in that document’s bundle, the .sketch file, itself. For this guide, we won’t export these images alongside the JSON, but use placeholder values instead.

Extract for user selection and all sublayers

To create the initial data set from a selection, we’ll use a script traversing the selected layer and all its sub-layers to build up a data object following the same structure and naming as the layers. The result is automatically copied to the clipboard so we can quickly use it outside Sketch.

This script consists of two parts — a walk function for the traversal and a function to extract the data values from layers.

The first part is pretty straightforward. It adds the values of each layer to a single object and makes sure to handle layer groups differently than individual layers. It will only assign data values to (and extract them from) text layers, fills and override values of Symbol instances. For groups, it invokes the walk function recursively instead.

const walk = (layers, extract, initialValue) => {
  var value = initialValue

  for (const l of Array.from(layers).reverse()) {
    // layer groups can only create nested data objects, not values
    let v = isLayerGroup(l) ? walk(l.layers, extract, undefined) : extract(l)

    if (v === undefined) continue
    value = { ...value, [l.name]: v }
  }
  return value
}

Different data types

Getting the data from a layer is slightly more complex. We need to differentiate between the types of layers to correctly get the text or image data and name used as an object key.

Text layers are straightforward — their text value is all we need.

Symbol instances, on the other hand, need a bit more work. You’ll see their override values as a flat list, including any overrides stemming from nested symbols. For Sketch to later apply the data correctly, we need to reconstruct the nested data structure. We can use the override’s path value, which includes all Symbol IDs and the affected layer ID, each separated by slashes.

Lastly, for any layer that isn’t text or a Symbol instance, the script will check for image fills and again return a placeholder value for the image path.

Putting together all the data scripts

Below you’ll find the complete script, including the toData function that takes care of extracting the data for the different layer types. It also includes the mechanism to copy the JSON encoded data to the clipboard as well as providing feedback to the user, for instance when the selection is missing and to indicate the JSON has been successfully generated.

const { getSelectedDocument, Style } = require('sketch')
const { message } = require('sketch/ui')

function isLayerGroup(tbc) {
  return 'type' in tbc && tbc.type == 'Group'
}

const toData = (layer) => {
  switch (layer.type) {
    // text layers use the value
    case 'Text':
      return layer.text

    // symbol instances can have override values
    case 'SymbolInstance':
    case 'SymbolMaster':
      // ensure overrides for nested symbols won't be processed before the
      // actual symbol override and filter out any override values that cannot
      // be used with data
      let supportedProperties = ['symbolID', 'stringValue', 'image']
      let overrides = layer.overrides
        .sort((a, b) => a.path.localeCompare(b.path))
        .filter((val) => supportedProperties.includes(val.property))

      var data = {}
      var dataGroupByPath = { '': data }
      var hasValues = false

      for (const o of overrides) {
        let pathComponents = o.path.split('/')
        pathComponents.pop()
        let parentPath = pathComponents.join('/')

        if (o.property === 'symbolID') {
          dataGroupByPath[o.path] = {}
          dataGroupByPath[parentPath][o.affectedLayer.name] =
            dataGroupByPath[o.path]
          continue
        }

        dataGroupByPath[parentPath][o.affectedLayer.name] =
          o.property === 'image' ? '/path/to/image.png' : o.value
        hasValues = true
      }
      // We need to remove the nodes that don't have any values
      data = removeEmptyNodes(data)

      return hasValues ? data : undefined

    // other layers can have image fills, in case of multiple image fills only
    // the last one is used as override value
    default:
      let hasImageFill = layer.style?.fills.reduce((prev, curr) => {
        if (curr.type !== Style.FillType.Pattern) return prev
        return true
      }, false)

      if (!hasImageFill) break
      return '/path/to/image.png' // actual image not exported, placeholder instead
  }
  return undefined
}

const walk = (layer, extract, initialValue) => {
  if (!isLayerGroup(layer)) {
    return extract(layer)
  }

  var value = initialValue
  for (const l of Array.from(layer.layers).reverse()) {
    // layer groups can only create nested data objects, not values
    let v = isLayerGroup(l) ? walk(l.layers, extract, undefined) : extract(l)
    if (v === undefined) continue
    value = { ...value, [l.name]: v }
  }
  return value
}

let doc = getSelectedDocument()

if (doc.selectedLayers.length !== 1) {
  message('☝️ Select exactly one layer group to create data set.')
  return
}

let selected = doc.selectedLayers.layers[0]

let data = walk(selected, toData, undefined)

// `data` can be `undefined` if the symbol overrides
// in the selected layer are disabled
if (data === undefined) {
  message('☝️ No symbol overrides found.')
} else {
  // wrap data in array before encoding as JSON because Sketch expects a
  // set of values, not a single object
  let json = JSON.stringify([data], null, 2)

  // use native macOS pasteboard APIs to copy the JSON so it can be easily
  // pasted outside Sketch
  let pasteboard = NSPasteboard.generalPasteboard()
  pasteboard.clearContents()
  pasteboard.setString_forType(json, NSPasteboardTypeString)

  message('📋 Data copied to clipboard.')
}

function removeEmptyNodes(obj) {
  let hasEmptyNodes = false
  Object.entries(obj).forEach(([key, value]) => {
    if (Object.keys(value).length === 0) {
      delete obj[key]
      hasEmptyNodes = true
    } else if (typeof value === 'object') {
      obj[key] = removeEmptyNodes(value)
    }
  })
  return hasEmptyNodes ? removeEmptyNodes(obj) : obj
}

Extract data from your Sketch document

To put the script in action we’ll be using our Travel App sample project. Its hotel list and detail screens are great examples of designs for which you might want to ask your co-worker to provide representative sample content. We will be creating the initial data for the list of rooms in the hotel detail screen.

First, paste the script above into the script editor, and select Plugins > Run Script in the Sketch Mac app. Then select the group you want to convert and run the script to create your JSON.

You can close the script editor and re-run the script from the Plugins menu at any time or use the keyboard shortcut for further selections. Just ensure there’s only ever one group selected at the time. To save the JSON data, paste it into any text editor, such as macOS’ own TextEdit or Visual Studio Code, and save it.

You can save the script as a plugin from the Run Script… sheet to make it available directly from within the Plugins menu for future use.

Update and refresh

Now you have your JSON file, you can share it with other stakeholders in your team. For example, copywriters can make changes to the text right in the JSON file, then send it back to you to implement in the design. To do that, open Sketch > Preferences, select the Data tab, and add the JSON file. From there, you can select it as a data source by Ctrl-clicking the group, hovering over Data, and selecting your source.

Watch the video below to see the entire process from start to finish.

This example shows how you can use Sketch’s Linked Data feature without worrying about creating JSON files yourself. Generating the data from your design provides a template you or others in your team can work into directly.


Give the script a try with your own designs and let us know how it worked out for you. We hope these additions make it easier to work with realistic data in Sketch. And if you have thoughts or feedback about designing with data, get in touch.