Loving Tina? ⭐️ us on GitHubStar

Docs

v.Latest
Introduction
Core Concepts
Querying Content
Editing
Customizing Tina
Going To Production
Media
Drafts
Guides
Further Reference
    From GraphQL Gateway to TinaCMS

    Package reorganization

    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-gateway

    As 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@latest
    yarn remove tina-graphql-gateway
    - import { useGraphqlForms } from 'tina-graphql-gateway'
    + import { useGraphqlForms } from 'tinacms'
    Heads up, useGraphqlForms is now considered a lower-level API. You may want to instead use the default import from tinacms to configure things. Learn more

    tina-graphql-gateway-cli is now @tinacms/cli

    yarn add @tinacms/cli
    yarn remove tina-graphql-gateway-cli
    - import { defineSchema } from 'tina-graphql-gateway-cli'
    + import { defineSchema } from 'tinacms'
    Note: the defineSchema API 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

    The new defineSchema API

    We'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.

    Collections now accept a fields or templates property

    You 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 changes

    Types 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,
    })
    One important gotcha

    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 plugin
    name: '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. .

    Every type can be a list

    Previously, 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"]
    }

    Introducing the object type

    Tina 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 that blocks usage would always be as an array. We'll be keeping that assumption with the blocks type for compatibility, but object will allow for non-array polymorphic objects.
    Defining an object type

    An 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: object with templates: [] and list: false is 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"
    }]
    }

    Introducing the dataJSON field

    You 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"
    }
    }
    }

    Lists queries will now adhere to the GraphQL connection spec

    Read the spec

    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 default

    There 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.

    Other changes

    References now point to more than one collection.

    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 {
    title
    author {
    ...on Author_Document {
    name
    }
    ...on Post_Document {
    title
    }
    }
    }
    }

    The template field on polymorphic objects (formerly blocks) is now _template

    Old API:

    ---
    myBlocks:
    - template: hero
    title: Hello
    ---

    New API:

    ---
    myBlocks:
    - _template: hero
    title: Hello
    ---

    data __typename values have changed

    They 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 {
    title
    myBlocks {
    ...on Page_Hero_Data { # previously this would have been Hero_Data
    # ...
    }
    }
    }
    }
    }

    Undefined list fields will return null

    Previously 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