As we've trialed the GraphQL API and how it works with TinaCMS, it's become apparent that this is the primary path we want to carve out for users of Tina. We'll be moving the tina-graphq-gateway APIs in to the tinacms package to reflect its more central role in the Tina workflow. Other backend integrations will still be available to build out via the new @tinacms/toolkit package. You can read more details on those changes here.
tinacms is absorbing tina-graphql-gatewayAs a result, upgrading to the new and improved GraphQL experience will require you to move to tinacms@0.50.0 and remove the tina-graphql-gateway package entirely.
yarn add tinacms@latestyarn remove tina-graphql-gateway
- import { useGraphqlForms } from 'tina-graphql-gateway'+ import { useGraphqlForms } from 'tinacms'
Heads up,useGraphqlFormsis now considered a lower-level API. You may want to instead use the default import fromtinacmsto configure things. Learn more
tina-graphql-gateway-cli is now @tinacms/cliyarn add @tinacms/cliyarn remove tina-graphql-gateway-cli
- import { defineSchema } from 'tina-graphql-gateway-cli'+ import { defineSchema } from 'tinacms'
Note: thedefineSchemaAPI has changed, too. Read on for how to upgrade
tina-gql cli command is now tinacms- yarn tina-gql server:start+ yarn tinacms server:start
defineSchema APIWe're going to be leaning on a more primitive concept of how types are defined with Tina, and in doing so will be introducing some breaking changes to the way schemas are defined.
fields or templates propertyYou can now provide fields instead of templates for your collection, doing so will result in a more straightforward schema definition:
{collections: [{name: 'post',label: 'Post',path: 'content/posts',fields: [{name: 'title',label: 'Title',type: 'string', // learn more about _type_ changes below},],},]}
Why?
Previously, a collection could define multiple templates, the ambiguity introduced with this feature meant that your documents needed a _template field on them so we'd know which one they belonged to. It also mean having to disambiguate your queries in graphql:
getPostDocument(relativePage: $relativePath) {data {...on Article_Doc_Data {title}}}
Going forward, if you use fields on a collection, you can omit the _template key and simplify your query:
getPostDocument(relativePage: $relativePath) {data {title}}
type changesTypes will look a little bit different, and are meant to reflect the lowest form of the shape they can represent. Moving forward, the ui field will represent the UI portion of what you might expect. For a blog post "description" field, you'd define it like this:
{type: "string",label: "Description",name: "description",}
By default string will use the text field, but you can change that by specifying the component:
{type: "string",label: "Description",name: "description",ui: {component: "textarea"}}
For the most part, the UI properties are added to the field and adhere to the existing capabilities of Tina's core field plugins. But there's nothing stopping you from providing your own components -- just be sure to register those with the CMS object on the frontend:
{type: "string",label: "Description",name: "description",ui: {component: "myMapField"someAdditionalMapConfig: 'some-value'}}
Register your myMapField with Tina:
cms.fields.add({name: 'myMapField',Component: MapPicker,})
Every property in the defineSchema API must be serializable. Meaning functions will not work. For example, there's no way to define a validate or parse function at this level. However, you can either use the formifyCallback API to get access to the Tina form, or provide your own logic by specifying a plugin of your choice:
{type: "string",label: "Description",name: "description",ui: {component: "myText"}}
And then when you register the plugin, provide your custom logic here:
import { TextFieldPlugin } from 'tinacms'// ...cms.fields.add({...TextFieldPlugin, // spread existing text pluginname: 'myText',validate: value => {someValidationLogic(value)},})
Why?
The reality is that under the hood this has made no difference to the backend, so we're removing it as a point of friction. Instead, type is the true definition of the field's shape, while ui can be used for customizing the look and behavior of the field's UI.
.
type can be a listPreviously, we had a list field, which allowed you to supply a field property. Instead, every primitive type can be represented as a list:
{type: "string",label: "Categories",name: "categories",list: true}
Additionally, enumerable lists and selects are inferred from the options property. The following example is represented by a select field:
{type: "string",label: "Categories",name: "categories",options: ["fitness", "movies", "music"]}
While this, is a checkbox field
{type: "string",label: "Categories",name: "categories"list: true,options: ["fitness", "movies", "music"]}
object typeTina currently represents the concept of an object in two ways: a group (and group-list), which is a uniform collection of fields; and blocks, which is a polymporphic collection. Moving forward, we'll be introducing a more comprehensive type, which envelopes the behavior of both group and blocks, and since every field can be a list, this also makes group-list redundant.
Note: we've previously assumed thatblocksusage would always be as an array. We'll be keeping that assumption with theblockstype for compatibility, butobjectwill allow for non-array polymorphic objects.
object typeAn object type takes either a fields or templates property (just like the collections definition). If you supply fields, you'll end up with what is essentially a group item. And if you say list: true, you'll have what used to be a group-list definition.
Likewise, if you supply a templates field and list: true, you'll get the same API as blocks. However you can also say list: false (or omit it entirely), and you'll have a polymorphic object which is not an array.
Gotcha -type: objectwithtemplates: []andlist: falseis not yet supported for form generation. You can use it your API but won't be able to edit that field.
This is identical to the current blocks definition:
{type: "object",label: "Page Sections",name: "pageSections",list: true,templates: [{label: "Hero",name: "hero",fields: [{label: "Title",name: "title",type: "string"}]}]}
And here is one for group:
{type: "object",label: "Hero",name: "hero",fields: [{label: "Title",name: "title",type: "string"}]}
dataJSON fieldYou can now request dataJSON for the entire data object as a single query key. This is great for more tedious queries like theme files where including each item in the result is cumbersome.
Note there is no typescript help for this feature for now
getThemeDocument(relativePath: $relativePath) {dataJSON}
{"getThemeDocument": {"dataJSON": {"every": "field","in": {"the": "document"},"is": "returned"}}}
Keep in mind, dataJSON does not resolve across multiple documents. Instead, it will return the foreign key for a reference:
{"getPostDocument": {"data": {"title": "Hello, World!","author": "path/to/author.md"}}}
Previously, lists would return a simple array of items:
{getPostList {id}}
Which would result in:
{"data": {"getPostList": [{"id": "content/posts/voteForPedro.md"}]}}
In the new API, you'll need to step through edges & nodes:
{getPostList {edges {node {id}}}}
{"data": {"getPostList": {"edges": [{"node": {"id": "content/posts/voteForPedro.md"}}]}}}
Why?
The GraphQL connection spec opens up a more future-proof structure, allowing us to put more information in to the connection itself like how many results have been returned, and how to request the next page of data.
Read a detailed explanation of how the connection spec provides a richer set of capabilities.
Note: sorting and filtering is still not supported for list queries.
_body is no longer included by defaultThere is instead an isBody boolean which can be added to any string field
Why?
Since markdown files sort of have an implicit "body" to them, we were automatically populating a field which represented the body of your markdown file. This wasn't that useful, and kind of annoying. Instead, just attach isBody to the field which you want to represent your markdown "body":
{collections: [{name: "post",label: "Post",path: "content/posts",fields: [{name: "title",label: "Title",type: "string"}{name: "myBody",label: "My Body",type: "string",component: 'textarea',isBody: true}]}]}
This would result in a form field called My Body getting saved to the body of your markdown file (if you're using markdown):
---title: Hello, World!---This is the body of the file, it's edited through the "My Body" field in your form.
Instead of a collection property, you must now define a collections field, which is an array:
{type: "reference",label: "Author",name: "author",collections: ["author"]}
{getPostDocument(relativePath: "hello.md") {data {titleauthor {...on Author_Document {name}...on Post_Document {title}}}}
template field on polymorphic objects (formerly blocks) is now _templateOld API:
---myBlocks:- template: herotitle: Hello---
New API:
---myBlocks:- _template: herotitle: Hello---
data __typename values have changedThey now include the proper namespace to prevent naming collisions and no longer require _Doc_Data suffix. All generated __typename properties are going to be slightly different. We weren't fully namespacing fields, so it wasn't possible to guarantee that no collisions would occur. The pain felt here will likely be most seen when querying and filtering through blocks. This ensures the stability of this type in the future
{getPageDocument(relativePath: "home.md") {data {titlemyBlocks {...on Page_Hero_Data { # previously this would have been Hero_Data# ...}}}}}
nullPreviously a listable field which wasn't defined in the document was treated as an empty array. Moving forward the API response will result in null rather than []:
---title: 'Hello, World'categories: []---
The response would be categories: []. If you omit the field entirely:
---title: 'Hello, World'---
The response will be categories: null. Previously this would have been [], which was incorrect