Basic space setup and CRUD with Contentful CLI for development
Tags: contentful, javascript
Published: 2020-02-08 17:18:17

We love CLI's. They help us automate things. But Contentful's CLI documentation is scattered all over the place. Here, I walk you through the basic bits needed for development. This is not comprehensive, but it should help get you going.

# Basic setup

## Get the CLI

Brew, yarn, or npm. Eg: `npm install -g contentful-cli`

Ref: [Contentful CLI git repo](https://github.com/contentful/contentful-cli).

## Get your CMA

Log into Contentful. Go to Settings. Go to Content management tokens. Click on "Generate personal token". Copy the token somewhere (important because you'll never see it on the site ever again).

## Login on CLI

Run this in shell: `contentful login --management-token YOUR_CONTENT_MANAGEMENT_TOKEN`. It'll tell you it saved your access token in .contentfulrc.json in your present working directory.

Ref: [Contentful CLI - Documentation](https://github.com/contentful/contentful-cli/tree/master/docs/login)

## Create a space

`contentful space create --name 'Your Space Name'`. You might need `--management-token` and pass in your content management key. And `--default-locale` if you want to set a default locale that isn't us-En.

If successful, it'll show you your space name alongside your space-id. Copy the space-id down somewhere.

Ref: [Contentful CLI - Space command](https://github.com/contentful/contentful-cli/tree/master/docs/space)

## Remember your space

`contentful space use -s YOUR_SPACE_ID`. This lets the CLI remember which space you're working in so you don't have to pass in `--space-id` all the time.

Ref: [Contentful CLI - `space use` command](https://github.com/contentful/contentful-cli/tree/master/docs/space/use)

## Logout

Don't logout now. But if you want to: `contentful logout`. It'll delete the .contentfulrc.json.

# Set up space with migrations

Now it's time to create content types (like database tables) and their fields in your space. The CLI uses a migration script written in JavaScript to do this. It's not immediately clear from the docs if we can write this in other languages and I don't have time to check right now.

In general, most of the info is here: [contentful-migration - content model migration tool](https://github.com/contentful/contentful-migration). But this is a large chunk of everything.

You'll need to look at the data model too, to figure out what you can use to make, say, a blog post title and a post body: [Contentful data model](https://www.contentful.com/developers/docs/concepts/data-model/)

Here is a more specific way to set things up on a basic level: [The right way to migrate your content using the Contentful Migration CLI](https://www.contentful.com/blog/2017/09/18/using-the-contentful-migration-cli/)

In brief, your script should look something like this, taken directly from the link above:

```
module.exports = function(migration) {
  const author = migration.createContentType('author')
  author
    .name('Author')
    .description('Author of a blog post')
    
  author
    .createField('fullName')
    .name('Full name')
    .type('Symbol')
    .required(true)
    
  author
    .createField('twitter')
    .name('Twitter')
    .type('Symbol')
    .validations([
      { "unique": true },
      { "regexp": 
        { "pattern": "^\\w[\\w.-]*@([\\w-]+\\.)+[\\w-]+$" }
      }
    ])
    
  const blogPost = migration.editContentType('blogPost')
  blogPost.createField('author')
    .name('Author')
    .type('Link')
    .linkType('Entry')
    .validations([
      { "linkContentType": ["author"] }
    ])
};
```

Put it in migrations/01-add-author.js and run `contentful space migration migrations/01-add-author.js`.

If you didn't run `contentful space use -s YOUR_SPACE_ID` earlier, you might have to pass in `--space-id YOUR_SPACE_ID` too.

I'll paste my whole blog setup migration script at the end of this article so you can have something a little meatier to look at.

# Delete content types and fields

This is our development phase so we CRUD everything all the time. We've created content types. But how to delete? It's probably possible to use native commands to delete stuff but, again, it's not immediately clear from the docs how to do this.

I'm in a rush and I'm too tired from reading docs all day so I'll cheat a little and use this library called [contentful-clean-space](https://github.com/jugglingthebits/contentful-clean-space). Install it: `npm install -g contentful-clean-space`.

Then, run: `contentful-clean-space --space-id YOUR_SPACE_ID --accesstoken YOUR_CONTENT_MANAGEMENT_TOKEN --content-types`. This deletes all entries/records and content types too. Check out their repo for a bit more info.

(Reads of entries are through the Contentful [CDA](https://www.contentful.com/developers/docs/references/content-delivery-api/) and updates are done later through the [CMA](https://www.contentful.com/developers/docs/references/content-management-api/))

# The end

I think that's it.

# References

[CLI docs](https://github.com/contentful/contentful-cli/tree/master/docs): basic use of CLI like login and how to do a migration. But does not include what a migration file looks like, and how to add content types and fields to them.

[CLI migration docs](https://github.com/contentful/contentful-migration): details on how to write the migration script.

[Data model](https://www.contentful.com/developers/docs/concepts/data-model): tells you about what fields can be added to your migration script, but has no info on what the script itself looks like.

[Automated Contentful migrations](https://www.robinandeer.com/blog/2019/04/26/automated-contentful-migrations/): a couple of examples for migration scripts worth skimming through for a start.

[The right way to migrate your content using the Contentful Migration CLI](https://www.contentful.com/blog/2017/09/18/using-the-contentful-migration-cli/): more useful details for migration scripts, like how to link from one content type to another:

```
const blogPost = migration.editContentType('blogPost')
  blogPost.createField('author')
    .name('Author')
    .type('Link')
    .linkType('Entry')

    // Isolates link to only the "author" content type.
    .validations([
      { "linkContentType": ["author"] }
    ])
};
```

[How to do validations on items({})](https://www.contentfulcommunity.com/t/confusing-validations-error-from-the-migrations-cli/776/4). Basically:

```
blogPost.createField("categories")
    .name("Categories")
    .required(false)
    .type('Array')
    .items({
        type: 'Link',
        linkType: "Entry",

        // Right here.
        validations: [{
            linkContentType: [
                "categories"
            ],
        }]

    })
```

[Contentful Migration Cheat Sheet](https://www.cheatography.com/gburgett/cheat-sheets/contentful-migration/): clues you in on how to do some of these fields, and also nice reminders.

Delete content types and fields from CLI: [contentful-clean-space](https://github.com/jugglingthebits/contentful-clean-space)

# My blog site migration script

It's not the best but it's a good start. It sets up content types and fields for generic pages, blog posts, and default site settings.

```
module.exports = (migration, context) => {

    // BLOG POST CONTENT TYPE

    const blogPost = migration.createContentType("blogPost")
        .name("Blog Post")
        .description("Blog post model")
        .displayField("title")

    blogPost.createField("title")
        .name("Title")
        .type("Symbol")
        .required(false)

    blogPost.createField("body")
        .name("Body")
        .type("Text")
        .required(false)

    blogPost.createField("author")
        .name("Author name")
        .type("Symbol")
        .required(false)

    blogPost.createField("datetime")
        .name("Datetime")
        .type("Date")
        .required(false)

    blogPost.createField("categories")
        .name("Categories")
        .type('Array')
        .items({
            type: 'Link',
            linkType: "Entry",
            validations: [{
                linkContentType: [
                    "categories"
                ],
            }]
        })
        .required(false)

    blogPost.createField("tags")
        .name("Tags")
        .type("Array")
        .items({ "type": "Symbol" })


    blogPost.createField("featuredImage")
        .name("Featured image")
        .type("Link")
        .linkType("Asset")
        .required(false)


    // CATEGORIES CONTENT TYPE

    const categories = migration.createContentType('categories')
        .name('Categories')
        .description('Categories for blog posts')
        .displayField("category")

    categories.createField('category')
        .name('Category')
        .type('Symbol')
        .required(true)

    categories.createField('slug')
        .name('URL Slug')
        .type('Symbol')
        .validations([{ "unique": true }])
        .required(false)

    categories.createField('featuredImage')
        .name('Featured image')
        .type('Link')
        .linkType('Asset')
        .required(false)

    categories.createField('description')
        .name('Description')
        .type('Text')
        .required(false)


    // PAGE CONTENT TYPE

    const page = migration.createContentType("page")
        .name("Page")
        .description("Page model")
        .displayField("title")

    page.createField("title")
        .name("Title")
        .type("Symbol")
        .required(false)

    page.createField("body")
        .name("Body")
        .type("Text")
        .required(false)

    page.createField("featuredImage")
        .name("Featured image")
        .type("Link")
        .linkType("Asset")
        .required(false)

    // SITE SETTINGS

    const siteSettings = migration.createContentType("siteSettings")
        .name("Site settings")
        .description("Site Settings model")
        .displayField("siteName")

    siteSettings.createField("siteName")
        .name("Site name")
        .type("Symbol")
        .required(false)

    siteSettings.createField("author")
        .name("Author")
        .type("Symbol")
        .required(false)

    siteSettings.createField("address")
        .name("Address")
        .type("Symbol")
        .required(false)

    siteSettings.createField("phoneNumber")
        .name("Phone number")
        .type("Symbol")
        .required(false)

    siteSettings.createField("email")
        .name("Email")
        .type("Symbol")
        .required(false)

    siteSettings.createField("facebookLink")
        .name("Facebook link")
        .type("Symbol")
        .required(false)

    siteSettings.createField("twitterLink")
        .name("Twitter link")
        .type("Symbol")
        .required(false)

    siteSettings.createField("instagramLink")
        .name("Instagram link")
        .type("Symbol")
        .required(false)

    siteSettings.createField("defaultImage")
        .name("Default Image")
        .type("Link")
        .linkType("Asset")
        .required(false)
}
```