Skip to main content

Schema Spec Reference

Every TakeShape project has a schema that defines its functionality. Users can configure most of a project's functionality in its JSON schema file. Configurable features include:

  • What the project's GraphQL API is capable of, including which API services it uses and what queries and mutations it exposes. (Read more in our Shapes guide)
  • Which workflows should be used when a project's content is edited. (Read more in our workflows guide)
  • What the GUI interface for editing a project's content should look like.

But the JSON schema file does not contain the entire project's schema. Instead, a TakeShape project's schema composes the editable JSON schema with other hidden configuration information.

There are hidden objects which are part of the project's schema, though they are not visible or configurable in the JSON schema file. These hidden objects are outlined at the end of this specification, and should be ignored in most cases.

There is also a service schema, which is hidden from the editable JSON schema file. The service schema is automatically updated when a new API service is added to a TakeShape project, and cannot be directly viewed or configured.

Learn more about the Service Schema:

This specification details all configurable functionality in a TakeShape project's JSON schema file.

Root objects​

Root-level objects in a project's schema define the project's high-level functionality.

TakeShape uses the queries, mutations and shapes objects to generate a GraphQL API every time the project is deployed. The services object configures API service connections. The forms object configures the interface for creating content. The workflows object configures content creation workflows. The locales and defaultLocale objects configure TakeShape's language localization features.

The following sections will explain the functionality and configuration of all of a TakeShape schema's root-level objects.

shapes​

A Shape is a schema object for structuring and storing data from one or more sources, including a TakeShape project's data store, and connected services like Shopify.

Shapes are analogous to GraphQL types. Learn more in our shapes guide.

Usage​

All shapes exist inside the root-level schema object shapes. The key for the shape should be the shape's name. For example, a shape to represent products should be called Product. A shape to represent reviews should be called Review.

example shapes object
"shapes": {
"Review": {
"id": "Review",
"name": "Review",
"title": "A custom Review shape",
"schema": {
...
}
}
}

Shape properties​

Because shapes represent data a TakeShape project needs to manipulate, they have several properties to identify them for reference in other areas of the schema and project. Those properties include:

id (required)​

The shape's ID, which will be used to reference it in other places in the schema. The value of the id key is a used in the shapeIds property of an @relationship and available as _shapeId on an item.

Changing a shape's ID orphans its data

When using TakeShape's built-in database to store shape data, the id value is used in the database as a stable "table name," and changing it will result in orphaning that data. Changing the ID back will make the data accessible again.

name (required)​

The name of the shape. Must be in pascal-case. This value must be the same as the shape's object key value. For example, a Review shape must have a name property of "Review"

The name property is important.

TakeShape uses the name property's value in multiple areas, including:

  • When @ref and @mapping annotations are needed.
  • During the automatic generation of queries and mutations for models, like getShape and getShapeList
  • In the _shapeName property on an item.

title (required)​

The shape's title. The value of the title property will appear as the shape's title in the TakeShape web client's UI.

workflow​

Takes the name of the workflow this shape should be scoped to as a string.

description​

A description of the shape that will appear in TakeShape's GUI. This description appears in the Docs Panel.

model​

The data model TakeShape should use to store instances of this shape. Valid properties include multiple, single, and taxonomy.

schema​

Because shapes function like GraphQL types, queries and mutations can return instances of them. The fields available to such queries must be configured in the schema property.

The schema object contains three configuration properties:

Shape schema config​

type​

The value of this property should always be "object". It indicates the GraphQL type of the shape, which is always an object.

required​

An array of strings, which match the keys of the properties that are required when creating an instance of this shape.

For example, if you have a Product shape with properties price, name and id, and all three are required when creating a Product instance, your required array will look like this:

"required":["price", "name", "id"]
properties​

This object can have an arbitrary number of nested objects within it, each of which will be fields for the shape. If you have a Product shape that has a price field, you might configure the properties like so:

"Product": {
"id": "jadxIUkZV",
"name": "Product",
"title": "Product",
"schema": {
"type": "object",
"properties": {
"price": {
"type": "number",
"description": "The product's price",
"title": "Product price"
}
}
}
}

Shape fields​

When creating a field in the properties object, the only required property is type, which is the GraphQL type of the data the property represents, like string, boolean, integer, etc. But there are many other possible properties, as demonstrated in the example below.

properties example
"properties": {
"customId": {
"type": "integer",
"description": "A custom ID property for this shape.",
"title": "This shape's custom ID.",
"minimum": 0
},
"handle": {
"type":"string"
},
"myResolvedProperty": {
"description": "A custom property that uses @resolver.",
"title": "My Resolved Property.",
"@ref": "shopify-storefront:Product",
"@resolver": {
"name": "graphql:query",
"service": "shopify-storefront",
"if": "$source.handle",
"fieldName": "productByHandle",
"args": {
"ops": [{"path": "handle", "mapping": "$source.handle"}]
}
}
}
}

The list of fields possible for a property include:

type (required)​

The GraphQL type of the property. Possible values:

  • array
  • boolean
  • integer
  • number
  • string
  • object
type isn't always required.

Using the @ref annotation indicates that a field is an instance of a shape defined elsewhere in the schema. That means you shouldn't use type if you use @ref. Otherwise, type is required.

Configuring object fields​

If the field's type is object, then properties will also be required. Within this nested properties object, you can add the properties of the object field. For example:

Shape with an object property example
"Product": {
"id": "Product",
"name": "Product",
"title": "My Custom Product",
"schema": {
"type": "object",
"properties": {
"variants": {
"type": "object",
"properties": {
"variantId":{
"type":"integer",
"description": "The ID for this variant"
},
"variantTitle":{
"type":"string",
"title": "Variant Title"
}
}
},
"id": {"type": "integer"},
"price": {"type":"number"}
}
}
}

When querying for the above Product, the properties will be accessible as fields:

Product query example
getProduct(id:123){
id
price
variant {
variantId
variantTitle
}
}

Objects can be nested arbitrarily deeply, and fields defined within objects can have the same annotations as top-level fields, like @resolver, @input, etc.

Configuring array fields​

Array fields require the items object, which allows you to specify the data type of the items in the array. The items object can be configured the same as a standard field, meaning it requires either an @ref or a type, and can have any of the other properties specified for fields in this section, including annotations.

For example, here is a ShoppingCart shape that contains an array of Product instances, and an array of Coupon instances.

Array field example
"ShoppingCart": {
"id": "PShoppingCartroduct",
"name": "ShoppingCart",
"title": "My Custom Shopping Cart",
"schema": {
"type": "object",
"properties": {
"products": {
"type": "array",
"items": {"@ref":"local:Product"}
},
"coupons": {
"type": "array",
"items": {
"type": "object",
"properties": {
"couponCode": {"type":"string"},
"discountValue": {"type":"number"}
}
}
}
}
}
},
"Product": {
"id": "Product",
"name": "Product",
"title": "My Custom Product",
"schema": {
"properties": {
"title": {"type":"string"},
"price": {"type":"integer"}
}
}
}
You can use @ref in nested fields.

