Migrations with Tento CLI

Shopify Metaobject and Metafield definitions require you to specify a strict schema of definitions and if (when) you need to change those entities - you will need to do it via migrations.

Overview

This guide assumes familiarity with:

Tento has a CLI tool for managing Shopify Metaobjects and Metafields schema definitions.

Based on your schema, Tento let’s you push your definitions schema directly to the Shopify, pull schema definitions from Shopify and has a couple of commands.

npm
yarn
pnpm
bun
npx tento pull
npx tento push
tento pulllets you pull (introspect) Shopify definitions schema, convert it to Tento schema and save it to your codebase, see here
tento pushlets you push your Tento definitions schema directly to Shopify, see here

tento.config.ts

Tento migrations is configured through tento.config.ts configuration file.
It’s required to provide schemaPath, shop and X-Shopify-Access-Token for Tento.

πŸ“¦ <project root>
 β”œ πŸ“‚ src
 β”œ πŸ“œ .env
 β”œ πŸ“œ tento.config.ts  <--- Tento config file
 β”œ πŸ“œ package.json
 β”” πŸ“œ tsconfig.json
tento.config.ts
import 'dotenv/config';
import { defineConfig } from "@drizzle-team/tento/cli";

export default defineConfig({
  schemaPath: './src/schema.ts',
  shop: process.env.SHOPIFY_SHOP_ID!,
  headers: {
    'X-Shopify-Access-Token': process.env.SHOPIFY_ACCESS_TOKEN!,
  },
});
IMPORTANT

To have understanding of what SHOPIFY_SHOP_ID and SHOPIFY_ACCESS_TOKEN variables represent go through Tento Get started with OAuth.

pull

tento pull lets literally pull (introspect) your Shopify definitions schema and generate schema.ts tento schema file.

How it works under the hood?

When you run pull command it will:

  1. Pull Metaobject and Metafield definitions schema from Shopify
  2. Generate schema.ts tento schema file and save it to out folder
                                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                   β”‚                        β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚                        β”‚
β”‚ ~ tento pull             β”‚       β”‚                        β”‚
β””β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚        Shopify         β”‚
  β”‚                                β”‚                        β”‚
  β”” Pull definitions schema -----> β”‚                        β”‚
  β”Œ Generate Tento schema   <----- β”‚                        β”‚
  β”‚ TypeScript file                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  β”‚
  v
src/schema.ts
import { metaobject, metafield } from '@drizzle-team/tento';

export const description = metafield({
  name: "Description",
  key: "description",
  namespace: "custom",
  ownerType: "PRODUCT",
  fieldDefinition: (f) => f.multiLineTextField(),
});

export const designer = metaobject({
  name: "Designer",
  type: "designer",
  fieldDefinitions: (f) => ({
    name: f.singleLineTextField({
      name: "Title",
      required: true,
      validations: (v) => [v.min(1), v.max(50)],
    }),
    description: f.multiLineTextField({
      name: "Description",
    }),
    website: f.url({
      name: "Website",
    }),
  }),
});

push

tento push lets literally push your Tento definitions schema directly to Shopify.

How it works under the hood?

When you run Tento push command it will:

  1. Read through your Tento definitions schema file
  2. Pull (introspect) Shopify definitions schema
  3. Based on differences between those two it will generate list of definitions to create, update or delete
  4. Apply those differences to the Shopify
src/schema.ts
import { metaobject, metafield } from '@drizzle-team/tento';

export const description = metafield({
  name: "Description",
  key: "description",
  namespace: "custom",
  ownerType: "PRODUCT",
  fieldDefinition: (f) => f.multiLineTextField(),
});

export const designer = metaobject({
  name: "Designer",
  type: "designer",
  fieldDefinitions: (f) => ({
    name: f.singleLineTextField({
      name: "Title",
      required: true,
      validations: (v) => [v.min(1), v.max(50)],
    }),
    description: f.multiLineTextField({
      name: "Description",
    }),
    website: f.url({
      name: "Website",
    }),
  }),
});
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  
β”‚ ~ tento push        β”‚                  
β””β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  
  β”‚                                            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”” Pull current definitions schema ---------> β”‚                          β”‚
                                               β”‚                          β”‚
  β”Œ Generate alternations based on diff <----  β”‚         Shopify          β”‚
  β”‚                                            β”‚                          β”‚
  β”” Apply differences to the Shopify ------->  β”‚                          β”‚
                                       β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                       β”‚
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   create metafield description, create metaobject designer;

Client

Tento also supports apply and remove schema definitions using client.

Apply


optiondefaultvaluesdescription
unknownEntitiesignoreignore, removehelps define the scope of metaobjects and metafields to be migrated, preventing unnecessary downloads and avoiding the deletion of other existing items
import 'dotenv/config';
import '@shopify/shopify-api/adapters/web-api';
import * as schema from './db/schema';
import { createClient, tento } from '@drizzle-team/tento';

const client = tento({
  client: createClient({
    shop: process.env.SHOPIFY_SHOP_ID!,
    headers: {
      "X-Shopify-Access-Token": process.env.SHOPIFY_ACCESS_TOKEN!,
    },
  }),
  schema,
});

async function main() {
  // 'ignore' strategy as default
  await client.applySchema();

  // you can specify explicitly configuration option you want
  await client.applySchema({ unknownEntities: "ignore" });
  await client.applySchema({ unknownEntities: "delete" });
}

main();
ignoremeans that only the metaobjects and metafields from your definitions schema will be processed.
deletemeans that all metaobjects and metafields will be processed. If you select this option and run command, we will create/ update your schema in Shopify and remove all metaobjects and metafields that do not exist in your schema.

Remove


optiondefaultvaluesdescription
unknownEntitiesignoreignore, removehelps define the scope of metaobjects and metafields to be migrated, preventing unnecessary downloads and avoiding the deletion of other existing items
import 'dotenv/config';
import '@shopify/shopify-api/adapters/web-api';
import * as schema from './db/schema';
import { createClient, tento } from '@drizzle-team/tento';

const client = tento({
  client: createClient({
    shop: process.env.SHOPIFY_SHOP_ID!,
    headers: {
      "X-Shopify-Access-Token": process.env.SHOPIFY_ACCESS_TOKEN!,
    },
  }),
  schema,
});

async function main() {
  // 'ignore' strategy as default
  await client.removeSchema();

  // you can specify explicitly configuration option you want
  await client.removeSchema({ unknownEntities: "ignore" });
  await client.removeSchema({ unknownEntities: "delete" });
}

main();
ignoremeans that only the metaobjects and metafields from your definitions schema will be processed.
deletemeans that all metaobjects and metafields will be processed. If you select this option and run command, we will create/ update your schema in Shopify and remove all metaobjects and metafields that do not exist in your schema.