In the example above, ShoppingCart has two array fields. The first, products, is an array with items that are instances of the Product shape, which is declared later in the project schema. The second, coupons, is defined within the ShoppingCart shape.

The benefit of declaring a shape in the schema is that you can re-use it everywhere with the @ref annotation. But if a field has a simple configuration, and won't be used anywhere else, it makes sense to define it within the shape that uses it.

description​

A description of the property that will appear in the TakeShape GUI. For example, you will see a property's description in the docs panel of the API Explorer when you hover over it.

title​

The title of the field that will appear in the TakeShape GUI.

minLength​

An integer minimum length for the value of the property.

minimum​

The minimum value possible for a property whose type is set to integer.

Shape annotations {shape-annotations}​

Annotations are properties on fields that enable extended functionality. Similar to GraphQL directives, annotations enable defining a field as an instance of a shape defined in the schema, attaching a query resolver to a field so that its data can be fetched from remote API services, and more.

No annotation is ever required, but @ref can be used in place of the type property on a field.

The following a list of all valid annotations.

@args​

This annotation lets you define the schema of the arguments that can be passed to a field that uses an @resolver. Because the @resolver can query remote API services, @args should be used for defining the arguments that will be used when querying a service.

For example, here's what an @args annotation looks like on a field with a resolver that fetches a Shopify product:

@args example
"Product": {
"id": "Product",
"name": "Product",
"title": "Product",
"schema": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"shopifyProduct": {
"title": "Shopify Product",
"description": "A field that returns a shopify product",
"@ref": "shopify:Product}",
"@args": {
"type": "object",
"properties": {
"handle": {"type":"string"},
"id": {"type":"string"}
}
},
"@resolver": {
"if": "!isEmpty($args.handle) || !isEmpty($args.handle)",
"name": "graphql:query",
"service": "shopify",
"fieldName": "product",
"args": {
"ops":[
{"path": "handle", "mapping": "$args.handle"},
{"path": "id", "mapping": "$args.id"}
]
}
}
}
}
}
}

When querying for the above product, the id or handle can be passed to the shopifyProduct field.

getProduct(id:123){
shopifyProduct(handle:"example-product"){
title
id
handle
}
}

The fields available on shopifyProduct are defined in the Shopify GraphQL API spec, since shopifyProduct is an instance of shopify:Product. To learn more about using @ref this way, read our section on using @ref below.

@ref​

This annotation takes the scoped name of a shape that exists in a project's schema. It's used when a field is an instance of a shape. It's useful when a field has many nested subfields, or when a field is an instance of a shape defined elsewhere in the schema.

For example, if you have a User shape that you want to associate Stripe customer data with, you might define a stripeCustomerData field on the User shape as shown below:

@ref example
"User": {
"id": "User",
"name": "User",
"title": "User",
"schema": {
"type": "object",
"properties": {
"stripeCustomerData": {
"@ref": "stripe:Customer",
"description": "The stripe customer data associated with this user."
},
"username": {
"type": "string",
"description": "The username associated with this user."
}
}
}
}

The above example creates a stripeCustomerData field that can return Stripe customer data, but an @resolver annotation is required to define how that data can be fetched from stripe. read our section on @resolver below ot learn more.

Referencing scoped shapes with @ref​

To define a field as an instance of a shape, that shape must fulfill one of three conditions:

  • It must be defined in the JSON schema, either manually or automatically via the visual editor.

  • It must be a hidden schema object, all of which are listed at the end of this spec.

  • It must be defined in the hidden service schema.

The service schema is neither visible nor directly editable, but all TakeShape projects have them. They contain configuration information for connected API services, like Stripe or ShipEngine. TakeShape projects automatically update the service schema whenever a service is added or deleted.

To create a field that is an instance of a shape from a connected service, you must scope the shape with the service name and a colon, like so:

"@ref":"shopify:Product"

The format is service:ShapeName.

Service schema errors can happen

When a service is connected with a generic REST provider, or a generic GraphQL provider, there is a chance TakeShape will fail to add its API configuration to the service schema.

In such cases, shapes from the service must be manually defined in the JSON schema file. Trying to reference them from the service schema will fail.

Defining a field as an instance of a shape defined in the JSON schema file requires the use of the local scope, like so:

"@ref":"local:Product"

The format is local:ShapeName.

@resolver​

An annotation that enables fields to have return data from remote services when queried.

For example, a User shape with a stripeCustomerData field should have customer data from stripe when that field is queried. To ensure that data is there, an @resolver annotation must be used to trigger a query to stripe's customers endpoint.

Example query for stripeCustomerData
{
getUser(id: 123){
username
# Including a below field will cause the query
# in @resolver to be executed before a response
# is returned.
stripeCustomerData(stripeCustomerId: "cus_ABC123pxLtTYUM"){
email
phone
}
}
}

Here's how the User shape might be defined:

@resolver example
"User": {
"id": "User",
"name": "User",
"title": "User",
"schema": {
"type": "object",
"properties": {
"stripeCustomerData": {
"@ref": "stripe:Customer",
"description": "The stripe customer data associated with this user.",
"@resolver": {
"name": "rest:get",
"service": "stripe",
"options": {"ignoreErrors": true},
"path": {
"ops": [
{"path": "customerId", "mapping": "$args.stripeCustomerId"}
],
"serialize": {"template": "/v1/customers/{customerId}"}
}
},
"@args": {
"type": "object",
"properties": {
"stripeCustomerId": {
"type": "string"
}
}
}
}
},
"username": {
"type": "string",
"description": "The username associated with this user."
}
}
}

@mapping​

This annotation is usually generated by TakeShape for shapes created in the visual editor. Editing this value can cause a loss of data. It maps instances of the shape to a table in a TakeShape project's database. Learn more in our @mapping section below.

@input​

This annotation enables overriding the input type for the field, which can be useful when setting up fields that store foreign keys and resolver data from other services. The @input properties will overwrite the field's original properties, with the exception of @mapping, which is preserved.

You cannot rely on any property inheritence from the original field when using an @input annotation.

For example, this ProductPage Shape accepts a string as input for its product field but returns a product from Shopify when that field is queried.

{
"ProductPage": {
"id": "ProductPage",
"name": "ProductPage",
"title": "ProductPage",
"schema": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"product": {
"@ref": "shopify:Product",
"@resolver": {
"name": "graphql:query",
"service": "shopify",
"fieldName": "product",
"args": {
"ops": [{"path": "id", "mapping": "$source.product"}]
}
},
"@input": {
"type": "string"
}
}
}
}
}
}
Keep your shapes as flat and composed as possible

Rather than deeply nesting and copy-pasting objects to create fields, complex shapes are best composed with the @ref and @relationship annotations.

Hidden properties

Shape schemas also have a number of hidden properties that are automatically managed by TakeShape, like _created and _updated, which record the dates shape items are created and edited. Though you cannot manipulate them, remember that their names are reserved, and fields with those names cannot be created.

Learn more in our shapes guide.

indexedShapes​

You can add shapes to the indexedShapes object to choose what data TakeShape's indexing function will cache, and explicitly configure how it will do so. For a detailed walkthrough of configuring indexedShapes in your project, check out our API Indexing guide.

Valid shape properties in indexedShapes​

To configure a shape to be indexed, you must declare it inside the indexedShapes objects and define which queries will be used to fetch instances of that shape from a remote API service.

Below are the valid properties for configuring a shape to be indexed:

queries (required)​

This object contains the queries that the API Indexer will use to index remote API data. Valid properties are list and get. See the section on valid list and get properties below for more information.

Example:

"indexedShapes": {
"Shopify_Product": {
"queries": {
"list": {
"name": "Shopify_products",
"objectDepthLimit": 1
},
"get": {
"name": "Shopify_product"
}
}
}
}

triggers (required)​

This array contains objects that define the conditions under which the indexing function will trigger its queries. See the section on valid triggers properties below for more information.

Example:

  "triggers": [
{
"type": "schedule",
"query": "list",
"interval": 1
},
{
"type": "webhook",
"query": "get",
"service": "yourServiceIdHere",
"events": ["names", "of", "events", "here"]
}
]

searchSummaryField​

This property takes a string that will be the name of the field in the indexed shape that you want to use as the searchSummary. Useful for customizing the results returned for this shape when running the search query against your project's API.

Learn more about using the search query in our search recipe.

idField​

This property takes a string that is the name of the property in the indexed shape that will return the shape's ID. Useful for customizing the results for this shape when running the search query on your project's API.

NOTE

If the shape being indexed doesn't have a field called id, this should be set to whichever field is the actual unique identifier.

Valid triggers properties​

type** (required)​

A string defining the trigger's type. Valid values are schedule and webhook.

query (required)​

A string specifying which query the trigger corresponds to. Valid values are get and list.

interval** (required for schedule)​

Valid only for schedule triggers. The integer number of minutes between each time the indexing function will run.

service (required for webhook)​

Valid only for webhook triggers. The string ID of the service in your schema that has the webhooks to trigger this query.

events (required for webhook)​

Valid only for webhook triggers. The array of string names of webhook events that can trigger this query.

Example:

  "triggers": [
{
"type": "schedule",
"query": "list",
"interval": 1
},
{
"type": "webhook",
"query": "get",
"service": "bigcommerce",
"events": ["store/cart/created"]
}
]

Valid list and get properties​

Within your list or get queries, you can also configure how TakeShape's indexing function can find the information in the result of the query.

name (required)​

The string name of the query to run.

Example: "Stripe_listCustomers"

objectDepthLimit​

The integer number of levels into the query response object's nesting that TakeShape's indexing function should preserve. Defaults to 3.

ignoreFields​

The array of string names of properties that TakeShape's indexing function should ignore when querying the API Index.

pagination​

Valid only for list queries. A configuration object that allows you to specify how TakeShape's indexing function fetches paginated data. See the section below on valid pagination properties for more information.

Example:

"pagination": {
"type": "cursor",
"cursorPath": "data[(@.length-1)].id",
"itemsPath": "data",
"hasMorePath": "has_more",
"cursorArg": "starting_after"
}

Valid pagination properties​

Every pagination configuration has a type property, which specifies the type of pagination. Below are the properties that are valid for all pagination types:

NOTE

All path property values are parsed with jsonPath syntax.

type (required)​

The type of pagination, which can be one of cursor, offset, or page.

Example:

"pagination": {
"type": "cursor",
"cursorPath": "edges[(@.length-1)].cursor",
"itemsPath": "edges",
"itemPath": "node",
"hasMorePath": "has_more",
}

Below are other properties categorized by which pagination type they're appropriate for.

cursor pagination​
  • cursorArg (required) β€” The string name of the query argument that accepts the cursor value for the start of the next page.

  • cursorPath (required) β€” The string path to the cursor returned in the query's response object.

  • hasMorePath (required) β€” The string path to the property in the query's response that indicates whether there are more pages to return.

  • itemsToIndexPath (required) β€” The path to the array of items to index in the query's response.

  • pageSize (optional) β€” The number of items to fetch in each page.

  • pageSizeArg (required) β€” The string name of the of the argument passed to the pagination query to indicate the size of the desired result.

Example: "first"

offset pagination​
  • offsetArg (required) β€” The string name of the offset argument to use when querying the API.

  • itemsToIndexPath (required) β€” The path to the array of items to index in the query's response.

  • itemTotalPath (required) β€” The path to the property which contains the number representing the total items.

page pagination​
  • pageArg (required) β€” The string argument passed to the pagination query to indicate where the pagination slicing should begin or end.

  • pageTotalPath (required) β€” The path to the total number of pages in the response data.

  • itemsToIndexPath (required) β€” The path to the array of items to index in the query's response.

Examples​

This example shows how you might map a Stripe list API call to an object in indexedShapes for API Indexing.

Consider this response object from the customers/list endpoint of the Stripe API:

{
"object": "list",
"url": "/v1/customers",
"has_more": false,
"data": [
{
"id": "cus_KmAUtL4NbulKr4",
"object": "customer",
"address": null,
"balance": 0,
"created": 1639492731,
"currency": "usd",
"default_source": null,
"delinquent": false,
"description": null,
"discount": null,
"email": null,
"invoice_prefix": "F5DABA5",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": null,
"footer": null
},
"livemode": false,
"metadata": {},
"name": null,
"next_invoice_sequence": 1,
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
},
{...},
{...}
]
}

Here is how you might configure your shape for indexing:

{
"indexedShapes": {
"Stripe_Customer": {
"queries": {
"list": {
"name": "Stripe_listCustomers",
"objectDepthLimit": 2,
"pagination": {
"type": "cursor",
"cursorPath": "data[(@.length-1)].id",
"itemsToIndexPath": "data",
"hasMorePath": "has_more",
"cursorArg": "starting_after",
"pageSizeArg": "limit"
}
}
},
"triggers": [{"type": "schedule", "query": "list", "interval": 1440}]
}
}
}

Since this is a list query, when you configure the shape in your indexedShapes object, you want to put it under a list object. The name will be the name of the query as it appears in your schema. The interval and objectDepthLimit can be arbitrarily set according to your needs.

pagination

type β€” Stripe's API uses cursor-based pagination, so the type in the pagination object should be cursor.

cursorPath β€” Because Stripe's API returns no specific cursor value, we'll specify the path to the cursor as a jsonPath string. This string will point to the id of the last object in the data array in the response object.

itemsToIndexPath β€” This is a jsonPath string specifying the path to the array of items expected in the response object. In this case, that array is called data.

hasMorePath β€” Stripe's API provides a has_more field at the root level of the response object, so you should set this value to "has_more".

cursorArg β€” You can check Stripe's documentation for the possible cursor arguments.

pageSizeArg β€” As Stripe's documentation shows, list queries take a limit argument to indicate the size of each result.

services​

By connecting new Services to your project schema, you can add shapes, queries, and mutations from 3rd party sources to your API in TakeShape. This allows you to mesh together exactly the API you need to interact with all of the services your app or business depends upon.

Services use an encrypted configuration to keep auth tokens secret. For this reason, services can only be added to a schema through the web client or through the Admin API.

Guides

Adding GraphQL services, shapes, and queries

Adding REST services, shapes, and queries

queries & mutations​

Queries and Mutations in your schema map directly to queries and mutations in your project's GraphQL API. In the schema you can define behaviors for these queries and mutations that span one or more services.

Though they describe different functionality, query and mutation objects both have the same set of properties:

shape (required)​

This string property specifies which shape is returned by the query or mutation. The shape can refer to a shape in the schema's shapes object or a shape that's available on a service schema.

resolver (required)​

This configuration object describes the resolver for a query or mutation.

description​

This string property provides more detail about what the query or mutation is for. This will be displayed in the automatically-generated GraphQL API docs.

args​

The args field takes the name of one of the shapes you have defined in your schema as a string. This shape will be used to define the arguments your query or mutation can take as inputs. If your query or mutation takes no arguments, you can omit this string.

Guides

Working with Queries & Mutations

forms​

Forms define a visual interface for editing Shapes. In most cases, users do not need to edit the forms object directly. TakeShape populates the forms object with new data when a user creates shapes in the visual editor.

Example​

"Sneakers": {
"default": {
"order": ["sneakerShopId", "sneakerShop"],
"properties": {
"sneakerShopId": {
"instructions": "Format: gid://shopify/Product/11111111",
"label": "product ID",
"widget": "serviceObjectId",
"provider": "shopify",
"serviceObjectType": "product",
"service": "sneaker-shop"
},
"sneakerShop": {
"properties": {
"title": {
"widget": "serviceObjectProperty",
"provider": "shopify"
}
},
"widget": "shopify",
"wrapper": "shopifyServiceWrapper",
"order": ["title"]
}
}
}
}

workflows​

Workflows describe the status of a shape item. Every schema starts with a default workflow with two steps: Disabled and Enabled.

Each step can be configured to have a custom name, title, and color. Steps have a live value to indicate whether items in the state should be returned in list queries.

Developer plans are limited to the default workflow. Professional projects can add new workflows and specify them on a per-shape basis. Learn more about professional plans

Example​

This is the default workflow that is applied to every project. It has two states: Disabled and Enabled.

"default": {
"name": "default",
"title": "Default",
"steps": [
{
"name": "disabled",
"title": "Disabled",
"key": "r1uCfi4ZL",
"color": "#bdbdbd",
"live": false
},
{
"name": "enabled",
"title": "Enabled",
"key": "rkhRGs4WL",
"color": "#5cd79b",
"live": true
}
]
}

locales​

Locales are an array of IETF language tags your project supports for internationalization. By default, projects have a single en locale which is also the default locale.

Developer projects are limited to 2 locales. Professional projects can have up to 5 locales. Learn more about professional plans

Metadata properties​

Also at the root of the schema are a number of properties that are mainly used by the web client and API. As a schema author, you should almost never need to edit these fields.

version​

This is the revision number of your schema. Every time your project schema is updated, this value should be incremented. The version is used to identify versions in your schema's history.

projectId​

The ID of the TakeShape project this schema belongs to.

defaultLocale​

The locale that should be preferred when creating new Shape items. This must be an entry in the **locales** array.

author​

The ID of the TakeShape user who created the schema.

created​

The date the schema was created

updated​

The date the schema was last updated

schemaVersion​

The version of the schema format your project is using. We increase the version as we make breaking changes to the schema format. The current version is 3.

apiVersion​

The version of the TakeShape API your project is using. We increase the version as we make breaking changes to the API endpoints.The current version is 2.

Resolvers​

A resolver is a function that executes when a query or mutation is called, or a shape field is accessed. A resolver is required on queries and mutations, and may be used in a shape property.

Below are the valid properties for a resolver.

name (required)​

The name of the resolver your query or mutation should use. There are three types of resolvers:

  1. GraphQL resolvers
  2. REST resolvers
  3. TakeShape resolvers

service (required)​

Specifies the endpoint and authentication configuration the resolver should use, identified by a connected service's slug. For example:

  • graphql:rick-andmorty
  • rest:open-weather
  • takeshape:local

options​

Each type of resolverβ€”GraphQL, REST, or TakeShapeβ€”takes its own set of options. These are specified in the Built-in resolvers section below.

Mapping arguments​

Because a resolver can be used to fetch data from remote APIs, TakeShape allows you to map values available in a query's context to the inputs of a remote API's query.

That is to say, if a resolver is executing a query that requires an input, you can map data to that input.

There are four objects that can be used to map data to a query's input:

  • args for graphql queries
  • json for queries that expect json
  • form for queries that expect a form
  • body for queries that expect a string
argsMapping is deprecated

We previously recommended using the argsMapping object instead of the above options, but that method has been deprecated.

Instead, use one of the above objects.

To learn more about argsMapping, check out our deprecated page.

In general, you map values to query inputs using parameter ops, which means the input object has an ops property with an array of objects in them. These objects will contain mappings. Here's a generic example that maps arguments from a query to JSON:

"resolver": {
"name": "rest:post",
"service": "service-name-here",
"path": "/service-endpoint-here",
"json": {
"ops": [
{"path": "customer.source_id", "mapping": "$args.email"},
{"path": "status", "mapping": "$args.status"},
{"path": "amount", "mapping": "$args.amount"},
{"path": "items", "mapping": "$args.items"},
{"path": "items[*].id", "op": "remove"},
{"path": "items[*].name", "op": "remove"}
]
}
}

You can figure out what inputs your service requires by reading their docs. Once you know that, it's a matter of mapping data available to your TakeShape query to the service's query's inputs.

The following sections will give examples of the different ways you can map inputs with TakeShape.

args​

Necessary for mapping values to graphql query arguments. Shopify Admin, Shopify Storefront, and BigCommerce Storefront are popular examples of graphql APIs you might use.

For example, let's say you have a getMyCustomer query that fetched a Shopify customer. Fetching a customer from Shopify Storefront's API requires a customerAccessToken. Assuming you're passing an access token to the getMyCustomer query, you would map that argument to the Shopify query in the resolver like this:

"getMyCustomer": {
"resolver": {
"name": "graphql:query",
"service": "shopify-storefront",
"args": {
"type": "object",
"properties": {
"customer_access_token": {
"type": "string",
"description": "The customer access token necessary to fetch this customer from shopify."
}
},
"required": ["customer_access_token"]
},
"fieldName": "customer",
"args": {
"ops": [
{"path": "customerAccessToken", "mapping": "$args.customer_access_token"}
]
}
},
"shape": "ShopifyStorefront_Customer"
},

json​

Necessary for mapping values to the input of a query that expects JSON.

For example, if you had a createMyCustomer query that created a customer in Shopify's REST Admin API using their /customers.json endpoint, your resolver might look like this:

"resolver": {
"name": "rest:post",
"service": "shopify",
"path": "/admin/api/2022-04/customers.json",
"options": {
"endpoint": "https://deluxe-sample-project.myshopify.com/"
},
"json": {
"ops": [
{
"path": "customer.first_name",
"mapping": "$args.input.firstName"
},
{
"path": "customer.last_name",
"mapping": "$args.input.lastName"
},
{"path": "customer.email", "mapping": "$args.input.email"},
{
"path": "customer.password",
"mapping": "$args.input.password"
},
{
"path": "customer.password_confirmation",
"mapping": "$args.input.password"
},
{"path": "customer.phone", "mapping": "$args.input.phone"},
{"path": "customer.send_email_welcome", "value": false},
{"path": "customer.verified_email", "value": true}
]
}
}

form​

Necessary for mapping values to the input of a query that expects form data.

To pass url-encoded form data, you could use the $ character to map it directly to the input rather than to any specific arguments or paths. Your resolver might look like this:

"resolver": {
"name": "rest:post",
"service": "shopify",
"path": "/admin/api/2022-04/customers.json",
"options": {
"endpoint": "https://deluxe-sample-project.myshopify.com/"
},
"form": {
"ops": [
{"path": "$", "mapping": "$args.input"}
]
}
}

body​

Necessary for mapping values to the input of a query that expects an object. Can also be used to map values to the input a query that expects a string.

For example, if you wanted to make a query that added a newsletter member to a Klaviyo email list, you would have to use their /members endpoint, which expects an object.

Here's the example curl request from their docs:

curl --request POST \
--url 'https://a.klaviyo.com/api/v2/list/LIST_ID/members?api_key=API_KEY' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '
{
"profiles": [
{
"email": "george.washington@klaviyo.com"
},
{
"phone_number": "+13239169023"
}
]
}
'

Here's what the resolver for a subscribeMyEmailToNewsletter query that adds users to a mailing list might look like:

"subscribeMyEmailToNewsletter": {
"shape": "Klaviyo_AddMembersResponse",
"resolver": {
"name": "rest:post",
"service": "klaviyo",
"body": {
"ops": [{"path": "profiles[0].email", "mapping": "$claims.email"}],
"serialize": {"content": {"contentType": "application/json"}}
},
"path": {
"ops": [{"path": "list_id", "mapping": "$args.list_id"}],
"serialize": {
"template": "/v2/list/{list_id}/members",
"paths": {"list_id": {"style": "simple"}}
}
},
"headers": {
"ops": [{"path": "content-type", "value": "application/json"}]
},
"results": {"ops": [{"path": "items", "mapping": "$resolvers[0]"}]}
},
"args": {
"type": "object",
"properties": {"list_id": {"type": "string"}},
"required": ["list_id"]
}
},

In depth input mapping​

It's important to remember that "input" must first be defined as a property on your args shape. Also, it does NOT have to be called "input". It could be anything. But it has to be defined in your args shape, and then must be referenced by the same name in your args/json/body/form.

Your args can take the name of a shape in your schema, as described in this section above, or a JSON object literal that defines the shape you want. Your args mapping will then depend on the properties of the shape you've assigned to the args property.

As an example, here is a shape you could use as args for a query:

  "ExampleArgs": {
"id": "your-custom-id",
"name": "ExampleArgs",
"title": "ExampleArgs",
"schema": {
"type": "object",
"properties": {
"input": {"type": "object"},
}
}

And if you wanted to make a mutation with arguments defined in the above shape, then you'd add your above shape to your mutation's args property:

  "exampleMutation": {
"description": "Makes an example mutation",
"shape": "ShapeOfResponse",
"args": "ExampleArgs",
"resolver": {
"name": "rest:post",
"service": "your-service-slug",
"options": {"path": "your-endpoint-path"},
"form": {
"ops": [
{"path":"$", "mapping":"args.input"}
]
}
}
}

Alternatively, if you don't want to define the shape of your args separately, your mutation shape could look like this:

"exampleMutation": {
"description": "Makes an example mutation",
"shape": "ShapeOfResponse",
"args": {
"type": "object",
"properties": {
"input": {"type": "object"},
},
"resolver": {
"name": "rest:post",
"service": "your-service-slug",
"options": {"path": "your-endpoint-path"},
"form": {
"ops": [
{"path":"$", "mapping":"args.input"}
]
}
}
}
NOTE

If you're using the body property to map a string to an input, you have to set the type of your input argument to "string":

  "ExampleArgs": {
"id": "your-custom-id",
"name": "ExampleArgs",
"title": "ExampleArgs",
"schema": {
"type": "object",
"properties": {
"input": {"type": "string"},
}
}

Or:

  "args": {
"type": "object",
"properties": {
"input": {"type": "string"}
}
}

You can learn more about mapping GraphQL query inputs to REST parameters in our doc on the subject.

Appending inputs to your query's path​

Sometimes you may want to take a user's input and append it to the path of the endpoint that the resolver will hit when executing the query.

For example, you may want to take an input, which the user might specify as "myCustomPath", and append it to the resolver's path so the query's request goes to https://your-custom-endpoint.com/your-endpoint-path/myCustomPath.

To do this, add a path field to your query shape's resolver object with your preferred path set as the value, as shown below:

"resolver": {
"name": "rest:get",
"service": "service-name-here",
"path": "/your-endpoint-path",
...
}

The above will make the resolver add /your-endpoint-path to the service's endpoint when making the request. To splice the user's query input into the endpoint path, use the same parameter ops method that applies to args.

You would map the query's input to a variable name of your choosing, for example id, then use the serialize property of the resolver's path object to create a string template for the path the resolver should use. Wrap the variable name in brackets {id}, and insert it into the path where appropriate.

The example below demonstrates this:

"resolver": {
"name": "rest:get",
"service": "service-name-here",
"path": {
"ops": [
{"path": "id", "mapping": "$args.id"}
],
"serialize": {"template": "/your-endpoint-path/{id}"}
}
...
}

If you want to assign a value without using an input to the query, you can use value instead of mapping in the paramter ops.

Here's what the resolver looks like if you do that:

"resolver": {
"name": "rest:get",
"service": "service-name-here",
"path": {
"ops": [
{"path": "id", "value": 123}
],
"serialize": {"template": "/your-endpoint-path/{id}"}
}
...
}

Your resolver would hit this path: /endpoint/your-endpoint-path/123.

Adding inputs as query parameters​

You can aslo map input args to query parameters on the resolver's endpoint path.

This requires using a searchParams object with parameter ops to map the input values to the query parameter name.

For example:

"resolver": {
"name": "rest:get",
"service": "recharge",
"path": "/customers",
"searchParams": {
"ops": [{"path": "email", "mapping": "$args.email"}]
}
},

In the above example, the email arg's value is mapped to the email query parameter, which means the endpoint the resolver hits will look like this:

/customers?email=example@email.com

As with setting values for the resolver path, you can use value to assign data to a query parameter directly.

"resolver": {
"name": "rest:get",
"service": "recharge",
"path": "/customers",
"searchParams": {
"ops": [{"path": "email", "value": "example@email.com"}]
}
},

Here's a full example of a query that gets subscription information from Recharge by composing two resolvers that use searchParams:

"getMySubscriptions": {
"shape": "Recharge_SubscriptionList",
"resolver": {
"compose": [
{
"id": "customerByEmail",
"name": "rest:get",
"service": "recharge",
"path": "/customers",
"searchParams": {
"ops": [{"path": "email", "mapping": "$claims.email"}]
}
},
{
"id": "subscriptions",
"name": "rest:get",
"service": "recharge",
"path": "/subscriptions",
"searchParams": {
"ops": [
{
"path": "shopify_customer_id",
"mapping": "$resolvers.customerByEmail.customers[0].shopify_customer_id"
}
]
}
}
]
"results": {
"ops": [
{
"path": "$",
"mapping": [["get", {"path": "$resolvers.subscriptions"}]]
}
]
},
},
"description": "Get my subscriptions"
},

Mapping args to your query's headers​

Custom headers can be added to a resolver with the headers object. Again, use parameter ops to map the header values.

For example, here's how you set the "content-type" header to "application/json":

"resolver": {
"name": "rest:post",
"service": "service-name-here",
"headers": {
"ops": [{"path": "content-type", "value": "application/json"}]
}
},

You can also map input arguments to headers:

"resolver": {
"name": "rest:post",
"service": "service-name-here",
"headers": {
"ops": [{"path": "Authorization", "mapping": "$args.apiKey"}]
}
},

Mapping resolver results​

You can also map a service endpoint's response results to the expected shape of the query's response.

Here's an example of a mutation that creates a checkout in Shopify by composing multiple resolvers, then mapping the results to the shape that the query expects:

"createMyCheckoutSession": {
"shape": "shopify-storefront:Cart",
"resolver": {
"results": {
"ops": [{"path": "$", "mapping": "$resolvers.getShopifyCart"}]
},
"compose": [
{
"id": "createShopifyCart",
"name": "graphql:mutation",
"service": "shopify-storefront",
"fieldName": "cartCreate",
"options": {"selectionSet": "{cart{id}}"},
"args": {
"ops": [
{"path": "input.lines", "mapping": "$args.lines"},
{
"path": "input.buyerIdentity.email",
"mapping": "$claims.email"
}
]
}
},
{
"id": "getShopifyCart",
"name": "graphql:query",
"service": "shopify-storefront",
"fieldName": "cart",
"options": {"selectionSet": "{checkoutUrl}"},
"args": {
"ops": [
{
"path": "id",
"mapping": "$resolvers.createShopifyCart.cart.id"
}
]
}
}
]
},
"description": "Create a Shopify storefront checkout session.",
"args": {
"type": "object",
"properties": {
"lines": {
"type": "array",
"items": {
"type": "object",
"properties": {
"quantity": {"type": "integer"},
"merchandiseId": {"type": "string"},
"sellingPlanId": {"type": "string"}
}
}
}
},
"required": ["lines"]
}
}

Resolver types​

TakeShape provides a library of built-in resolvers when you're writing your own queries and mutations.

GraphQL resolvers​

When writing queries and mutations that use a GraphQL service, you'll have access to two resolvers that execute queries and mutations on the connected service.

graphql:query
graphql:mutation
Options:​
fieldName​

The name of the query or mutation on the GraphQL service's schema that the resolver will use

selectionSet​

Useful when doing mutation composition, this allows the schema author to override or modify the selectionSet of fieldName

ttl​

The number of seconds to cache query results for. This setting only applies to graphl:query resolvers.

timeout​

The number of milliseconds to allowed before timing out. The default is no timeout.

REST resolvers​

When writing queries and mutations that use a REST service, you'll have access to resolvers that execute many of the standard HTTP request methods on the connected service.

rest:get
rest:head
rest:post
rest:put
rest:patch
rest:delete
Options:​
path​

The resource path your resolver is accessing on the REST service. This is appended to the service's base path when making requests.

The path can use variable values, like /character/{id}. This variable would be set using parameters ops and the serialize property of the path object. For example:

"path": {
"ops": [
{
"path": "productId",
"mapping": "$args.product_id"
}
],
"serialize": {"template": "/products/{productId}"}
},
ttl​

The number of seconds to cache request results for. This setting only applies to rest:get resolvers.

timeout​

The number of milliseconds to allowed before timing out. The default is no timeout.

retry​

A number or object that is passed through to the got retry config.

If a retry configuration is specified in both the resolver options and the service configuration, the retry configurations are merged with the resolver taking precedence.

TakeShape resolvers​

There are also TakeShape-specific resolvers that allow you to execute common CRUD actions on model-annotated shapes.

takeshape:get
takeshape:list
takeshape:create
takeshape:update
takeshape:delete
takeshape:duplicate
Options:​
model​

The name of the TakeShape model this resolver should act upon.

Resolver composition​

A resolver can compose multiple resolvers in a pipeline of resolution $resolvers.

Each resolver in the compose array is executed serially and referable by its id property.

id property​

On any resolver step you can provide an id property which gives you a stable, easy-to-use identifier for use in your expressions and directives.

Example​

The id below will allow you to refer to this step elsewhere in the resolver as $resolvers.takeshapeUpdate.

{
"if": "!isEmpty(args.input._id)",
"id": "takeshapeUpdate",
"name": "takeshape:update",
"service": "local",
"options": {
"model": "Product"
}
}

currentQuery context​

The context of a query is available for use in directives and expressions. The context is comprised of:

  • source The return value of this field's parent resolver.
  • args The args provided to the parent query, essentially, your input.
  • $resolvers The results of each step in the compose pipeline. These are available as array index values, $resolvers[0] and optionally with ids you supply in the step config, for example, $resolvers.my-id.foo.
  • results Deprecated, an alias of $resolvers
  • previousStep The output of the previous step or directive, if there was one.

if expressions​

if expressions control the execution of a step in the resolver pipeline. If the expression evaluates as true, the step runs. A step without an if will always run.

JavaScript-style logical operations and a limited number of functions from [lodash/fp](https://gist.github.com/jfmengels/6b973b69c491375117dc) are built-in to our expression engine.

A simple boolean evaluation looks like this:

"if": "args.input._id == 123"

In this next example, we're accessing a property in the args array (which may or may not exist), and utilizing the isEmpty function. isEmpty returns a boolean, so this expression can evaluate on its own:

"if": "isEmpty(args.input._id)"

Groupings, negations and more complicated expressions are also possible:

"if": "!isEmpty($resolvers.takeshapeUpdate.result.shopifyProductId) || !isEmpty($resolvers.takeshapeCreate.result.shopifyProductId)"
Available lodash/fp functions​

Documentation for these functions is on GitHub.

  • at
  • add
  • camelCase
  • capitalize
  • ceil
  • chunk
  • clamp
  • cloneWith
  • compact
  • concat
  • cond
  • conforms
  • conformsTo
  • constant
  • countBy
  • divide
  • each
  • eachRight
  • endsWith
  • entries
  • entriesIn
  • eq
  • escape
  • every
  • filter
  • find
  • first
  • flatMap
  • floor
  • flow
  • forEach
  • forEachRight
  • forIn
  • forInRight
  • forOwn
  • forOwnRight
  • fromPairs
  • get
  • groupBy
  • gt
  • gte
  • has
  • hasIn
  • inRange
  • includes
  • indexOf
  • intersection
  • invert
  • isArguments
  • isArray
  • isArrayBuffer
  • isArrayLike
  • isArrayLikeObject
  • isBoolean
  • isBuffer
  • isDate
  • isElement
  • isEmpty
  • isEqual
  • isEqualWith
  • isError
  • isFinite
  • isFunction
  • isInteger
  • isLength
  • isMap
  • isMatch
  • isMatchWith
  • isNaN
  • isNative
  • isNil
  • isNull
  • isNumber
  • isObject
  • isObjectLike
  • isPlainObject
  • isRegExp
  • isSafeInteger
  • isSet
  • isString
  • isSymbol
  • isTypedArray
  • isUndefined
  • isWeakMap
  • isWeakSet
  • join
  • kebabCase
  • keyBy
  • keys
  • keysIn
  • last
  • lastIndexOf
  • lowerCase
  • lowerFirst
  • lt
  • lte
  • map
  • matches
  • matchesProperty
  • max
  • maxBy
  • mean
  • meanBy
  • min
  • minBy
  • multiply
  • now
  • nth
  • omit
  • orderBy
  • partition
  • pick
  • pickBy
  • property
  • propertyOf
  • pull
  • pullAt
  • range
  • rangeRight
  • reject
  • remove
  • replace
  • result
  • reverse
  • round
  • set
  • size
  • slice
  • snakeCase
  • some
  • sortBy
  • sortedIndex
  • sortedIndexBy
  • sortedIndexOf
  • sortedLastIndex
  • sortedLastIndexBy
  • sortedLastIndexOf
  • sortedUniq
  • sortedUniqBy
  • split
  • startCase
  • startsWith
  • subtract
  • sum
  • sumBy
  • tail
  • take
  • takeRight
  • template
  • times
  • toArray
  • toFinite
  • toInteger
  • toLength
  • toLower
  • toNumber
  • toPairs
  • toPairsIn
  • toPath
  • toPlainObject
  • toSafeInteger
  • toString
  • toUpper
  • trim
  • trimEnd
  • trimStart
  • truncate
  • unescape
  • union
  • unionBy
  • uniq
  • uniqBy
  • uniqueId
  • upperCase
  • upperFirst
  • values
  • words
  • xor
Resolver composition example​

This mutation composes several resolvers together in a pipeline to add a new mutation, upsertMyCustomer, that will update a customer in TakeShape and in the connected Shopify service. This example uses many resolver concepts described above.

"upsertMyCustomer": {
"shape": "Profile",
"resolver": {
"results": {
"ops": [
{"path": "$", "mapping": "$resolvers.updatedProfile"},
{"path": "$", "mapping": "$resolvers.existingProfile"}
]
},
"compose": [
{
"if": "!isEmpty($claims.sub)",
"id": "existingProfile",
"name": "takeshape:find",
"service": "takeshape:local",
"shapeName": "Profile",
"args": {"ops": [{"path": "where.id.eq", "mapping": "$claims.sub"}]}
},
{
"if": "!isEmpty($claims.sub) && !isEmpty($claims.email) && isEmpty($resolvers.existingProfile.shopifyCustomerId)",
"id": "existingCustomerByEmail",
"name": "graphql:query",
"service": "shopify-admin",
"fieldName": "customers",
"options": {"selectionSet": "{edges{node{id}}}"},
"args": {
"ops": [
{"path": "first", "value": 1},
{
"path": "query",
"mapping": [
["get", {"path": "$source.email"}],
["prepend", {"text": "email:"}]
]
}
]
}
},
{
"if": "!isEmpty($claims.sub) && !isEmpty($claims.email) && !isEmpty($resolvers.existingProfile.shopifyCustomerId)",
"id": "existingCustomerById",
"name": "graphql:query",
"service": "shopify-admin",
"fieldName": "customer",
"options": {
"selectionSet": "{id firstName lastName defaultAddress{address1 address2 firstName lastName city province provinceCode zip country}}"
},
"args": {
"ops": [
{
"path": "id",
"mapping": [
[
"get",
{"path": "$resolvers.existingProfile.shopifyCustomerId"}
]
]
}
]
}
},
{
"if": "!isEmpty($claims.sub) && !isEmpty($claims.email) && (!isEmpty($resolvers.existingProfile.shopifyCustomerId) || !isEmpty($resolvers.existingCustomerByEmail.edges[0].node.id) || !isEmpty($resolvers.existingCustomerById.id))",
"id": "updatedShopifyCustomer",
"name": "graphql:mutation",
"service": "shopify-admin",
"fieldName": "customerUpdate",
"options": {
"selectionSet": "{customer{id firstName lastName defaultAddress{address1 address2 firstName lastName city province provinceCode zip country}} userErrors{field message}}"
},
"args": {
"ops": [
{"path": "input.email", "mapping": "$claims.email"},
{
"path": "input.id",
"mapping": [
[
"get",
{"path": "$resolvers.existingProfile.shopifyCustomerId"}
],
[
"get",
{"path": "$resolvers.createShopifyCustomer.customer.id"}
],
[
"get",
{
"path": "$resolvers.existingCustomerByEmail.edges[0].node.id"
}
],
["get", {"path": "$resolvers.existingCustomerById.id"}],
["get", {"path": "$args.id"}]
]
},
{"path": "input.firstName", "mapping": "$args.firstName"},
{"path": "input.lastName", "mapping": "$args.lastName"},
{"path": "input.note", "mapping": "$args.description"},
{
"path": "input.addresses.firstName",
"mapping": "$args.firstName"
},
{
"path": "input.addresses.lastName",
"mapping": "$args.lastName"
},
{
"path": "input.addresses.address1",
"mapping": "$args.defaultAddress.address1"
},
{
"path": "input.addresses.address2",
"mapping": "$args.defaultAddress.address2"
},
{
"path": "input.addresses.city",
"mapping": "$args.defaultAddress.city"
},
{
"path": "input.addresses.country",
"mapping": "$args.defaultAddress.country"
},
{
"path": "input.addresses.zip",
"mapping": "$args.defaultAddress.zip"
},
{
"path": "input.addresses.provinceCode",
"mapping": "$args.defaultAddress.provinceCode"
}
]
}
},
{
"if": "!isEmpty($claims.sub) && !isEmpty($claims.email) && !isEmpty($resolvers.existingProfile)",
"id": "updatedProfile",
"name": "takeshape:update",
"service": "takeshape:local",
"shapeName": "Profile",
"args": {
"ops": [
{
"path": "input._id",
"mapping": "$resolvers.existingProfile._id"
},
{
"path": "input.shopifyCustomerId",
"mapping": [
[
"get",
{"path": "$resolvers.updatedShopifyCustomer.customer.id"}
],
[
"get",
{
"path": "$resolvers.existingCustomerByEmail.edges[0].node.id"
}
],
[
"get",
{"path": "$resolvers.existingProfile.shopifyCustomerId"}
]
]
},
{"path": "input.firstName", "mapping": "$args.firstName"},
{"path": "input.lastName", "mapping": "$args.lastName"}
]
}
}
]
},
"description": "Upsert my customer.",
"args": {
"type": "object",
"properties": {
"id": {"type": "string"},
"sub": {"type": "string"},
"email": {"type": "string"},
"firstName": {"type": "string"},
"lastName": {"type": "string"},
"description": {"type": "string"},
"defaultAddress": {
"type": "object",
"properties": {
"address1": {"type": "string"},
"address2": {"type": "string"},
"city": {"type": "string"},
"country": {"type": "string"},
"zip": {"type": "string"},
"provinceCode": {"type": "string"},
"province": {"type": "string"}
}
}
}
}
},

Here's a step-by-step walkthrough of what each resolver is doing:

"existingProfile"

Checks if there's an existing profile in the TakeShape project's database. Uses $claims, which are sent in the query's headers by auth0. Read more about $claims in our auth0 provider guide.

"existingCustomerByEmail"

If the existingProfile resolver above failed to retrieve a profile with a Shopify customer ID, try to find a customer with the Shopify Admin API, and get the customer's ID.

"existingCustomerById"

If the existingProfile resolver successfully retrieved a profile with a Shopify customer ID, fetch the customer from Shopify.

"existingCustomerById"

If the existingProfile, existingCustomerByEmail, or existingCustomerById resolvers successfully retrieved a profile and the corresponding Shopify customer information, update that customer in Shopify with the input args provided to the query.

"existingCustomerById"

If the existingProfile resolver successfully retrieved a profile, update that profile in TakeShape with the input args provided to the query.

Mappings​

Mappings describe the location of a property's data in TakeShape or in a connected service. A shape property with a @mapping reads and writes data to the provided location. Custom shape properties require a @mapping.

The value of a mapping follows the format serviceId:shapeName.key.

For TakeShape data, the key is randomly generated by the client when a field is added. This is done to allow Shape field names to be renamed without performing migrations. (TakeShape uses shortId to generate database keys)

For connected services, the key is the name of a property on a remote shape.

A @mapping can also be an array of locations. In this case, mappings that produce values are preferred in order.

Query mappings​

In queries, the mapping is used to determine where to get the value of the current property.

In the following example, the @mapping is pointing to the local takeshape database table Book and using the K66gvuh1h column of the row to select the value:

{
"title": {
"type": "string",
"@mapping": "takeshape:local:Book.K66gvuh1h"
}
}

In this next example, @mapping is pointing to the Product type in the Shopify GraphQL API and using the descriptionHTML property to fetch the value of html:

{
"html": {
"type": "string",
"@mapping": "shopify:my-store:Product.descriptionHtml"
}
}

Mutation mappings​

In mutations, the mapping is used to map data of the given shape back to its original type in GraphQL.

Models​

A Model is a shape with data that is viewable and editable in the TakeShape web client and through your project's GraphQL API. Models can reference other shapes in their schemas as objects or repeaters, and they can have database relationships with other Models. Models can also be remotely accessed from connected services.

Shapes can defined by as models by providing the model key with the properties:

  • type: ('single' | 'multiple' | 'taxonomy')

Any Shape that's a model will also have queries and mutations automatically created for common operations like Get, List, Create, Update, and Delete.

Relationships​

One-to-one, many-to-one, and many-to-many relationships can also be created between Models. Relationships are configured by setting the @relationship on a shape's property.

Back references​

@backreference controls whether TakeShape will automatically create a reference for you from the referred-to type shape back to the referring shape in a relationship.

@backreference can be either a boolean β€” true tells TakeShape to create a dynamic reference that uses the shape name to generate a reference. For example, "@backreference": true on the shape Post which is related to the shape Author would create a field on Author types called postSet.

If you provide @backreference as an object, you can specify an explicit name for your back reference.

Examples​

boolean @backreference
{
"shapes": {
"Post": {
"properties": {
"author": {
"$ref": "#/shapes/TSRelationship/schema",
"@relationship": {
"shapeIds": ["Sk6yWoljg"],
"type": "single"
},
"@backreference": true
}
}
}
}
}
object @backreference
{
"shapes": {
"Post": {
"properties": {
"author": {
"$ref": "#/shapes/TSRelationship/schema",
"@relationship": {
"shapeIds": ["Sk6yWoljg"],
"type": "single"
},
"@backreference": {
"name": "postReference"
}
}
}
}
}
}

References​

By design, the schema is flat. Rather than using deeply nested and potentially repeated objects, we create references using the @ref property. References link to other objects in the schema file or in a connected service's schema.

Objects in the schema are referenced using the local:ShapeName syntax, while remote objects are referenced using the service-key:TypeName syntax.

References can be used in queries and mutations in place of a shape's name in the args and shape keys. They can also be set in a shape's inner schema properties using the @ref key.

oneOf​

The oneOf property can be used to create union fields.

Examples​

  • An array of @ref schemas
oneOf example schema fragment created by adding a shape array widget
{
"type": "array",
"items": {"oneOf": [{"@ref": "local:Dog"}, {"@ref": "local:Cat"}]}
}
  • An object with @ref schemas
oneOf example schema fragment as an object
"featuredPet": {"oneOf": [{"@ref": "local:Dog"}, {"@ref": "local:Cat"}]}

extends​

The extends keyword can be used in a shape's schema to extend another shape. This is most useful when working with remote shapes where you can't modify the original shape, but want to add specific properties.

Examples​

This example shows a how to extend a Shopify Product with a Note that is stored in TakeShape's ShapeDB.

Extending
{
"Shopify_Product": {
"id": "Shopify_Product",
"name": "Shopify_Product",
"title": "Shopify_Product",
"schema": {
"extends": [
{"@ref": "my-shopify-store:Product"},
{
"type": "object",
"properties": {
"note": {
"@ref": "local:Note",
"@resolver": {
"name": "takeshape:find",
"service": "takeshape:local",
"shapeName": "Note",
"args": {
"ops": [{"path": "where.productId.eq", "mapping": "$source.id"}]
}
},
"title": "Note"
}
}
}
]
}
},
"Note": {
"id": "a_fjI9J54",
"name": "Note",
"title": "Note",
"workflow": "default",
"model": {"type": "multiple"},
"schema": {
"type": "object",
"properties": {
"productId": {
"@l10n": false,
"type": "string",
"title": "Product Id",
"@mapping": "takeshape:local:Note.vzxZ2Ld93"
},
"body": {
"minLength": 1,
"type": "string",
"@tag": "mdx",
"title": "Body",
"@mapping": "takeshape:local:Note.gOv6xZKgg"
}
},
"required": ["productId", "body"]
}
}
}

Built-in objects​

The TakeShape schema also contains some "built-in" objects that are required on every project.

The names of built-in objects are reserved.

We hide those objects when displaying or exporting a schema, and we automatically add them when a schema is created or imported.

Built-in shapes​

Asset, TSRelationship, TSColorHsl, TSColorHsv, TSColorRgb, TSColor, TsStaticSite, TsStaticSiteEnvironmentVariables, TsStaticSiteTriggers

Built-in forms​

Asset, TsStaticSite